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