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 }