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