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