3077611ac1490b664f26f2b0217cd0ca396e940a
[mir.git] / source / mir / util / ParameterExpander.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mir.util;
31
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37
38 import mir.generator.Generator;
39 import mir.generator.GeneratorExc;
40 import multex.Exc;
41
42 import org.apache.commons.beanutils.MethodUtils;
43 import org.apache.commons.beanutils.PropertyUtils;
44
45 /**
46  * Class to work with expressions. Will be gradually phased out and replaced
47  * with {@link mir.util.expressions.ExpressionParser}
48  */
49 public class ParameterExpander {
50   final static String NODE_SEPARATOR = ".";
51   final static char STRING_ESCAPE_CHARACTER = '\\';
52
53   /**
54    * Fundamental method to retrieve a field of an object. Supported are
55    *  maps, beans and objects with a generic get method  
56    */
57   public static Object getObjectField(Object anObject, Object aField) {
58     if (anObject instanceof Map) {
59       return ((Map) anObject).get(aField);
60     }
61     else if ((aField instanceof String) && PropertyUtils.isReadable(anObject, (String) aField)) {
62       try {
63         return PropertyUtils.getProperty(anObject, (String) aField);
64       }
65       catch (Throwable t) {
66         throw new RuntimeException(t.getMessage());
67       }
68     }
69     else {
70       try {
71         return MethodUtils.invokeExactMethod(anObject, "get", aField);
72       }
73       catch (Throwable t) {
74         throw new RuntimeException("Invalid reference of " + aField + " into " + anObject);
75       }
76     }
77   }
78
79   private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
80     Iterator i;
81     String location = "";
82     Object node = aMap;
83     Object newNode;
84
85     i = aParts.iterator();
86
87     while (i.hasNext()) {
88       String part = (String) i.next();
89
90       if (!(node instanceof Map)) {
91         throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
92       }
93
94       if (location.length()>0) {
95         location=location + NODE_SEPARATOR;
96       }
97       location = location + part;
98
99       newNode = ((Map) node).get(part);
100
101       if (newNode == null)
102         if (aMakeIfNotPresent) {
103           newNode = new HashMap();
104           ((Map) node).put(part, newNode);
105         }
106         else
107           throw new ParameterExpanderExc( "Can't expand key " + aKey + ": " + location + " does not exist");
108
109       node = newNode;
110     }
111
112     return node;
113   }
114
115   public static Object findValueForKey(Map aMap, String aKey) throws Exception {
116     Object node;
117     List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
118
119     node = findNode(aKey, aMap, parts, false);
120
121     return node;
122   }
123
124   public static String findStringForKey(Map aMap, String aKey) throws Exception {
125     Object expandedValue = findValueForKey(aMap, aKey);
126
127     if (!(expandedValue instanceof String))
128       throw new ParameterExpanderExc( "Value of key is not a string but a " + expandedValue.getClass().getName());
129
130     return (String) expandedValue;
131   }
132
133   public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
134     List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
135
136     String key = (String) parts.get(parts.size()-1);
137     parts.remove(parts.size()-1);
138
139     Object node=findNode(aKey, aMap, parts, true);
140
141     // todo: bean support
142     if (node instanceof Map) {
143       ((Map) node).put(key, aValue);
144     }
145     else
146       throw new ParameterExpanderExc( "Can't set key " + aKey + " : not inside a Map");
147   }
148
149   public static String expandExpression(Object aContext, String anExpression) throws Exception {
150     int previousPosition = 0;
151     int position;
152     int endOfExpressionPosition;
153     StringBuffer result = new StringBuffer();
154
155     while ((position=anExpression.indexOf("$", previousPosition))>=0) {
156       result.append(anExpression.substring(previousPosition, position));
157
158       if (position>=anExpression.length()-1) {
159         result.append(anExpression.substring(position, anExpression.length()));
160         previousPosition=anExpression.length();
161       }
162       else
163       {
164         if (anExpression.charAt(position+1) == '{') {
165           endOfExpressionPosition=position+2;
166           while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
167             if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
168               char boundary = anExpression.charAt(endOfExpressionPosition);
169
170               endOfExpressionPosition++;
171               while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
172                 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
173                   endOfExpressionPosition++;
174                 endOfExpressionPosition++;
175               }
176               if (endOfExpressionPosition>=anExpression.length()) {
177                 throw new ParameterExpanderExc("Unterminated string in '" +anExpression+"'");
178               }
179             }
180             endOfExpressionPosition++;
181           }
182           if (endOfExpressionPosition<anExpression.length()) {
183             result.append(evaluateStringExpression(aContext, anExpression.substring(position+2, endOfExpressionPosition)));
184             previousPosition=endOfExpressionPosition+1;
185           }
186           else {
187             throw new ParameterExpanderExc("Missing } in " + anExpression);
188           }
189         }
190         else
191         {
192           previousPosition=position+2;
193           result.append(anExpression.charAt(position+1));
194         }
195       }
196     }
197     result.append(anExpression.substring(previousPosition, anExpression.length()));
198
199     return result.toString();
200   }
201
202   public static boolean evaluateBooleanExpression(Object aMap, String anExpression) throws Exception {
203     Parser parser = new Parser(anExpression, aMap);
204
205     return parser.parseBoolean();
206   }
207
208   public static String evaluateStringExpression(Object aMap, String anExpression) throws Exception {
209     Parser parser = new Parser(anExpression, aMap);
210
211     return parser.parseString();
212   }
213
214   public static int evaluateIntegerExpressionWithDefault(Object aMap, String anExpression, int aDefault) throws Exception {
215     if (anExpression == null || anExpression.trim().equals(""))
216       return aDefault;
217     else
218       return evaluateIntegerExpression(aMap, anExpression);
219   }
220
221   public static int evaluateIntegerExpression(Object aMap, String anExpression) throws Exception {
222     Parser parser = new Parser(anExpression, aMap);
223
224     return parser.parseInteger();
225   }
226
227   public static Object evaluateExpression(Object aRoot, String anExpression) throws Exception {
228     Parser parser = new Parser(anExpression, aRoot);
229
230     return parser.parseWhole();
231   }
232
233   private static class Reader {
234     private String data;
235     private int position;
236
237     public Reader(String aData) {
238       data = aData;
239       position=0;
240     }
241
242     public Character peek() {
243       if (position<data.length()) {
244         return (new Character(data.charAt(position)));
245       }
246
247       return null;
248     }
249
250     public boolean hasNext() {
251       return peek()!=null;
252     }
253
254     public Character getNext() {
255       Character result = peek();
256
257       if (result!=null)
258         position++;
259
260       return result;
261     }
262
263     public String getPositionString() {
264       return data.substring(0, position) + "<__>" + data.substring(position) ;
265     }
266   }
267
268   private static abstract class Token {
269   }
270
271   public static abstract class PunctuationToken extends Token { public PunctuationToken() { } }
272     private static class LeftSquareBraceToken extends PunctuationToken {}
273     private static class RightSquareBraceToken extends PunctuationToken {}
274     private static class EqualsToken extends PunctuationToken {}
275     private static class EqualsNotToken extends PunctuationToken {}
276     private static class NOTToken extends PunctuationToken {}
277     private static class LeftParenthesisToken extends PunctuationToken {}
278     private static class RightParenthesisToken extends PunctuationToken {}
279     private static class CommaToken extends PunctuationToken {}
280     private static class PeriodToken extends PunctuationToken {}
281     private static class PlusToken extends PunctuationToken {}
282     private static class TimesToken extends PunctuationToken {}
283     private static class DivideToken extends PunctuationToken {}
284     private static class MinusToken extends PunctuationToken {}
285     private static class ConcatenateToken extends PunctuationToken {}
286     private static class LessThanOrEqualsToken extends PunctuationToken {}
287     private static class GreaterThanOrEqualsToken extends PunctuationToken {}
288     private static class LessThanToken extends PunctuationToken {}
289     private static class GreaterThanToken extends PunctuationToken {}
290
291
292   private static class IdentifierToken extends Token {
293     private String name;
294
295     public IdentifierToken(String aName) {
296       name = aName;
297     }
298
299     public String getName() {
300       return name;
301     }
302
303   }
304
305   private static class LiteralToken extends Token {
306     private Object value;
307
308     public LiteralToken(Object aValue) {
309       value = aValue;
310     }
311
312     public Object getValue() {
313       return value;
314     }
315   }
316
317   private static class Scanner {
318     private Reader reader;
319     private Token nextToken;
320     private String positionString;
321
322     public Scanner(Reader aReader) {
323       reader = aReader;
324       skipWhitespace();
325       positionString = reader.getPositionString();
326     }
327
328     public Token scanStringLiteral() {
329       StringBuffer result = new StringBuffer();
330       Character delimiter;
331
332       delimiter = reader.getNext();
333
334       while (reader.hasNext() && !reader.peek().equals(delimiter)) {
335         if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
336           reader.getNext();
337           if (reader.hasNext())
338             result.append(reader.getNext());
339         }
340         else {
341           result.append(reader.getNext());
342         }
343       }
344
345       if (!reader.hasNext())
346         throw new RuntimeException("unterminated string");
347       else
348         reader.getNext();
349
350       return new LiteralToken(result.toString());
351     }
352
353     public String getPositionString() {
354       return positionString;
355     }
356
357     private Token scanNumber() {
358       StringBuffer result = new StringBuffer();
359       result.append(reader.getNext());
360
361       while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
362         result.append(reader.getNext());
363       }
364
365       try {
366         return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
367       }
368       catch (NumberFormatException e) {
369         throw new RuntimeException("Invalid number: " + e.getMessage());
370       }
371     }
372
373     private Token scanIdentifierKeyword() {
374       StringBuffer result = new StringBuffer();
375       result.append(reader.getNext());
376
377       while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
378         result.append(reader.getNext());
379       }
380
381       return new IdentifierToken(result.toString());
382     }
383
384     private Token scanPunctuation() {
385       Character c;
386
387       c = reader.getNext();
388
389       switch(c.charValue()) {
390         case '[': return new LeftSquareBraceToken();
391         case ']': return new RightSquareBraceToken();
392         case '=':
393           if (reader.hasNext() && reader.peek().charValue() == '=') {
394             reader.getNext();
395             return new EqualsToken();
396           }
397           else {
398             throw new RuntimeException("Unknown character: '='");
399           }
400
401         case '!':
402           if (reader.hasNext() && reader.peek().charValue() == '=') {
403             reader.getNext();
404             return new EqualsNotToken();
405           }
406           else {
407             return new NOTToken();
408           }
409
410         case '(': return new LeftParenthesisToken ();
411
412         case ')': return new RightParenthesisToken ();
413         case ',': return new CommaToken ();
414         case '.': return new PeriodToken ();
415         case '+':
416           if (reader.hasNext() && reader.peek().charValue() == '+') {
417             reader.getNext();
418             return new ConcatenateToken();
419           }
420           else {
421             return new PlusToken ();
422           }
423         case '*': return new TimesToken ();
424         case '/': return new DivideToken ();
425         case '-': return new MinusToken ();
426         case '<':
427           if (reader.hasNext() && reader.peek().charValue() == '=') {
428             reader.getNext();
429             return new LessThanOrEqualsToken();
430           }
431           else {
432             return new LessThanToken();
433           }
434
435         case '>':
436           if (reader.hasNext() && reader.peek().charValue() == '=') {
437             reader.getNext();
438             return new GreaterThanOrEqualsToken();
439           }
440           else {
441             return new GreaterThanToken();
442           }
443         default:
444           throw new RuntimeException("Unexpected character: "+c);
445       }
446     }
447
448     public void skipWhitespace() {
449       while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
450         reader.getNext();
451     }
452
453     private boolean isIdentifierStart(char c) {
454       return Character.isLetter(c) || (c == '_');
455     }
456
457     private boolean isIdentifierRest(char c) {
458       return Character.isLetterOrDigit(c) || (c == '_');
459     }
460
461     private boolean isNumberStart(char c) {
462       return Character.isDigit(c);
463     }
464
465     private boolean isNumberRest(char c) {
466       return Character.isDigit(c);
467     }
468
469     public Token scanNext() {
470       Token result = null;
471
472       skipWhitespace();
473
474       if (reader.hasNext()) {
475         Character c = reader.peek();
476
477         switch(c.charValue()) {
478           case '\'':
479           case '"':
480             result = scanStringLiteral();
481             break;
482
483           default: {
484             if (isIdentifierStart(c.charValue())) {
485               result = scanIdentifierKeyword();
486             }
487             else if (isNumberStart(c.charValue())) {
488               result = scanNumber();
489             }
490             else
491               result = scanPunctuation();
492           }
493         }
494       }
495
496       skipWhitespace();
497
498       return result;
499     }
500
501     public Token scan() {
502       Token result = peek();
503       nextToken = null;
504       positionString = reader.getPositionString();
505
506       return result;
507     }
508
509     public Token peek() {
510       if (nextToken==null) {
511         nextToken = scanNext();
512       }
513
514       return nextToken;
515     }
516
517     public boolean hasToken() {
518       return peek()!=null;
519     }
520   }
521
522   private static class Parser {
523     private Scanner scanner;
524     private Object valueMap;
525
526     public Parser(String anExpression, Object aValueMap) {
527       scanner = new Scanner(new Reader(anExpression));
528       valueMap = aValueMap;
529     }
530
531     public boolean parseBoolean() {
532       try {
533         return interpretAsBoolean(parseWhole());
534       }
535       catch (Throwable t) {
536         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
537       }
538     }
539
540     public int parseInteger() {
541       try {
542         return interpretAsInteger(parseWhole());
543       }
544       catch (Throwable t) {
545         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
546       }
547     }
548
549     public String parseString() {
550       try {
551         return interpretAsString(parseWhole());
552       }
553       catch (Throwable t) {
554         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
555       }
556     }
557
558     private String getLocation() {
559       return scanner.getPositionString();
560     }
561
562     private Object parseWhole() {
563       Object result = parse();
564
565       if (scanner.hasToken()) {
566         throw new RuntimeException("Operator expected");
567       }
568
569       return result;
570     }
571
572     private Object parse() {
573       return parseUntil(MAX_OPERATOR_LEVEL);
574     }
575
576     private List parseList() {
577       Token token;
578       Object expression;
579       List result = new ArrayList();
580
581       token = scanner.scan();
582       if (!(token instanceof LeftParenthesisToken)) {
583         throw new RuntimeException("( expected");
584       }
585
586       if (scanner.peek() instanceof RightParenthesisToken) {
587         scanner.scan();
588         return result;
589       }
590
591       do {
592         expression = parse();
593
594         result.add(expression);
595
596         token = scanner.scan();
597       }
598       while (token instanceof CommaToken);
599
600       if (!(token instanceof RightParenthesisToken)) {
601         throw new RuntimeException(") or , expected");
602       }
603
604       return result;
605     }
606
607     private Object parseVariable() {
608       boolean done;
609       Token token;
610       Object currentValue = valueMap;
611       Object qualifier;
612       List parameters;
613
614       do {
615         token = scanner.peek();
616
617         if (token instanceof LeftSquareBraceToken) {
618           scanner.scan();
619           qualifier = parseUntil(MAX_OPERATOR_LEVEL);
620           token = scanner.scan();
621           if (!(token instanceof RightSquareBraceToken))
622             throw new RuntimeException("] expected");
623
624           currentValue = getObjectField(currentValue, qualifier);
625         }
626         else if (token instanceof IdentifierToken) {
627           scanner.scan();
628           qualifier = ((IdentifierToken) token).getName();
629
630           currentValue = getObjectField(currentValue, qualifier);
631         }
632         else if (token instanceof LeftParenthesisToken) {
633           if (currentValue instanceof Generator.Function) {
634             parameters = parseList();
635             try {
636               currentValue = ((Generator.Function) currentValue).perform(parameters);
637             }
638             catch (GeneratorExc t) {
639               throw new RuntimeException(t.getMessage());
640             }
641           }
642           else
643             throw new RuntimeException("not a function");
644         }
645         else
646           throw new RuntimeException("fieldname or [ expected");
647
648         if (scanner.peek() instanceof PeriodToken ||
649             scanner.peek() instanceof LeftSquareBraceToken ||
650             scanner.peek() instanceof LeftParenthesisToken) {
651           done = false;
652
653           if (scanner.peek() instanceof PeriodToken)
654             scanner.scan();
655         }
656         else
657           done = true;
658       } while (!done);
659
660       return currentValue;
661     }
662
663
664     private Object parseUntil(int aMaxOperatorLevel) {
665       Token token = scanner.peek();
666       Object value;
667
668       if (token instanceof LeftParenthesisToken) {
669         scanner.scan();
670         value = parse();
671         token = scanner.peek();
672         if (!(token instanceof RightParenthesisToken))
673           throw new RuntimeException(") expected");
674         scanner.scan();
675       }
676       else if (isUnaryOperator(token)) {
677         scanner.scan();
678         value = parseUntil(unaryOperatorLevel(token));
679         value = expandOperatorExpression(token, value);
680       }
681       else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
682         value = parseVariable();
683       }
684       else if (token instanceof LiteralToken) {
685         scanner.scan();
686         value = ((LiteralToken) token).getValue();
687       }
688       else
689         throw new RuntimeException("Expression expected");
690
691       token = scanner.peek();
692
693       while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
694         Object value2;
695         scanner.scan();
696
697         if (isINOperator(token)) {
698           value2 = parseList();
699         }
700         else {
701           value2 = parseUntil(binaryOperatorLevel(token));
702         }
703
704         value = expandOperatorExpression(token, value, value2);
705
706         token = scanner.peek();
707       }
708
709       return value;
710     }
711
712     private static final int MAX_OPERATOR_LEVEL = 1000;                //
713     private static final int LOGICAL_OPERATOR_LEVEL = 5;               // && || !
714     private static final int COMPARISON_OPERATOR_LEVEL = 4;            // == <= >= in < >
715     private static final int ADDITION_OPERATOR_LEVEL = 3;              // + - &
716     private static final int MULTIPLICATION_OPERATOR_LEVEL = 2;        // * /
717
718     private int unaryOperatorLevel(Token aToken) {
719       if (aToken instanceof NOTToken)
720         return LOGICAL_OPERATOR_LEVEL;
721       else if (aToken instanceof MinusToken)
722         return ADDITION_OPERATOR_LEVEL;
723
724       throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
725     }
726
727     private boolean isUnaryOperator(Token aToken) {
728       return
729           ((aToken instanceof NOTToken) ||
730            (aToken instanceof MinusToken));
731     }
732
733     private int binaryOperatorLevel(Token aToken) {
734       if (isANDOperator(aToken) ||
735           isOROperator(aToken))
736         return LOGICAL_OPERATOR_LEVEL;
737
738       if ((aToken instanceof EqualsToken) ||
739           (aToken instanceof EqualsNotToken) ||
740           (aToken instanceof LessThanOrEqualsToken) ||
741           (aToken instanceof LessThanToken) ||
742           (aToken instanceof GreaterThanOrEqualsToken) ||
743           (aToken instanceof GreaterThanToken) ||
744           isINOperator(aToken))
745         return COMPARISON_OPERATOR_LEVEL;
746
747       if ((aToken instanceof PlusToken) ||
748           (aToken instanceof ConcatenateToken) ||
749           (aToken instanceof MinusToken))
750         return ADDITION_OPERATOR_LEVEL;
751
752       if ((aToken instanceof TimesToken) ||
753           (aToken instanceof DivideToken))
754         return MULTIPLICATION_OPERATOR_LEVEL;
755
756       throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
757     }
758
759     private boolean isINOperator(Token aToken) {
760       return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
761     }
762
763     private boolean isANDOperator(Token aToken) {
764       return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
765     }
766
767     private boolean isOROperator(Token aToken) {
768       return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
769     }
770
771     private boolean isBinaryOperator(Token aToken) {
772       return
773            (aToken instanceof EqualsToken) ||
774            (aToken instanceof EqualsNotToken) ||
775            (aToken instanceof PlusToken) ||
776            (aToken instanceof TimesToken) ||
777            (aToken instanceof DivideToken) ||
778            (aToken instanceof MinusToken) ||
779            (aToken instanceof ConcatenateToken) ||
780            (aToken instanceof LessThanOrEqualsToken) ||
781            (aToken instanceof LessThanToken) ||
782            (aToken instanceof GreaterThanOrEqualsToken) ||
783            (aToken instanceof GreaterThanToken) ||
784            isINOperator(aToken) ||
785            isOROperator(aToken) ||
786            isANDOperator(aToken);
787     }
788
789     private boolean interpretAsBoolean(Object aValue) {
790       if (aValue instanceof Boolean)
791         return ((Boolean) aValue).booleanValue();
792
793       if (aValue instanceof RewindableIterator) {
794         ((RewindableIterator) aValue).rewind();
795       }
796
797       if (aValue instanceof Iterator) {
798         return ((Iterator) aValue).hasNext();
799       }
800
801       if (aValue instanceof List) {
802         return ((List) aValue).size()>0;
803       }
804
805       if (aValue instanceof String) {
806         return ((String) aValue).length()>0;
807       }
808
809       return aValue!=null;
810     }
811
812     private int interpretAsInteger(Object aValue) {
813       if (aValue instanceof Integer)
814         return ((Integer) aValue).intValue();
815
816       if (aValue instanceof String) {
817         try {
818           return Integer.parseInt((String) aValue);
819         }
820         catch (Throwable t) {
821         }
822       }
823
824       throw new RuntimeException("Not an integer");
825     }
826
827     private String interpretAsString(Object aValue) {
828       if (aValue==null)
829         return "";
830       if (aValue instanceof String)
831         return (String) aValue;
832       if (aValue instanceof Integer)
833         return aValue.toString();
834
835       throw new RuntimeException("Not a string");
836     }
837
838     private Object expandOperatorExpression(Token aToken, Object aValue) {
839       if (aToken instanceof NOTToken)
840         return new Boolean(!interpretAsBoolean(aValue));
841       else if (aToken instanceof MinusToken)
842         return new Integer(-interpretAsInteger(aValue));
843
844       throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
845     }
846
847     private boolean areEqual(Object aValue1, Object aValue2) {
848       if (aValue1==null || aValue2==null)
849         return (aValue1==null) && (aValue2==null);
850       else
851         return aValue1.equals(aValue2);
852     }
853
854     private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
855       if (isINOperator(aToken)) {
856         if (!(aValue2 instanceof List)) {
857           throw new RuntimeException("Internal error: List expected");
858         }
859
860         Iterator i = ((List) aValue2).iterator();
861
862         while (i.hasNext()) {
863           if (areEqual(aValue1, i.next()))
864             return Boolean.TRUE;
865         }
866
867         return Boolean.FALSE;
868       }
869
870       if (isANDOperator(aToken))
871         return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
872       if (isOROperator(aToken))
873         return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
874       if (aToken instanceof EqualsToken) {
875         return new Boolean(areEqual(aValue1, aValue2));
876       }
877       if (aToken instanceof EqualsNotToken)
878         return new Boolean(!areEqual(aValue1, aValue2));
879       if (aToken instanceof PlusToken)
880         return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
881       if (aToken instanceof TimesToken)
882         return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
883       if (aToken instanceof DivideToken)
884         return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
885       if (aToken instanceof MinusToken)
886         return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
887
888       if (aToken instanceof ConcatenateToken)
889         return interpretAsString(aValue1) + interpretAsString(aValue2);
890
891       if (aToken instanceof LessThanOrEqualsToken)
892         return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
893       if (aToken instanceof LessThanToken)
894         return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
895       if (aToken instanceof GreaterThanOrEqualsToken)
896         return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
897       if (aToken instanceof GreaterThanToken)
898         return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
899
900       throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
901     }
902   }
903
904   public static class ParameterExpanderExc extends Exc {
905     public ParameterExpanderExc(String msg) {
906       super(msg);
907     }
908   }
909 }