View Javadoc
1   /*
2    * Copyright 2011 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jexl3.internal;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.apache.commons.jexl3.JexlEngine;
23  import org.apache.commons.jexl3.JexlException;
24  import org.apache.commons.jexl3.JexlFeatures;
25  import org.apache.commons.jexl3.JexlScript;
26  import org.apache.commons.jexl3.parser.ASTJexlScript;
27  import org.apache.commons.jexl3.parser.JexlNode;
28  import org.apache.commons.jexl3.parser.JexlScriptParser;
29  import org.apache.commons.jexl3.parser.Parser;
30  
31  /**
32   * Helper methods for validate sessions.
33   */
34  public class Util {
35  
36      /**
37       * Checks the equality of 2 nodes by comparing all their descendants.
38       * Descendants must have the same class and same image if non null.
39       * @param lhs the left script
40       * @param rhs the right script
41       * @return null if true, a reason otherwise
42       */
43      private static String checkEquals(JexlNode lhs, JexlNode rhs) {
44          if (lhs != rhs) {
45              final ArrayList<JexlNode> lhsl = flatten(lhs);
46              final ArrayList<JexlNode> rhsl = flatten(rhs);
47              if (lhsl.size() != rhsl.size()) {
48                  return "size: " + lhsl.size() + " != " + rhsl.size();
49              }
50              for (int n = 0; n < lhsl.size(); ++n) {
51                  lhs = lhsl.get(n);
52                  rhs = rhsl.get(n);
53                  if (lhs.getClass() != rhs.getClass()) {
54                      return "class: " + lhs.getClass() + " != " + rhs.getClass();
55                  }
56                  final String lhss = lhs.toString();
57                  final String rhss = rhs.toString();
58                  if (lhss == null && rhss != null
59                          || lhss != null && rhss == null) {
60                      return "image: " + lhss + " != " + rhss;
61                  }
62                  if (lhss != null && !lhss.equals(rhss)) {
63                      return "image: " + lhss + " != " + rhss;
64                  }
65              }
66          }
67          return null;
68      }
69  
70      /**
71       * Will force testing the debugger for each derived test class by
72       * recreating each expression from the JexlNode in the JexlEngine cache &
73       * testing them for equality with the origin.
74       * @throws Exception
75       */
76      public static void debuggerCheck(final JexlEngine ijexl) throws Exception {
77          final Engine jexl = (Engine) ijexl;
78          // without a cache, nothing to check
79          if (jexl == null || jexl.cache == null) {
80              return;
81          }
82          final Engine jdbg = new Engine();
83          final JexlScriptParser jexlp = jdbg.parser;
84          if (!(jexlp instanceof Parser)) {
85              // jexl-438 escape
86              return;
87          }
88          final Parser parser = (Parser) jexlp;
89          parser.allowRegisters(true);
90          final Debugger dbg = new Debugger();
91          // iterate over all expression in
92          for (final Map.Entry<Source, Object> entry : jexl.cache.entries()) {
93              final Object c = entry.getValue();
94              // we may have cached Jxlt expressions due to interpolation strings, skip them
95              if (!(c instanceof ASTJexlScript)) {
96                  continue;
97              }
98              final ASTJexlScript node = (ASTJexlScript) c;
99              // recreate expr string from AST
100             dbg.debug(node);
101             final String expressiondbg = dbg.toString();
102             final JexlFeatures features = entry.getKey().getFeatures();
103             // recreate expr from string
104             try {
105                 final Script exprdbg = jdbg.createScript(features, null, expressiondbg);
106                 // make arg cause become the root cause
107                 JexlNode root = exprdbg.script;
108                 while (root.jjtGetParent() != null) {
109                     root = root.jjtGetParent();
110                 }
111                 // test equality
112                 final String reason = checkEquals(root, node);
113                 if (reason != null) {
114                     throw new IllegalStateException("check equal failed: "
115                             + expressiondbg
116                             + " /**** " + reason + " **** */ "
117                             + entry.getKey());
118                 }
119             } catch (final JexlException xjexl) {
120                 throw new IllegalStateException("check parse failed: "
121                         + expressiondbg
122                         + " /*********/ "
123                         + entry.getKey(), xjexl);
124 
125             }
126         }
127     }
128 
129     /**
130      * Creates a list of all descendants of a script including itself.
131      * @param node the script to flatten
132      * @return the descendants-and-self list
133      */
134     protected static ArrayList<JexlNode> flatten(final JexlNode node) {
135         final ArrayList<JexlNode> list = new ArrayList<>();
136         flatten(list, node);
137         return list;
138     }
139 
140     /**
141      * Recursively adds all children of a script to the list of descendants.
142      * @param list   the list of descendants to add to
143      * @param node the script & descendants to add
144      */
145     private static void flatten(final List<JexlNode> list, final JexlNode node) {
146         final int nc = node.jjtGetNumChildren();
147         list.add(node);
148         for (int c = 0; c < nc; ++c) {
149             flatten(list, node.jjtGetChild(c));
150         }
151     }
152 
153     /**
154      * A helper class to help validate AST problems.
155      * @param e the script
156      * @return an indented version of the AST
157      */
158     protected static String flattenedStr(final JexlScript e) {
159         return ""; //e.getText() + "\n" + flattenedStr(((Script)e).script);
160     }
161 
162     private static String indent(JexlNode node) {
163         final StringBuilder strb = new StringBuilder();
164         while (node != null) {
165             strb.append("  ");
166             node = node.jjtGetParent();
167         }
168         return strb.toString();
169     }
170 
171     private String flattenedStr(final JexlNode node) {
172         final ArrayList<JexlNode> flattened = flatten(node);
173         final StringBuilder strb = new StringBuilder();
174         for (final JexlNode flat : flattened) {
175             strb.append(indent(flat));
176             strb.append(flat.getClass().getSimpleName());
177             final String sflat = flat.toString();
178             if (sflat != null) {
179                 strb.append(" = ");
180                 strb.append(sflat);
181             }
182             strb.append("\n");
183         }
184         return strb.toString();
185     }
186 }