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