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       * Checks the equality of 2 nodes by comparing all their descendants.
37       * Descendants must have the same class and same image if non null.
38       * @param lhs the left script
39       * @param rhs the right script
40       * @return null if true, a reason otherwise
41       */
42      private static String checkEquals(JexlNode lhs, JexlNode rhs) {
43          if (lhs != rhs) {
44              final ArrayList<JexlNode> lhsl = flatten(lhs);
45              final ArrayList<JexlNode> rhsl = flatten(rhs);
46              if (lhsl.size() != rhsl.size()) {
47                  return "size: " + lhsl.size() + " != " + rhsl.size();
48              }
49              for (int n = 0; n < lhsl.size(); ++n) {
50                  lhs = lhsl.get(n);
51                  rhs = rhsl.get(n);
52                  if (lhs.getClass() != rhs.getClass()) {
53                      return "class: " + lhs.getClass() + " != " + rhs.getClass();
54                  }
55                  final String lhss = lhs.toString();
56                  final String rhss = rhs.toString();
57                  if (lhss == null && rhss != null
58                          || lhss != null && rhss == null) {
59                      return "image: " + lhss + " != " + rhss;
60                  }
61                  if (lhss != null && !lhss.equals(rhss)) {
62                      return "image: " + lhss + " != " + rhss;
63                  }
64              }
65          }
66          return null;
67      }
68  
69      /**
70       * Will force testing the debugger for each derived test class by
71       * recreating each expression from the JexlNode in the JexlEngine cache &
72       * testing them for equality with the origin.
73       * @throws Exception
74       */
75      public static void debuggerCheck(final JexlEngine ijexl) throws Exception {
76          final Engine jexl = (Engine) ijexl;
77          // without a cache, nothing to check
78          if (jexl == null || jexl.cache == null) {
79              return;
80          }
81          final Engine jdbg = new Engine();
82          final JexlScriptParser jexlp = jdbg.parser;
83          if (!(jexlp instanceof Parser)) {
84              // jexl-438 escape
85              return;
86          }
87          final Parser parser = (Parser) jexlp;
88          parser.allowRegisters(true);
89          final Debugger dbg = new Debugger();
90          // iterate over all expression in
91          for (final Map.Entry<Source, Object> entry : jexl.cache.entries()) {
92              final Object c = entry.getValue();
93              // we may have cached Jxlt expressions due to interpolation strings, skip them
94              if (!(c instanceof ASTJexlScript)) {
95                  continue;
96              }
97              final ASTJexlScript node = (ASTJexlScript) c;
98              // recreate expr string from AST
99              dbg.debug(node);
100             final String expressiondbg = dbg.toString();
101             final JexlFeatures features = entry.getKey().getFeatures();
102             // recreate expr from string
103             try {
104                 final Script exprdbg = jdbg.createScript(features, null, expressiondbg);
105                 // make arg cause become the root cause
106                 JexlNode root = exprdbg.script;
107                 while (root.jjtGetParent() != null) {
108                     root = root.jjtGetParent();
109                 }
110                 // test equality
111                 final String reason = checkEquals(root, node);
112                 if (reason != null) {
113                     throw new IllegalStateException("check equal failed: "
114                             + expressiondbg
115                             + " /**** " + reason + " **** */ "
116                             + entry.getKey());
117                 }
118             } catch (final JexlException xjexl) {
119                 throw new IllegalStateException("check parse failed: "
120                         + expressiondbg
121                         + " /*********/ "
122                         + entry.getKey(), xjexl);
123 
124             }
125         }
126     }
127 
128     /**
129      * Creates a list of all descendants of a script including itself.
130      * @param node the script to flatten
131      * @return the descendants-and-self list
132      */
133     protected static ArrayList<JexlNode> flatten(final JexlNode node) {
134         final ArrayList<JexlNode> list = new ArrayList<>();
135         flatten(list, node);
136         return list;
137     }
138 
139     /**
140      * Recursively adds all children of a script to the list of descendants.
141      * @param list   the list of descendants to add to
142      * @param node the script & descendants to add
143      */
144     private static void flatten(final List<JexlNode> list, final JexlNode node) {
145         final int nc = node.jjtGetNumChildren();
146         list.add(node);
147         for (int c = 0; c < nc; ++c) {
148             flatten(list, node.jjtGetChild(c));
149         }
150     }
151 
152     /**
153      * A helper class to help validate AST problems.
154      * @param e the script
155      * @return an indented version of the AST
156      */
157     protected static String flattenedStr(final JexlScript e) {
158         return ""; //e.getText() + "\n" + flattenedStr(((Script)e).script);
159     }
160 
161     private static String indent(JexlNode node) {
162         final StringBuilder strb = new StringBuilder();
163         while (node != null) {
164             strb.append("  ");
165             node = node.jjtGetParent();
166         }
167         return strb.toString();
168     }
169 
170     private String flattenedStr(final JexlNode node) {
171         final ArrayList<JexlNode> flattened = flatten(node);
172         final StringBuilder strb = new StringBuilder();
173         for (final JexlNode flat : flattened) {
174             strb.append(indent(flat));
175             strb.append(flat.getClass().getSimpleName());
176             final String sflat = flat.toString();
177             if (sflat != null) {
178                 strb.append(" = ");
179                 strb.append(sflat);
180             }
181             strb.append("\n");
182         }
183         return strb.toString();
184     }
185 }