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