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