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