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