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    }