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