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