001: /*
002: * hgcommons 7
003: * Hammurapi Group Common Library
004: * Copyright (C) 2003 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.biz/hammurapi-biz/ef/xmenu/hammurapi-group/products/products/hgcommons/index.html
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.eval;
024:
025: import java.io.StringReader;
026: import java.lang.reflect.Array;
027: import java.lang.reflect.Method;
028: import java.util.ArrayList;
029: import java.util.Collection;
030: import java.util.Enumeration;
031: import java.util.Iterator;
032: import java.util.List;
033: import java.util.Map;
034:
035: import org.apache.log4j.Logger;
036:
037: import biz.hammurapi.eval.ExpressionLexer;
038: import biz.hammurapi.eval.ExpressionRecognizer;
039: import biz.hammurapi.eval.ExpressionTokenTypes;
040:
041: import antlr.RecognitionException;
042: import antlr.TokenStreamException;
043: import biz.hammurapi.antlr.AST;
044: import biz.hammurapi.antlr.Token;
045: import biz.hammurapi.config.BeanContext;
046: import biz.hammurapi.config.Context;
047: import biz.hammurapi.convert.CompositeConverter;
048:
049: /**
050: * @author Pavel Vlasov
051: * @revision $Revision$
052: */
053: public class EvaluatingContext implements Context {
054: private static final Logger logger = Logger
055: .getLogger(EvaluatingContext.class);
056:
057: private CompositeConverter converter;
058:
059: private Collection methods = new ArrayList();
060:
061: private Context context;
062:
063: public EvaluatingContext(Context context, Collection methods,
064: CompositeConverter converter) throws EvaluationException {
065: this .context = context;
066: this .converter = converter;
067:
068: if (methods != null) {
069: this .methods.addAll(methods);
070: }
071:
072: try {
073: this .methods.add(new MethodEntry(null,
074: EvaluatingContext.class.getMethod("forEach",
075: new Class[] { Object.class })));
076: } catch (SecurityException e) {
077: throw new EvaluationException(e);
078: } catch (NoSuchMethodException e) {
079: throw new EvaluationException(e);
080: }
081: }
082:
083: public static MultiResult forEach(Object param)
084: throws EvaluationException {
085: Collection values = new ArrayList();
086: populate(((SingleResult) param).getValue(), values);
087: return new MultiResult(null, values, null);
088: }
089:
090: public Object get(String expression) throws EvaluationException {
091: logger.debug("New evaluator for expression: " + expression);
092: ExpressionLexer lexer = new ExpressionLexer(new StringReader(
093: expression));
094: lexer.setTokenObjectClass(Token.class.getName());
095: ExpressionRecognizer parser = new ExpressionRecognizer(lexer);
096: parser.setASTNodeClass(AST.class.getName());
097: try {
098: parser.expressionList();
099: } catch (RecognitionException e) {
100: throw new EvaluationException(e);
101: } catch (TokenStreamException e) {
102: throw new EvaluationException(e);
103: }
104:
105: List nodeList = new ArrayList();
106: for (AST ast = (AST) parser.getAST().getFirstChild(); ast != null; ast = (AST) ast
107: .getNextSibling()) {
108: nodeList.add(ast);
109: }
110:
111: AST[] nodes = (AST[]) nodeList
112: .toArray(new AST[nodeList.size()]);
113:
114: Collection ret = new ArrayList();
115: for (int i = 0; i < nodes.length; i++) {
116: Result result = evaluate(null, nodes[i], context);
117: if (result instanceof SingleResult) {
118: ret.add(((SingleResult) result).getValue());
119: } else {
120: Object[] values = ((MultiResult) result).getValues();
121: for (int j = 0; j < values.length; j++) {
122: ret.add(values[j]);
123: }
124: }
125: }
126:
127: return ret.size() == 1 ? ret.iterator().next() : ret;
128: }
129:
130: /**
131: * @param o
132: * @param ast
133: * @param context
134: * @return evaluation result
135: * @throws EvaluationException
136: */
137: private Result evaluate(Result res, AST ast, Context context)
138: throws EvaluationException {
139: logAst(ast);
140: switch (ast.getType()) {
141: case ExpressionTokenTypes.MINUS:
142: return minus(res, ast, context);
143: case ExpressionTokenTypes.PLUS:
144: return plus(res, ast, context);
145: case ExpressionTokenTypes.LNOT:
146: return lnot(res, ast, context);
147: case ExpressionTokenTypes.TYPECAST:
148: return typecast(res, ast, context);
149: case ExpressionTokenTypes.METHOD_CALL:
150: return invoke(res, ast, context);
151: case ExpressionTokenTypes.ARRAY_DECLARATOR:
152: throw new EvaluationException("Handle it!");
153: case ExpressionTokenTypes.INDEX_OP:
154: return index(res, ast, context);
155: case ExpressionTokenTypes.DOT:
156: return dot(res, ast, context);
157: case ExpressionTokenTypes.IDENT:
158: Object obj = context.get(ast.getText());
159: return obj instanceof Result ? (Result) obj
160: : new SingleResult(this .converter, null, obj);
161: case ExpressionTokenTypes.LITERAL_true:
162: return new SingleResult(this .converter, boolean.class,
163: Boolean.TRUE);
164: case ExpressionTokenTypes.LITERAL_false:
165: return new SingleResult(this .converter, boolean.class,
166: Boolean.FALSE);
167: case ExpressionTokenTypes.LITERAL_null:
168: return new SingleResult(this .converter, null, null);
169: case ExpressionTokenTypes.NUM_INT:
170: return new SingleResult(this .converter, int.class,
171: new Integer(ast.getText()));
172: case ExpressionTokenTypes.CHAR_LITERAL:
173: return new SingleResult(this .converter, char.class, ast
174: .getText().substring(0, 1));
175: case ExpressionTokenTypes.STRING_LITERAL:
176: String rawText = ast.getText();
177: StringBuffer sb = new StringBuffer(rawText.substring(1,
178: rawText.length() - 1));
179: for (int i = sb.indexOf("\\n"); i != -1; i = sb
180: .indexOf("\\n")) {
181: sb.replace(i, i + 2, "\n");
182: }
183: for (int i = sb.indexOf("\\r"); i != -1; i = sb
184: .indexOf("\\r")) {
185: sb.replace(i, i + 2, "\r");
186: }
187: for (int i = sb.indexOf("\\t"); i != -1; i = sb
188: .indexOf("\\t")) {
189: sb.replace(i, i + 2, "\t");
190: }
191: return new SingleResult(this .converter, String.class, sb
192: .toString());
193: case ExpressionTokenTypes.NUM_FLOAT:
194: return new SingleResult(this .converter, float.class,
195: new Float(ast.getText()));
196: case ExpressionTokenTypes.NUM_LONG:
197: return new SingleResult(this .converter, long.class,
198: new Long(ast.getText()));
199: case ExpressionTokenTypes.NUM_DOUBLE:
200: return new SingleResult(this .converter, double.class,
201: new Double(ast.getText()));
202: case ExpressionTokenTypes.LITERAL_boolean:
203: return new SingleResult(this .converter, boolean.class, null);
204: case ExpressionTokenTypes.LITERAL_byte:
205: return new SingleResult(this .converter, byte.class, null);
206: case ExpressionTokenTypes.LITERAL_char:
207: return new SingleResult(this .converter, char.class, null);
208: case ExpressionTokenTypes.LITERAL_short:
209: return new SingleResult(this .converter, short.class, null);
210: case ExpressionTokenTypes.LITERAL_float:
211: return new SingleResult(this .converter, float.class, null);
212: case ExpressionTokenTypes.LITERAL_long:
213: return new SingleResult(this .converter, long.class, null);
214: case ExpressionTokenTypes.LITERAL_double:
215: return new SingleResult(this .converter, double.class, null);
216: default:
217: throw new EvaluationException(ast);
218: }
219: }
220:
221: /**
222: * @param ast
223: */
224: private static void logAst(AST ast) {
225: logger.debug("Evaluating: ["
226: + ExpressionRecognizer._tokenNames[ast.getType()]
227: + "] " + ast.getLine() + ":" + ast.getColumn() + " "
228: + ast.toString());
229: }
230:
231: /**
232: * @param res Current result
233: * @param ast Current node
234: * @param context Context
235: * @return evaluation result
236: * @throws EvaluationException
237: */
238: private Result dot(Result res, AST ast, Context context)
239: throws EvaluationException {
240: Result result = evaluate(res, (AST) ast.getFirstChild(),
241: context);
242: String property = ast.getFirstChild().getNextSibling()
243: .getText();
244: if (result instanceof SingleResult) {
245: Object value = ((SingleResult) result).getValue();
246: if (value == null) {
247: throw new EvaluationException(ast.getFirstChild()
248: .getText()
249: + " is null");
250: }
251:
252: Context ctx = value instanceof Context ? (Context) value
253: : new BeanContext(value) {
254: protected String translate(String name) {
255: return EvaluatingContext.this
256: .translate(name);
257: }
258: };
259: Object ret = ctx.get(property);
260: return ret instanceof Result ? (Result) ret
261: : new SingleResult(this .converter, null, ret);
262: }
263:
264: Object[] values = ((MultiResult) result).getValues();
265:
266: Collection cres = new ArrayList();
267: for (int i = 0; i < values.length; i++) {
268: Context ctx = values[i] instanceof Context ? (Context) values[i]
269: : new BeanContext(values[i]) {
270: protected String translate(String name) {
271: return EvaluatingContext.this
272: .translate(name);
273: }
274: };
275: Object ret = ctx.get(property);
276: if (ret instanceof SingleResult) {
277: cres.add(((SingleResult) ret).getValue());
278: } else if (ret instanceof MultiResult) {
279: Object[] rets = ((MultiResult) ret).getValues();
280: for (int j = 0; j < rets.length; j++) {
281: cres.add(rets[j]);
282: }
283: } else {
284: cres.add(ret);
285: }
286: }
287: return new MultiResult(null, cres, converter);
288: }
289:
290: /**
291: * @param res
292: * @param ast
293: * @param context
294: * @param converter
295: * @return
296: * @throws EvaluationException
297: */
298: private Result index(Result res, AST ast, Context context)
299: throws EvaluationException {
300: AST objectNode = (AST) ast.getFirstChild();
301: Result result = evaluate(null, objectNode, context);
302: if (result instanceof SingleResult) {
303: Object obj = ((SingleResult) result).getValue();
304: if (obj == null) {
305: throw new EvaluationException("Value "
306: + objectNode.getText() + " is null at "
307: + objectNode.getLine() + ":"
308: + objectNode.getColumn());
309: }
310:
311: AST indexNode = (AST) objectNode.getNextSibling();
312: Result idr = evaluate(null, indexNode, context);
313: if (idr instanceof SingleResult) {
314: Object idx = ((SingleResult) idr).getValue();
315: return new SingleResult(this .converter, null, index(
316: ast, obj, indexNode, idx));
317: }
318:
319: Collection values = new ArrayList();
320: Object[] idxa = ((MultiResult) idr).getValues();
321: for (int i = 0; i < idxa.length; i++) {
322: values.add(index(ast, obj, indexNode, idxa[i]));
323: }
324:
325: return new MultiResult(null, values, converter);
326: }
327:
328: Object[] objs = ((MultiResult) result).getValues();
329:
330: AST indexNode = (AST) objectNode.getNextSibling();
331: Collection values = new ArrayList();
332: Result idr = evaluate(null, indexNode, context);
333: if (idr instanceof SingleResult) {
334: Object idx = ((SingleResult) idr).getValue();
335: for (int i = 0; i < objs.length; i++) {
336: values.add(index(ast, objs[i], indexNode, idx));
337: }
338: return new MultiResult(null, values, converter);
339: }
340:
341: Object[] idxa = ((MultiResult) idr).getValues();
342: for (int j = 0; j < objs.length; j++) {
343: for (int i = 0; i < idxa.length; i++) {
344: values.add(index(ast, objs[j], indexNode, idxa[i]));
345: }
346: }
347:
348: return new MultiResult(null, values, converter);
349: }
350:
351: /**
352: * @param ast
353: * @param obj
354: * @param indexNode
355: * @param idx
356: * @throws EvaluationException
357: */
358: private Object index(AST ast, Object obj, AST indexNode, Object idx)
359: throws EvaluationException {
360: if (idx == null) {
361: throw new EvaluationException("Index "
362: + indexNode.getText() + " is null at "
363: + indexNode.getLine() + ":" + indexNode.getColumn());
364: }
365:
366: if (obj.getClass().isArray()) {
367: return Array.get(obj, ((Number) converter.convert(idx,
368: Number.class, false)).intValue());
369: }
370:
371: if (obj instanceof Collection) {
372: int index = ((Number) converter.convert(idx, Number.class,
373: false)).intValue();
374: Iterator it = ((Collection) obj).iterator();
375: for (int i = 0; it.hasNext(); i++) {
376: Object next = it.next();
377: if (i == index) {
378: return next;
379: }
380: }
381:
382: throw new EvaluationException("Index out of bounds: "
383: + index + " at " + ast.getLine() + ":"
384: + ast.getColumn());
385: }
386:
387: if (obj instanceof Iterator) {
388: int index = ((Number) converter.convert(idx, Number.class,
389: false)).intValue();
390: Iterator it = (Iterator) obj;
391: for (int i = 0; it.hasNext(); i++) {
392: Object next = it.next();
393: if (i == index) {
394: return next;
395: }
396: }
397:
398: throw new EvaluationException("Index out of bounds: "
399: + index + " at " + ast.getLine() + ":"
400: + ast.getColumn());
401: }
402:
403: if (obj instanceof Enumeration) {
404: int index = ((Number) converter.convert(idx, Number.class,
405: false)).intValue();
406: Enumeration enm = (Enumeration) obj;
407: for (int i = 0; enm.hasMoreElements(); i++) {
408: Object nextElement = enm.nextElement();
409: if (i == index) {
410: return nextElement;
411: }
412: }
413:
414: throw new EvaluationException("Index out of bounds: "
415: + index + " at " + ast.getLine() + ":"
416: + ast.getColumn());
417: }
418:
419: if (obj instanceof Context) {
420: return ((Context) obj).get(idx.toString());
421: }
422:
423: if (obj instanceof Map) {
424: return ((Map) obj).get(idx);
425: }
426:
427: throw new EvaluationException(
428: "Can't apply index operation to class "
429: + obj.getClass().getName());
430: }
431:
432: /**
433: * @param res
434: * @param ast
435: * @param context
436: * @param converter
437: * @return
438: * @throws EvaluationException
439: */
440: private Result invoke(Result res, AST ast, Context context)
441: throws EvaluationException {
442: int paramCount = ast.getFirstChild().getNextSibling()
443: .getNumberOfChildren();
444: AST nameNode = (AST) ast.getFirstChild();
445: Object object;
446: String methodName;
447: switch (nameNode.getType()) {
448: case ExpressionRecognizer.DOT:
449: methodName = nameNode.getFirstChild().getNextSibling()
450: .getText();
451: Result result = evaluate(res, (AST) nameNode
452: .getFirstChild(), context);
453: if (result instanceof MultiResult) {
454: Collection ret = new ArrayList();
455: Object[] values = ((MultiResult) result).getValues();
456: for (int i = 0; i < values.length; i++) {
457: ArrayList vCandidates = new ArrayList();
458: Method[] ma = values[i].getClass().getMethods();
459: for (int j = 0; j < ma.length; j++) {
460: vCandidates.add(new MethodEntry(values[i],
461: ma[j]));
462: }
463: Result ir = invokeInternal(res, context,
464: paramCount, nameNode, vCandidates,
465: methodName);
466: if (ir instanceof SingleResult) {
467: ret.add(((SingleResult) ir).getValue());
468: } else {
469: Object[] vv = ((MultiResult) ir).getValues();
470: for (int k = 0; k < vv.length; k++) {
471: ret.add(vv[k]);
472: }
473: }
474: }
475: return new MultiResult(null, ret, converter);
476: }
477: object = ((SingleResult) result).getValue();
478: break;
479: case ExpressionRecognizer.IDENT:
480: object = context;
481: methodName = nameNode.getText();
482: break;
483: default:
484: throw new EvaluationException(nameNode);
485: }
486:
487: ArrayList candidates = new ArrayList();
488: if (object == null) {
489: candidates.addAll(methods);
490: } else {
491: Method[] ma = object.getClass().getMethods();
492: for (int i = 0; i < ma.length; i++) {
493: candidates.add(new MethodEntry(object, ma[i]));
494: }
495: }
496:
497: return invokeInternal(res, context, paramCount, nameNode,
498: candidates, methodName);
499: }
500:
501: /**
502: * @param res
503: * @param context
504: * @param paramCount
505: * @param methods
506: * @param nameNode
507: * @param object
508: * @param methodName
509: * @return
510: * @throws EvaluationException
511: */
512: private Result invokeInternal(Result res, Context context,
513: int paramCount, AST nameNode, ArrayList methods,
514: String methodName) throws EvaluationException {
515: Iterator it = methods.iterator();
516: while (it.hasNext()) {
517: MethodEntry me = (MethodEntry) it.next();
518: if (!me.name.equals(methodName)
519: || me.method.getParameterTypes().length != paramCount) {
520: it.remove();
521: }
522: }
523:
524: if (methods.isEmpty()) {
525: throw new EvaluationException("No appropriate method '"
526: + methodName + "'");
527: }
528:
529: Result[] params = new Result[paramCount];
530: int idx = 0;
531: boolean multiResult = false;
532: for (AST paramNode = (AST) nameNode.getNextSibling()
533: .getFirstChild(); paramNode != null; paramNode = (AST) paramNode
534: .getNextSibling(), idx++) {
535: params[idx] = evaluate(res, paramNode, context);
536: if (params[idx] instanceof MultiResult) {
537: multiResult = true;
538: }
539:
540: if (params[idx].getType() != null) {
541: it = methods.iterator();
542: while (it.hasNext()) {
543: MethodEntry me = (MethodEntry) it.next();
544: if (!me.method.getParameterTypes()[idx]
545: .isAssignableFrom(params[idx].getType())) {
546: it.remove();
547: }
548: }
549: }
550: }
551:
552: it = methods.iterator();
553: Z: while (it.hasNext()) {
554: MethodEntry me = (MethodEntry) it.next();
555: Iterator jt = methods.iterator();
556: while (jt.hasNext()) {
557: switch (((MethodEntry) jt.next()).isMoreSpecific(me)) {
558: case 1:
559: it.remove();
560: break Z;
561: case -1:
562: jt.remove();
563: }
564: }
565: }
566:
567: // Finding proper method
568: if (methods.isEmpty()) {
569: throw new EvaluationException("No appropriate method '"
570: + methodName + "'");
571: }
572:
573: if (methods.size() > 1) {
574: StringBuffer msg = new StringBuffer("Ambiguous method '"
575: + methodName + "': ");
576: it = methods.iterator();
577: while (it.hasNext()) {
578: msg.append("\n\t");
579: msg.append(((MethodEntry) it.next()).method);
580: }
581:
582: throw new EvaluationException(msg.toString());
583: }
584:
585: final MethodEntry methodEntry = (MethodEntry) methods.get(0);
586:
587: if (multiResult) {
588: Collection ret = new ArrayList();
589: Collection args = new ArrayList();
590: args.add(new Object[params.length]);
591: for (int i = 0; i < params.length; i++) {
592: args = setArgs(args, i, params[i], methodEntry.method
593: .getParameterTypes()[i]);
594: }
595: return new MultiResult(methodEntry.method.getReturnType(),
596: ret, converter);
597: }
598:
599: Object[] args = new Object[params.length];
600: for (int i = 0; i < params.length; i++) {
601: args[i] = converter.convert(((SingleResult) params[i])
602: .getValue(),
603: methodEntry.method.getParameterTypes()[i], false);
604: }
605:
606: return new SingleResult(this .converter, methodEntry.method
607: .getReturnType(), methodEntry.invoke(args));
608: }
609:
610: private Collection setArgs(Collection args, int idx, Result arg,
611: Class paramType) {
612: if (arg instanceof SingleResult) {
613: Iterator it = args.iterator();
614: while (it.hasNext()) {
615: ((Object[]) it.next())[idx] = converter.convert(
616: ((SingleResult) arg).getValue(), paramType,
617: false);
618: }
619: return args;
620: }
621:
622: Collection ret = new ArrayList();
623: Object[] values = ((MultiResult) arg).getValues();
624: Iterator it = args.iterator();
625: while (it.hasNext()) {
626: Object[] objs = (Object[]) it.next();
627: for (int i = 0; i < values.length; i++) {
628: Object[] cobjs = (Object[]) objs.clone();
629: cobjs[idx] = converter.convert(values[i], paramType,
630: false);
631: ret.add(cobjs);
632: }
633: }
634: return ret;
635: }
636:
637: /**
638: * @param object
639: * @param values
640: * @throws EvaluationException
641: */
642: private static void populate(Object object, Collection values)
643: throws EvaluationException {
644: if (object.getClass().isArray()) {
645: for (int i = 0, j = Array.getLength(object); i < j; i++) {
646: values.add(Array.get(object, i));
647: }
648: } else if (object instanceof Collection) {
649: values.addAll((Collection) object);
650: } else if (object instanceof Map) {
651: values.addAll(((Map) object).entrySet());
652: } else if (object instanceof Iterator) {
653: while (((Iterator) object).hasNext()) {
654: values.add(((Iterator) object).next());
655: }
656: } else if (object instanceof Enumeration) {
657: while (((Enumeration) object).hasMoreElements()) {
658: values.add(((Enumeration) object).nextElement());
659: }
660: } else {
661: throw new EvaluationException(
662: "forEach() is not applicable for "
663: + object.getClass());
664: }
665: }
666:
667: /**
668: * @param res
669: * @param ast
670: * @param context
671: * @return
672: */
673: private Result typecast(Result res, AST ast, Context context) {
674: ast.print(ExpressionRecognizer._tokenNames, false);
675: throw new UnsupportedOperationException("Not yet implemented");
676: }
677:
678: /**
679: * @param res
680: * @param ast
681: * @param context
682: * @return
683: */
684: private Result lnot(Result res, AST ast, Context context) {
685: ast.print(ExpressionRecognizer._tokenNames, false);
686: throw new UnsupportedOperationException("Not yet implemented");
687: }
688:
689: /**
690: * @param res
691: * @param ast
692: * @param context
693: * @return
694: */
695: private Result plus(Result res, AST ast, Context context) {
696: ast.print(ExpressionRecognizer._tokenNames, false);
697: throw new UnsupportedOperationException("Not yet implemented");
698: }
699:
700: /**
701: * @param res
702: * @param ast
703: * @param context
704: * @return
705: */
706: private Result minus(Result res, AST ast, Context context) {
707: ast.print(ExpressionRecognizer._tokenNames, false);
708: throw new UnsupportedOperationException("Not yet implemented");
709: }
710:
711: private String identifier(AST ast) throws EvaluationException {
712: switch (ast.getType()) {
713: case ExpressionTokenTypes.IDENT:
714: return ast.getText();
715: case ExpressionTokenTypes.DOT:
716: return identifier((AST) ast.getFirstChild())
717: + "."
718: + identifier((AST) ast.getFirstChild()
719: .getNextSibling());
720: default:
721: throw new EvaluationException("Unexpected node type: "
722: + ExpressionRecognizer._tokenNames[ast.getType()]);
723: }
724: }
725:
726: /**
727: * Translates "indexed" property name.
728: * By default replaces '_' with ' '
729: * @param name
730: * @return
731: */
732: protected String translate(String name) {
733: return name.replace('_', ' ');
734: }
735: }
|