1   /***
2    * ExpressionEval.java
3    *  
4    * $Author: mballesteros $ 
5    * $Date: 2003/11/28 19:18:03 $ 
6    * $Revision: 1.1 $
7    */
8   package net.sf.jec;
9   
10  import java.util.HashMap;
11  import java.util.Iterator;
12  import java.util.Map;
13  
14  /***
15   * <p>ExpressionEval is an expression evaluator.</p>
16   * <p>It works caching class+expr pairs with their associated compiled operator.
17   * Whenever someone calls ExpressionEval for evaluating a 'get' or 'set' 
18   * expression on a context 'ctx', it checks for the pair (cxt.getClass() + expr)
19   * in the operator cache. If there's nothing, the expression is compiled on the 
20   * context using a {@link ExpressionCompiler} and a new entry is created within 
21   * the cache.</p>
22   * <p>So, ExpressionEval grows over the time, filled with  pairs of class plus
23   * expression, and its associated operator trees.</p>
24   * <p><b>WARNING:</b>Be careful with using ExpressionEval as a singleton in your 
25   * application. For expressions that evaluate on fixed clases, an expression
26   * evaluated on a given context will have always the same operator tree. But
27   * that isn't true for classes with dynamic properties, as those returning
28   * <code>java.util.Map</code>. Anyway, I'm not absolutely sure about this and
29   * it would be great to have a criteria for choosing the singleton option when
30   * possible.</p>
31   *
32   * @author mballesteros
33   */
34  public class ExpressionEval {
35  
36      //--------------------------------------------------------------------------
37      // INTERNAL FIELDS
38      //--------------------------------------------------------------------------
39      /***
40       * Expression compiler. Compiles expressions into operator trees.
41       */
42      private ExpressionCompiler expressionCompiler;
43  
44      /***
45       * Context classes cache. Each entry holds a "java.lang.Class" as key, and
46       * a "java.util.HashMap" as value, which in turn holds "java.lang.String"
47       * as keys (expressions) and "Operator" objects as values (associated
48       * operators).
49       */
50      private Map ctxClassCache;
51  
52      //--------------------------------------------------------------------------
53      // REPRESENTATION INVARIANT
54      //--------------------------------------------------------------------------
55      /***
56       * Checks the rep invariant, being the following for this class:
57       * 
58       *      - <code>expressionCompiler and ctxClassCache are never 
59       *        <code>null</code>.
60       * 
61       *      - <code>ctxClassCache</code> holds <code>java.lang.Class</code> keys
62       *        and <code>java.util.HashMap</code> values.
63       * 
64       *      - Each <code>ctxClassCache</code> value holds 
65       *        <code>java.lang.String</code> as keys and <code>Operator</code> 
66       *        values.
67       * 
68       * @return boolean If the rep invariant holds.
69       */
70      private boolean checkRepInv() {
71          return this.expressionCompiler != null
72              && this.ctxClassCache != null
73              && checkCtxClassCache();
74      }
75  
76      private boolean checkCtxClassCache() {
77          Iterator it = this.ctxClassCache.entrySet().iterator();
78          while (it.hasNext()) {
79              Map.Entry entry = (Map.Entry) it.next();
80              if (!(entry.getKey() instanceof Class)
81                  || !(entry.getValue() instanceof HashMap))
82                  return false;
83              if (!checkOperatorCache((HashMap) entry.getValue()))
84                  return false;
85          }
86          return true;
87      }
88  
89      private boolean checkOperatorCache(HashMap operatorCache) {
90          Iterator it = operatorCache.entrySet().iterator();
91          while (it.hasNext()) {
92              Map.Entry entry = (Map.Entry) it.next();
93              if (!(entry.getKey() instanceof String)
94                  || !(entry.getValue() instanceof Operator))
95                  return false;
96          }
97          return true;
98      }
99  
100     //--------------------------------------------------------------------------
101     // CONSTRUCTORS
102     //--------------------------------------------------------------------------
103     /*** 
104      * Creates a new instance of ExpressionEval
105      * 
106      * @param throwExceptions True if evaluation exceptions are desirable
107      */
108     public ExpressionEval(boolean throwExceptions) {
109         this.expressionCompiler = new ExpressionCompiler(throwExceptions);
110         this.ctxClassCache = new HashMap();
111         //assert (this.checkRepInv());
112     }
113 
114     /***
115      * Returns a default expression evaluator that uses a default expression
116      * compiler (see {@link ExpressionCompiler#getDefaultInstance}).
117      * 
118      * @param throwExceptions
119      * @return ExpressionEval
120      */
121     public static ExpressionEval getDefaultInstance(boolean throwExceptions) {
122         return new ExpressionEval(
123             ExpressionCompiler.getDefaultInstance(throwExceptions));
124     }
125 
126     /***
127      * Creates a new instance of ExpressionEval
128      * 
129      * @param expComp The {@link ExpressionCompiler} used to compile 
130      * expressions into {@link Operator} objects.
131      */
132     public ExpressionEval(ExpressionCompiler expComp) {
133         this.expressionCompiler = expComp;
134         this.ctxClassCache = new HashMap();
135         //assert (this.checkRepInv());
136     }
137 
138     //--------------------------------------------------------------------------
139     // MUTADORES
140     //--------------------------------------------------------------------------
141     /***
142      * Evaluates expression <code>expr</code> over the context <code>ctx</code>
143      * 
144      * @param ctx Expression context
145      * @param expr Expression to evaluate
146      * @return Object Final evaluation result
147      * @throws ParseException Thrown when the expression is not well formed
148      * @throws EvaluationException Thrown when, although the expression is
149      * valid, the evaluation cannot be performed.
150      */
151     // This method calls a mutator, so it's a mutator too.
152     // As it doesn't modify the internal representation, there's no need to
153     // check the invariant.
154     public Object get(Object ctx, String expr)
155         throws ParseException, EvaluationException {
156         Operator op = getCachedOperator(ctx, expr);
157         return op.apply(ctx, ctx);
158     }
159 
160     /***
161      * Sets the expression <code>expr</code> over the context <code>ctx</code>
162      * to the specified value <code>value</code>, so 
163      * <code>assert(value==get(ctx, expr))</code>
164      * 
165      * @param ctx Expression context
166      * @param expr Expression to evaluate
167      * @param value New expression value
168      * @throws ParseException Thrown when the expression is not well formed
169      * @throws EvaluationException Thrown when, although the expression is
170      * valid, the evaluation cannot be performed.
171      */
172     // This method calls a mutator, so it's a mutator too.
173     // As it doesn't modify the internal representation, there's no need to
174     // check the invariant.
175     public void set(Object ctx, String expr, Object value)
176         throws ParseException, EvaluationException {
177 
178         Operator op = getCachedOperator(ctx, expr);
179         if (op instanceof InvertibleOperator) {
180             ((InvertibleOperator) op).applyInverse(ctx, ctx, value);
181         } else {
182             throw new EvaluationException(
183                 "Expression "
184                     + expr
185                     + " doesn't admit inverse on context: "
186                     + ctx);
187         }
188     }
189 
190     /***
191      * This method completely encapsulates the internal data representation.
192      * Provides the function: <code>f(ctx, expr) --> Operator</code>
193      */
194     public Operator getCachedOperator(Object ctx, String expr)
195         throws ParseException {
196 
197         //assert(this.checkRepInv()); // precondition
198 
199         Map exprCache = (Map) ctxClassCache.get(ctx.getClass());
200         if (exprCache == null) {
201             exprCache = new HashMap();
202             ctxClassCache.put(ctx.getClass(), exprCache);
203         }
204         Operator op = (Operator) exprCache.get(expr);
205         if (op == null) {
206             op = expressionCompiler.compileOperator(expr);
207             //assert(op != null);
208             exprCache.put(expr, op);
209         }
210 
211         //assert(this.checkRepInv()); // postcondition
212 
213         return op;
214     }
215 
216     /***
217      * Removes all stored operators, so the garbage collector can collect them.
218      */
219     public void clearOperatorCache() {
220 
221         //assert(this.checkRepInv()); // precondition
222 
223         this.ctxClassCache = null;
224         this.ctxClassCache = new HashMap();
225 
226         //assert(this.checkRepInv()); // postcondition
227     }
228 
229     /***
230      * Removes all stored operator for objects of the given class
231      * 
232      * @param clazz The class whose Operator objects must be cleared
233      */
234     public void clearOperatorCache(Class clazz) {
235 
236         //assert(this.checkRepInv()); // precondition
237 
238         this.ctxClassCache.remove(clazz);
239 
240         //assert(this.checkRepInv()); // postcondition
241     }
242 }
This page was automatically generated by Maven