View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.parser;
18  
19  import org.apache.commons.jexl3.JexlArithmetic;
20  import org.apache.commons.jexl3.JexlInfo;
21  import org.apache.commons.jexl3.introspection.JexlMethod;
22  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
23  import org.apache.commons.jexl3.introspection.JexlPropertySet;
24  
25  /**
26   * Base class for parser nodes - holds an 'image' of the token for later use.
27   *
28   * @since 2.0
29   */
30  public abstract class JexlNode extends SimpleNode {
31      /**
32       * A marker interface for constants.
33       * @param <T> the literal type
34       */
35      public interface Constant<T> {
36          T getLiteral();
37      }
38      /**
39       * Marker interface for cachable function calls.
40       */
41      public interface Funcall {}
42  
43      /**
44       * An info bound to its node.
45       * <p>Used to parse expressions for templates.
46       */
47      public static class Info extends JexlInfo {
48          JexlNode node = null;
49  
50          /**
51           * Default ctor.
52           * @param jnode the node
53           */
54          public Info(final JexlNode jnode) {
55              this(jnode, jnode.jexlInfo());
56          }
57  
58          /**
59           * Copy ctor.
60           * @param jnode the node
61           * @param info the
62           */
63          public Info(final JexlNode jnode, final JexlInfo info) {
64              this(jnode, info.getName(), info.getLine(), info.getColumn());
65          }
66  
67          /**
68           * Full detail ctor.
69           * @param jnode the node
70           * @param name the file name
71           * @param l the line
72           * @param c the column
73           */
74          private Info(final JexlNode jnode, final String name, final int l, final int c) {
75              super(name, l, c);
76              node = jnode;
77          }
78  
79          @Override
80          public JexlInfo at(final int l, final int c) {
81              return new Info(node, getName(), l, c);
82          }
83  
84          @Override
85          public JexlInfo detach() {
86              node = null;
87              return this;
88          }
89  
90          /**
91           * @return the node this info is bound to
92           */
93          public JexlNode getNode() {
94              return node;
95          }
96      }
97  
98      /**
99       */
100     private static final long serialVersionUID = 1L;
101 
102     // line + column encoded: up to 4096 columns (ie 20 bits for line + 12 bits for column)
103     private int lc = -1;
104 
105     public JexlNode(final int id) {
106         super(id);
107     }
108 
109     /**
110      * Constructs a new instance.
111      *
112      * @param p not used.
113      * @param id the node type identifier
114      * @deprecated Use {@link #JexlNode(int)}.
115      */
116     @Deprecated
117     public JexlNode(final Parser p, final int id) {
118         super(p, id);
119     }
120 
121     /**
122      * Clears any cached value of type JexlProperty{G,S}et or JexlMethod.
123      * <p>
124      * This is called when the engine detects the evaluation of a script occurs with a class loader
125      * different that the one that created it.</p>
126      */
127     public void clearCache() {
128         final Object value = jjtGetValue();
129         if (value instanceof JexlPropertyGet
130             || value instanceof JexlPropertySet
131             || value instanceof JexlMethod
132             || value instanceof Funcall
133             || value instanceof Class  ) {
134             jjtSetValue(null);
135         }
136         for (int n = 0; n < jjtGetNumChildren(); ++n) {
137             jjtGetChild(n).clearCache();
138         }
139     }
140 
141     public int getColumn() {
142         return this.lc & 0xfff;
143     }
144 
145     public int getLine() {
146         return this.lc >>> 0xc;
147     }
148 
149     /**
150      * Whether this node is a constant node.
151      * <p>Its value can not change after the first evaluation and can be cached
152      * indefinitely.</p>
153      *
154      * @return true if constant, false otherwise
155      */
156     public boolean isConstant() {
157         return isConstant(this instanceof JexlNode.Constant<?>);
158     }
159 
160     protected boolean isConstant(final boolean literal) {
161         if (literal) {
162             for (int n = 0; n < jjtGetNumChildren(); ++n) {
163                 final JexlNode child = jjtGetChild(n);
164                 if (child instanceof ASTReference || child instanceof ASTMapEntry) {
165                     final boolean is = child.isConstant(true);
166                     if (!is) {
167                         return false;
168                     }
169                 } else if (!child.isConstant()) {
170                     return false;
171                 }
172             }
173             return true;
174         }
175         return false;
176     }
177 
178     /**
179      * @return true if this node looks like a global var
180      */
181     public boolean isGlobalVar() {
182         if (this instanceof ASTVar) {
183             return false;
184         }
185         if (this instanceof ASTIdentifier) {
186             return ((ASTIdentifier) this).getSymbol() < 0;
187         }
188         final int nc = jjtGetNumChildren() - 1;
189         if (nc >= 0) {
190             final JexlNode first = jjtGetChild(0);
191             return first.isGlobalVar();
192         }
193         if (jjtGetParent() instanceof ASTReference) {
194             return true;
195         }
196         return false;
197     }
198 
199     /**
200      * Whether this node is a left value.
201      * @return true if node is assignable, false otherwise
202      */
203     public boolean isLeftValue() {
204         JexlNode walk = this;
205         do {
206             if (walk instanceof ASTIdentifier
207                 || walk instanceof ASTIdentifierAccess
208                 || walk instanceof ASTArrayAccess) {
209                 return true;
210             }
211             final int nc = walk.jjtGetNumChildren() - 1;
212             if (nc < 0) {
213                 return walk.jjtGetParent() instanceof ASTReference;
214             }
215             walk = walk.jjtGetChild(nc);
216         } while (walk != null);
217         return false;
218     }
219 
220     /**
221      * Whether this node is the left-hand side of a safe access identifier as in.
222      * For instance, in 'x?.y' , 'x' is safe.
223      * @param safe whether the engine is in safe-navigation mode
224      * @return true if safe lhs, false otherwise
225      */
226     public boolean isSafeLhs(final boolean safe) {
227         if (this instanceof ASTReference) {
228             return jjtGetChild(0).isSafeLhs(safe);
229         }
230         if (this instanceof ASTMethodNode) {
231             if (jjtGetNumChildren() > 1
232                     && jjtGetChild(0) instanceof ASTIdentifierAccess
233                     && (((ASTIdentifierAccess) jjtGetChild(0)).isSafe() || safe)) {
234                 return true;
235             }
236         }
237         final JexlNode parent = jjtGetParent();
238         if (parent == null) {
239             return false;
240         }
241         // find this node in its parent
242         final int nsiblings = parent.jjtGetNumChildren();
243         int rhs = -1;
244         for(int s = 0; s < nsiblings; ++s) {
245             final JexlNode sibling = parent.jjtGetChild(s);
246             if (sibling == this) {
247                 // the next chid offset of this nodes parent
248                 rhs = s + 1;
249                 break;
250             }
251         }
252         // seek next child in parent
253         if (rhs >= 0 && rhs < nsiblings) {
254             JexlNode rsibling = parent.jjtGetChild(rhs);
255             if (rsibling instanceof ASTMethodNode || rsibling instanceof ASTFunctionNode) {
256                 rsibling = rsibling.jjtGetChild(0);
257             }
258             if (rsibling instanceof ASTIdentifierAccess
259                 && (((ASTIdentifierAccess) rsibling).isSafe() || safe)) {
260                 return true;
261             }
262             if (rsibling instanceof ASTArrayAccess) {
263                 return safe;
264             }
265         }
266         return false;
267     }
268 
269     /**
270      * Checks whether this node is an operator that accepts a null argument
271      * even when arithmetic is in strict mode.
272      * The default cases are equals and not equals.
273      *
274      * @param arithmetic the node to test
275      * @return true if node accepts null arguments, false otherwise
276      */
277     public boolean isStrictOperator(final JexlArithmetic arithmetic) {
278         return OperatorController.INSTANCE.isStrict(arithmetic, this);
279     }
280 
281     /**
282      * Gets the associated JexlInfo instance.
283      *
284      * @return the info
285      */
286     public JexlInfo jexlInfo() {
287         JexlInfo info = null;
288         JexlNode node = this;
289         while (node != null) {
290             if (node.jjtGetValue() instanceof JexlInfo) {
291                 info = (JexlInfo) node.jjtGetValue();
292                 break;
293             }
294             node = node.jjtGetParent();
295         }
296         if (lc >= 0) {
297             final int c = lc & 0xfff;
298             final int l = lc >> 0xc;
299             // at least an info with line/column number
300             return info != null ? info.at(info.getLine() + l - 1, c) : new JexlInfo(null, l, c);
301         }
302         // weird though; no jjSetFirstToken(...) ever called?
303         return info;
304     }
305 
306     public void jjtSetFirstToken(final Token t) {
307         // 0xc = 12, 12 bits -> 4096
308         // 0xfff, 12 bits mask
309         this.lc = t.beginLine << 0xc | 0xfff & t.beginColumn;
310     }
311 
312     public void jjtSetLastToken(final Token t) {
313         // nothing
314     }
315 
316 }