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