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