001: /**
002: * Copyright 2008 Jens Dietrich Licensed under the Apache License, Version 2.0 (the "License");
003: * you may not use this file except in compliance with the License.
004: * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
005: * Unless required by applicable law or agreed to in writing, software distributed under the
006: * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
007: * either express or implied. See the License for the specific language governing permissions
008: * and limitations under the License.
009: */package nz.org.take.nscript;
010:
011: import java.beans.BeanInfo;
012: import java.beans.IntrospectionException;
013: import java.beans.Introspector;
014: import java.beans.PropertyDescriptor;
015: import java.lang.reflect.Method;
016: import java.util.HashMap;
017: import java.util.Map;
018: import javax.el.ELException;
019: import de.odysseus.el.tree.ExpressionNode;
020: import de.odysseus.el.tree.Node;
021: import de.odysseus.el.tree.Tree;
022: import de.odysseus.el.tree.TreeBuilder;
023: import de.odysseus.el.tree.impl.Builder;
024: import de.odysseus.el.tree.impl.ast.AstBinary;
025: import de.odysseus.el.tree.impl.ast.AstDot;
026: import de.odysseus.el.tree.impl.ast.AstEval;
027: import de.odysseus.el.tree.impl.ast.AstFunction;
028: import de.odysseus.el.tree.impl.ast.AstIdentifier;
029: import de.odysseus.el.tree.impl.ast.AstNested;
030: import de.odysseus.el.tree.impl.ast.AstNode;
031: import de.odysseus.el.tree.impl.ast.AstNumber;
032: import de.odysseus.el.tree.impl.ast.AstString;
033: import de.odysseus.el.tree.impl.ast.AstUnary;
034: import de.odysseus.el.tree.impl.ast.AstBinary.Operator;
035: import nz.org.take.AggregationFunction;
036: import nz.org.take.BinaryArithmeticFunction;
037: import nz.org.take.Comparison;
038: import nz.org.take.ComplexTerm;
039: import nz.org.take.Constant;
040: import nz.org.take.Function;
041: import nz.org.take.JPredicate;
042: import nz.org.take.Predicate;
043: import nz.org.take.Prerequisite;
044: import nz.org.take.PrimitiveTypeUtils;
045: import nz.org.take.TakeException;
046: import nz.org.take.Term;
047: import nz.org.take.UnaryArithmeticFunction;
048: import nz.org.take.Variable;
049:
050: /**
051: * JUEL based parser for JSP EL expressions.
052: * @author <a href="http://www-ist.massey.ac.nz/JBDietrich/">Jens Dietrich</a>
053: */
054:
055: public class JSPELParser extends ParserSupport {
056:
057: private Map<String, Variable> variables = new HashMap<String, Variable>();
058: private Map<String, Constant> constants = new HashMap<String, Constant>();
059: private Map<String, AggregationFunction> aggregations = new HashMap<String, AggregationFunction>();
060:
061: private TreeBuilder builder = new Builder();
062:
063: public JSPELParser(Map<String, Variable> variables,
064: Map<String, Constant> constants,
065: Map<String, AggregationFunction> aggregations) {
066: super ();
067: this .variables = variables;
068: this .constants = constants;
069: this .aggregations = aggregations;
070: }
071:
072: public JSPELParser() {
073: super ();
074: }
075:
076: public Prerequisite parseCondition(String s, int line,
077: boolean isNegated) throws ScriptException {
078: s = "${" + s + "}"; // EL parser expects this
079: Tree tree = builder.build(s);
080: ExpressionNode root = tree.getRoot();
081: if (root.getCardinality() == 1) {
082: Node child = root.getChild(0);
083: // expressions like 42<person.age
084: if (child instanceof AstBinary) {
085: AstBinary binN = (AstBinary) child;
086: Operator op = binN.getOperator();
087:
088: if (op == AstBinary.AND)
089: this
090: .error(
091: line,
092: "The logical operator AND (&&) is not allowed here, use multiple conditions in rule body instead");
093: else if (op == AstBinary.OR)
094: this
095: .error(line,
096: "The logical operator OR (||) not allowed here, use multiple rules instead");
097:
098: Term t1 = this .parseTerm(binN.getChild(0), line); // left
099: Term t2 = this .parseTerm(binN.getChild(1), line); // right
100: Predicate p = getPredicate(op, t1.getType(), t2
101: .getType(), isNegated, line);
102: Prerequisite prereq = new Prerequisite();
103: prereq.setPredicate(p);
104: prereq.setTerms(new Term[] { t1, t2 });
105: return prereq;
106: }
107: // expression like person.male or person.isMale
108: else if (child instanceof AstDot) {
109: AstDot d = (AstDot) child;
110: // TODO hack: remove ". " prefix - will have to modify JUEL to access property name directly
111: String p = d.toString().substring(2);
112: AstNode c = d.getChild(0); // head
113: Term t = this .parseTerm(c, line);
114: Method m = null;
115: // verify property
116: PropertyDescriptor property = null;
117: try {
118: property = findProperty(t.getType(), p, line);
119: } catch (ScriptException x) {
120: // ignore - try to find a matching getter
121: }
122: if (property != null) {
123: if (property.getPropertyType() != Boolean.TYPE) {
124: this
125: .error(
126: line,
127: "Only boolean properties can be used as predicates, but ",
128: property.getName(), " in ", t
129: .getType().getName(),
130: " has the following type: ",
131: property.getPropertyType()
132: .getName());
133: } else {
134: m = property.getReadMethod();
135: }
136: }
137: if (m == null) {
138: // try to find a method with this name
139: // the advantage is that we can use expressions like person.isMale
140: // by only using properties we would have to use person.male
141: try {
142: Method m2 = t.getType().getMethod(p,
143: new Class[] {});
144: if (m2.getReturnType() == Boolean.TYPE
145: && java.lang.reflect.Modifier
146: .isPublic(m2.getModifiers())) {
147: m = m2;
148: }
149: } catch (Exception x) {
150: }
151: }
152:
153: if (m == null) {
154: this
155: .error(
156: line,
157: "Cannot build predicate for ",
158: p,
159: " - it represents neither a boolean property nor a getter for such a property in ",
160: t.getType(),
161: " if this property is defined by rules, use the following syntax: aProperty[anObject]");
162: } else {
163: JPredicate predicate = new JPredicate();
164: predicate.setMethod(m);
165: predicate.setNegated(isNegated);
166: Prerequisite prereq = new Prerequisite();
167: prereq.setPredicate(predicate);
168: prereq.setTerms(new Term[] { t });
169: return prereq;
170: }
171: }
172: }
173: this .error(line, "Unsupported EL expression: ", s);
174: return null;
175: }
176:
177: private Function getFunction(Operator op, Class type1, Class type2,
178: int line) throws ScriptException {
179:
180: if (op == AstBinary.ADD && isNumeric(type1) && isNumeric(type2)) {
181: return BinaryArithmeticFunction.getInstance("+", type1,
182: type2);
183: } else if (op == AstBinary.SUB && isNumeric(type1)
184: && isNumeric(type2)) {
185: return BinaryArithmeticFunction.getInstance("-", type1,
186: type2);
187: } else if (op == AstBinary.MUL && isNumeric(type1)
188: && isNumeric(type2)) {
189: return BinaryArithmeticFunction.getInstance("*", type1,
190: type2);
191: } else if (op == AstBinary.DIV && isNumeric(type1)
192: && isNumeric(type2)) {
193: return BinaryArithmeticFunction.getInstance("/", type1,
194: type2);
195: } else if (op == AstBinary.MOD && isNumeric(type1)
196: && isNumeric(type2)) {
197: return BinaryArithmeticFunction.getInstance("%", type1,
198: type2);
199: } else
200: this .error(line, "Cannot build function ", op.toString(),
201: " with parameter types ", type1.toString(), " ",
202: type2.toString());
203: return null;
204: }
205:
206: private Predicate getPredicate(Operator op, Class type1,
207: Class type2, boolean isNegated, int line)
208: throws ScriptException {
209:
210: if (op == AstBinary.EQ && isNumeric(type1) && isNumeric(type2)) {
211: try {
212: return isNegated ? new Comparison("!=")
213: : new Comparison("==");
214: } catch (TakeException e) {
215: error(line, e.getMessage());
216: }
217: } else if (op == AstBinary.EQ && !isNumeric(type1)
218: && !isNumeric(type2)) {
219: try {
220: Method m = type1.getMethod("equals",
221: new Class[] { Object.class });
222: JPredicate p = new JPredicate();
223: p.setNegated(isNegated);
224: p.setMethod(m);
225: return p;
226: } catch (Exception e) {
227: // nothing todo - always exists
228: }
229: } else if (op == AstBinary.NE && isNumeric(type1)
230: && isNumeric(type2)) {
231: try {
232: return isNegated ? new Comparison("==")
233: : new Comparison("!=");
234: } catch (TakeException e) {
235: error(line, e.getMessage());
236: }
237: } else if (op == AstBinary.NE && !isNumeric(type1)
238: && !isNumeric(type2)) {
239: try {
240: Method m = type1.getMethod("equals",
241: new Class[] { Object.class });
242: JPredicate p = new JPredicate();
243: p.setNegated(!isNegated);
244: p.setMethod(m);
245: return p;
246: } catch (Exception e) {
247: // nothing todo - always exists
248: }
249: } else if (isNumeric(type1) && isNumeric(type2)) {
250: // all other operators are numeric
251: try {
252: if (op == AstBinary.GE)
253: return isNegated ? new Comparison("<")
254: : new Comparison(">=");
255: else if (op == AstBinary.GT)
256: return isNegated ? new Comparison("<=")
257: : new Comparison(">");
258: else if (op == AstBinary.LE)
259: return isNegated ? new Comparison(">")
260: : new Comparison("<=");
261: else if (op == AstBinary.LT)
262: return isNegated ? new Comparison(">=")
263: : new Comparison("<");
264: } catch (TakeException e) {
265: error(line, e.getMessage());
266: }
267:
268: }
269: this .error(line, "Cannot build predicate for operation ", op
270: .toString());
271: return null;
272: }
273:
274: private boolean isNumeric(Class type) {
275: return PrimitiveTypeUtils.isNumericType(type);
276: }
277:
278: public Term parseTerm(String s, int line) throws ScriptException {
279: s = "${" + s + "}"; // EL parser expects this
280: try {
281: Tree tree = builder.build(s);
282: return parseTerm(tree.getRoot(), line);
283: } catch (ELException x) {
284: throw new ScriptException("Parser exception at line "
285: + line + " caused by EL parser exception", line);
286: }
287: }
288:
289: public Term parseTerm(Node n, int line) throws ScriptException {
290: boolean MINUS = false;
291: Term t = null;
292: if (n instanceof AstEval) {
293: n = n.getChild(0);
294: }
295:
296: if (n instanceof AstUnary
297: && ((AstUnary) n).getOperator() == AstUnary.NEG) {
298: MINUS = true;
299: n = n.getChild(0);
300: }
301:
302: if (n instanceof AstNumber) {
303: AstNumber _n = (AstNumber) n;
304: Number o = (Number) _n.eval(null, null);
305: Constant c = new Constant();
306: c.setObject(o);
307: t = c;
308: }
309:
310: else if (n instanceof AstString) {
311: AstString _n = (AstString) n;
312: String o = (String) _n.eval(null, null);
313: Constant c = new Constant();
314: c.setObject(o);
315: t = c;
316: }
317:
318: else if (n instanceof AstDot) {
319: AstDot d = (AstDot) n;
320: // TODO hack: remove ". " prefix - will have to modify JUEL to access property name directly
321: String p = d.toString().substring(2);
322: AstNode c = d.getChild(0); // head
323: Term t2 = this .parseTerm(c, line);
324:
325: // function with this name
326: Function f = createFunction(p, line, t2.getType());
327: if (f != null) {
328: ComplexTerm ct = new ComplexTerm();
329: ct.setFunction(f);
330: ct.setTerms(new Term[] { t2 });
331: t = ct;
332: }
333: }
334:
335: else if (n instanceof AstIdentifier) {
336: // TODO hack: remove toString and modify JUEL to access name property directly
337: String identifier = n.toString();
338: // try to find a variable
339: Term t2 = this .variables.get(identifier);
340: if (t2 != null)
341: t = t2;
342: // try to find a constant
343: else {
344: t2 = this .constants.get(identifier);
345: if (t2 != null)
346: t = t2;
347: else
348: this
349: .error(line, "identifier ", identifier,
350: " has not yet been declared - use var or ref to declare it");
351: }
352: }
353:
354: else if (n instanceof AstBinary) {
355: AstBinary binN = (AstBinary) n;
356: Operator op = binN.getOperator();
357: Term t1 = this .parseTerm(binN.getChild(0), line); // left
358: Term t2 = this .parseTerm(binN.getChild(1), line); // right
359: Function f = this .getFunction(op, t1.getType(), t2
360: .getType(), line);
361: ComplexTerm ct = new ComplexTerm();
362: ct.setFunction(f);
363: ct.setTerms(new Term[] { t1, t2 });
364: t = ct;
365: }
366:
367: else if (n instanceof AstNested) {
368: AstNested nn = (AstNested) n;
369: t = this .parseTerm(nn.getChild(0), line);
370: } else if (n instanceof AstFunction) {
371: AstFunction f = (AstFunction) n;
372: String name = f.getName();
373: int s = f.getCardinality();
374: Term[] terms = new Term[s];
375: Class[] termTypes = new Class[s];
376: for (int i = 0; i < s; i++) {
377: terms[i] = this .parseTerm(n.getChild(i), line);
378: termTypes[i] = terms[i].getType();
379: }
380: ComplexTerm ct = new ComplexTerm();
381: ct.setTerms(terms);
382:
383: Function af = createFunction(name, line, termTypes);
384: if (af != null) {
385: ct.setFunction(af);
386: t = ct;
387: }
388: }
389: // negate if necessary
390: if (MINUS) {
391: t = applyMinus(t, line);
392: }
393:
394: if (t != null)
395: return t;
396:
397: this .error(line, "cannot parse EL expression: ", n);
398: return null;
399: }
400:
401: private Term applyMinus(Term t, int line) throws ScriptException {
402: Class type = t.getType();
403: if (PrimitiveTypeUtils.isNumericType(type)) {
404: if (t instanceof Constant) {
405: if (type == Long.class) {
406: Constant c = new Constant();
407: c.setObject(new Long(-((Long) ((Constant) t)
408: .getObject()).longValue()));
409: return c;
410: } else if (type == Double.class) {
411: Constant c = new Constant();
412: c.setObject(new Double(-((Double) ((Constant) t)
413: .getObject()).doubleValue()));
414: return c;
415: } else {
416: this .error(line, "Unsupported numeric type ", type);
417: }
418: } else {
419: ComplexTerm ct = new ComplexTerm();
420: Function f = UnaryArithmeticFunction.getMINUS(t
421: .getType());
422: ct.setTerms(new Term[] { t });
423: ct.setFunction(f);
424: return ct;
425: }
426:
427: } else {
428: this
429: .error(
430: line,
431: "minus operator cannot be applied to terms of type ",
432: type);
433: }
434: return null;
435: }
436:
437: private Function createFunction(String name, int line,
438: Class... termTypes) throws ScriptException {
439: Function f = FunctionFactory.getDefaultInstance()
440: .createFunction(name, this .aggregations, termTypes);
441: if (f == null) {
442: this .error(line, "cannot interpret ", name,
443: " as a function");
444: return null;
445: } else {
446: this .debug("interpreting ", name, " in line ", line,
447: " as function ", f);
448: return f;
449: }
450: }
451:
452: private PropertyDescriptor findProperty(Class type, String p,
453: int line) throws ScriptException {
454: PropertyDescriptor[] properties = null;
455: try {
456: BeanInfo beanInfo = Introspector.getBeanInfo(type);
457: properties = beanInfo.getPropertyDescriptors();
458: for (PropertyDescriptor property : properties) {
459: if (property.getName().equals(p)) {
460: return property;
461: }
462: }
463: } catch (IntrospectionException x) {
464: }
465:
466: return null;
467: }
468:
469: public Map<String, Variable> getVariables() {
470: return variables;
471: }
472:
473: public void setVariables(Map<String, Variable> variables) {
474: this .variables = variables;
475: }
476:
477: public Map<String, Constant> getConstants() {
478: return constants;
479: }
480:
481: public void setConstants(Map<String, Constant> constants) {
482: this .constants = constants;
483: }
484:
485: public Map<String, AggregationFunction> getAggregations() {
486: return aggregations;
487: }
488:
489: public void setAggregations(
490: Map<String, AggregationFunction> aggregations) {
491: this.aggregations = aggregations;
492: }
493: }
|