more experimental rss code. Not finished yet though.
[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.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Vector;
37
38 import org.apache.commons.beanutils.*;
39
40 import multex.Exc;
41
42 import mir.generator.Generator;
43 import mir.generator.GeneratorExc;
44
45 public class ParameterExpander {
46   final static String NODE_SEPARATOR = ".";
47   final static char STRING_ESCAPE_CHARACTER = '\\';
48
49   private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
50     Iterator i;
51     String location = "";
52     Object node = aMap;
53     Object newNode;
54
55     i = aParts.iterator();
56
57     while (i.hasNext()) {
58       String part = (String) i.next();
59
60       if (!(node instanceof Map)) {
61         throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
62       }
63
64       if (location.length()>0) {
65         location=location + NODE_SEPARATOR;
66       }
67       location = location + part;
68
69       newNode = ((Map) node).get(part);
70
71       if (newNode == null)
72         if (aMakeIfNotPresent) {
73           newNode = new HashMap();
74           ((Map) node).put(part, newNode);
75         }
76         else
77           throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
78
79       node = newNode;
80     }
81
82     return node;
83   }
84
85   public static Object findValueForKey(Map aMap, String aKey) throws Exception {
86     Object node;
87     List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
88
89     node = findNode(aKey, aMap, parts, false);
90
91     return node;
92   }
93
94   public static String findStringForKey(Map aMap, String aKey) throws Exception {
95     Object expandedValue = findValueForKey(aMap, aKey);
96
97     if (!(expandedValue instanceof String))
98       throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
99
100     return (String) expandedValue;
101   }
102
103   public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
104     List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
105
106     String key = (String) parts.get(parts.size()-1);
107     parts.remove(parts.size()-1);
108
109     Object node=findNode(aKey, aMap, parts, true);
110
111     if (node instanceof Map) {
112       ((Map) node).put(key, aValue);
113     }
114     else
115       throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
116   }
117
118   public static String expandExpression(Map aMap, String anExpression) throws Exception {
119     int previousPosition = 0;
120     int position;
121     int endOfExpressionPosition;
122     StringBuffer result = new StringBuffer();
123
124     while ((position=anExpression.indexOf("$", previousPosition))>=0) {
125       result.append(anExpression.substring(previousPosition, position));
126
127       if (position>=anExpression.length()-1) {
128         result.append(anExpression.substring(position, anExpression.length()));
129         previousPosition=anExpression.length();
130       }
131       else
132       {
133         if (anExpression.charAt(position+1) == '{') {
134           endOfExpressionPosition=position+2;
135           while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
136             if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
137               char boundary = anExpression.charAt(endOfExpressionPosition);
138
139               endOfExpressionPosition++;
140               while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
141                 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
142                   endOfExpressionPosition++;
143                 endOfExpressionPosition++;
144               }
145               if (endOfExpressionPosition>=anExpression.length()) {
146                 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
147               }
148             }
149             endOfExpressionPosition++;
150           }
151           if (endOfExpressionPosition<anExpression.length()) {
152             result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
153             previousPosition=endOfExpressionPosition+1;
154           }
155           else {
156             throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
157           }
158         }
159         else
160         {
161           previousPosition=position+2;
162           result.append(anExpression.charAt(position+1));
163         }
164       }
165     }
166     result.append(anExpression.substring(previousPosition, anExpression.length()));
167
168     return result.toString();
169   }
170
171   public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
172     Parser parser = new Parser(anExpression, aMap);
173
174     return parser.parseBoolean();
175   }
176
177   public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
178     Parser parser = new Parser(anExpression, aMap);
179
180     return parser.parseString();
181   }
182
183   public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
184     if (anExpression == null || anExpression.trim().equals(""))
185       return aDefault;
186     else
187       return evaluateIntegerExpression(aMap, anExpression);
188   }
189
190   public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
191     Parser parser = new Parser(anExpression, aMap);
192
193     return parser.parseInteger();
194   }
195
196   public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
197     Parser parser = new Parser(anExpression, aMap);
198
199     return parser.parseWhole();
200   }
201
202   private static class Reader {
203     private String data;
204     private int position;
205
206     public Reader(String aData) {
207       data = aData;
208       position=0;
209     }
210
211     public Character peek() {
212       if (position<data.length()) {
213         return (new Character(data.charAt(position)));
214       }
215
216       return null;
217     }
218
219     public boolean hasNext() {
220       return peek()!=null;
221     }
222
223     public Character getNext() {
224       Character result = peek();
225
226       if (result!=null)
227         position++;
228
229       return result;
230     }
231
232     public String getPositionString() {
233       return data.substring(0, position) + "<__>" + data.substring(position) ;
234     }
235   }
236
237   private static abstract class Token {
238   }
239
240   public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
241     private static class LeftSquareBraceToken extends PunctuationToken {};
242     private static class RightSquareBraceToken extends PunctuationToken {};
243     private static class EqualsToken extends PunctuationToken {};
244     private static class EqualsNotToken extends PunctuationToken {};
245     private static class NOTToken extends PunctuationToken {};
246     private static class LeftParenthesisToken extends PunctuationToken {};
247     private static class RightParenthesisToken extends PunctuationToken {};
248     private static class CommaToken extends PunctuationToken {};
249     private static class PeriodToken extends PunctuationToken {};
250     private static class PlusToken extends PunctuationToken {};
251     private static class TimesToken extends PunctuationToken {};
252     private static class DivideToken extends PunctuationToken {};
253     private static class MinusToken extends PunctuationToken {};
254     private static class ConcatenateToken extends PunctuationToken {};
255     private static class LessThanOrEqualsToken extends PunctuationToken {};
256     private static class GreaterThanOrEqualsToken extends PunctuationToken {};
257     private static class LessThanToken extends PunctuationToken {};
258     private static class GreaterThanToken extends PunctuationToken {};
259
260
261   private static class IdentifierToken extends Token {
262     private String name;
263
264     public IdentifierToken(String aName) {
265       name = aName;
266     }
267
268     public String getName() {
269       return name;
270     }
271
272   }
273
274   private static class LiteralToken extends Token {
275     private Object value;
276
277     public LiteralToken(Object aValue) {
278       value = aValue;
279     }
280
281     public Object getValue() {
282       return value;
283     }
284   }
285
286   private static class Scanner {
287     private Reader reader;
288     private Token nextToken;
289     private String positionString;
290
291     public Scanner(Reader aReader) {
292       reader = aReader;
293       skipWhitespace();
294       positionString = reader.getPositionString();
295     }
296
297     public Token scanStringLiteral() {
298       StringBuffer result = new StringBuffer();
299       Character delimiter;
300
301       delimiter = reader.getNext();
302
303       while (reader.hasNext() && !reader.peek().equals(delimiter)) {
304         if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
305           reader.getNext();
306           if (reader.hasNext())
307             result.append(reader.getNext());
308         }
309         else {
310           result.append(reader.getNext());
311         }
312       }
313
314       if (!reader.hasNext())
315         throw new RuntimeException("unterminated string");
316       else
317         reader.getNext();
318
319       return new LiteralToken(result.toString());
320     }
321
322     public String getPositionString() {
323       return positionString;
324     }
325
326     private Token scanNumber() {
327       StringBuffer result = new StringBuffer();
328       result.append(reader.getNext());
329
330       while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
331         result.append(reader.getNext());
332       }
333
334       try {
335         return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
336       }
337       catch (NumberFormatException e) {
338         throw new RuntimeException("Invalid number: " + e.getMessage());
339       }
340     }
341
342     private Token scanIdentifierKeyword() {
343       StringBuffer result = new StringBuffer();
344       result.append(reader.getNext());
345
346       while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
347         result.append(reader.getNext());
348       }
349
350       return new IdentifierToken(result.toString());
351     }
352
353     private Token scanPunctuation() {
354       Character c;
355
356       c = reader.getNext();
357
358       switch(c.charValue()) {
359         case '[': return new LeftSquareBraceToken();
360         case ']': return new RightSquareBraceToken();
361         case '=':
362           if (reader.hasNext() && reader.peek().charValue() == '=') {
363             reader.getNext();
364             return new EqualsToken();
365           }
366           else {
367             throw new RuntimeException("Unknown character: '='");
368           }
369
370         case '!':
371           if (reader.hasNext() && reader.peek().charValue() == '=') {
372             reader.getNext();
373             return new EqualsNotToken();
374           }
375           else {
376             return new NOTToken();
377           }
378
379         case '(': return new LeftParenthesisToken ();
380
381         case ')': return new RightParenthesisToken ();
382         case ',': return new CommaToken ();
383         case '.': return new PeriodToken ();
384         case '+':
385           if (reader.hasNext() && reader.peek().charValue() == '+') {
386             reader.getNext();
387             return new ConcatenateToken();
388           }
389           else {
390             return new PlusToken ();
391           }
392         case '*': return new TimesToken ();
393         case '/': return new DivideToken ();
394         case '-': return new MinusToken ();
395         case '<':
396           if (reader.hasNext() && reader.peek().charValue() == '=') {
397             reader.getNext();
398             return new LessThanOrEqualsToken();
399           }
400           else {
401             return new LessThanToken();
402           }
403
404         case '>':
405           if (reader.hasNext() && reader.peek().charValue() == '=') {
406             reader.getNext();
407             return new GreaterThanOrEqualsToken();
408           }
409           else {
410             return new GreaterThanToken();
411           }
412         default:
413           throw new RuntimeException("Unexpected character: "+c);
414       }
415     }
416
417     public void skipWhitespace() {
418       while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
419         reader.getNext();
420     };
421
422     private boolean isIdentifierStart(char c) {
423       return Character.isLetter(c) || (c == '_');
424     }
425
426     private boolean isIdentifierRest(char c) {
427       return Character.isLetterOrDigit(c) || (c == '_');
428     }
429
430     private boolean isNumberStart(char c) {
431       return Character.isDigit(c);
432     }
433
434     private boolean isNumberRest(char c) {
435       return Character.isDigit(c);
436     }
437
438     public Token scanNext() {
439       Token result = null;
440
441       skipWhitespace();
442
443       if (reader.hasNext()) {
444         Character c = reader.peek();
445
446         switch(c.charValue()) {
447           case '\'':
448           case '"':
449             result = scanStringLiteral();
450             break;
451
452           default: {
453             if (isIdentifierStart(c.charValue())) {
454               result = scanIdentifierKeyword();
455             }
456             else if (isNumberStart(c.charValue())) {
457               result = scanNumber();
458             }
459             else
460               result = scanPunctuation();
461           }
462         }
463       }
464
465       skipWhitespace();
466
467       return result;
468     }
469
470     public Token scan() {
471       Token result = peek();
472       nextToken = null;
473       positionString = reader.getPositionString();
474
475       return result;
476     }
477
478     public Token peek() {
479       if (nextToken==null) {
480         nextToken = scanNext();
481       }
482
483       return nextToken;
484     }
485
486     public boolean hasToken() {
487       return peek()!=null;
488     }
489   }
490
491   private static class Parser {
492     private Scanner scanner;
493     private Map valueMap;
494
495     public Parser(String anExpression, Map aValueMap) {
496       scanner = new Scanner(new Reader(anExpression));
497       valueMap = aValueMap;
498     }
499
500     public boolean parseBoolean() {
501       try {
502         return interpretAsBoolean(parseWhole());
503       }
504       catch (Throwable t) {
505         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
506       }
507     }
508
509     public int parseInteger() {
510       try {
511         return interpretAsInteger(parseWhole());
512       }
513       catch (Throwable t) {
514         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
515       }
516     }
517
518     public String parseString() {
519       try {
520         return interpretAsString(parseWhole());
521       }
522       catch (Throwable t) {
523         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
524       }
525     }
526
527     private String getLocation() {
528       return scanner.getPositionString();
529     }
530
531     private Object parseWhole() {
532       Object result = parse();
533
534       if (scanner.hasToken()) {
535         throw new RuntimeException("Operator expected");
536       }
537
538       return result;
539     }
540
541     private Object parse() {
542       return parseUntil(MAX_OPERATOR_LEVEL);
543     }
544
545     private List parseList() {
546       Token token;
547       Object expression;
548       List result = new Vector();
549
550       token = scanner.scan();
551       if (!(token instanceof LeftParenthesisToken)) {
552         throw new RuntimeException("( expected");
553       }
554
555       if (scanner.peek() instanceof RightParenthesisToken) {
556         scanner.scan();
557         return result;
558       }
559
560       do {
561         expression = parse();
562
563         if (expression==null) {
564           throw new RuntimeException("expression expected");
565         }
566
567         result.add(expression);
568
569         token = scanner.scan();
570       }
571       while (token instanceof CommaToken);
572
573       if (!(token instanceof RightParenthesisToken)) {
574         throw new RuntimeException(") or , expected");
575       }
576
577       return result;
578     }
579
580     private Object evaluateObjectField(Object anObject, Object aField) {
581       if (anObject instanceof Map) {
582         return ((Map) anObject).get(aField);
583       }
584       else if ((aField instanceof String) && PropertyUtils.isReadable(anObject, (String) aField)) {
585         try {
586           return PropertyUtils.getProperty(anObject, (String) aField);
587         }
588         catch (Throwable t) {
589           throw new RuntimeException(t.getMessage());
590         }
591       }
592       else {
593         try {
594           return MethodUtils.invokeExactMethod(anObject, "get", aField);
595         }
596         catch (Throwable t) {
597           throw new RuntimeException("Invalid reference of " + aField + "into " + anObject);
598         }
599       }
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 = evaluateObjectField(currentValue, qualifier);
620         }
621         else if (token instanceof IdentifierToken) {
622           scanner.scan();
623           qualifier = ((IdentifierToken) token).getName();
624
625           currentValue = evaluateObjectField(currentValue, qualifier);
626         }
627         else if (token instanceof LeftParenthesisToken) {
628           if (currentValue instanceof Generator.GeneratorFunction) {
629             parameters = parseList();
630             try {
631               currentValue = ((Generator.GeneratorFunction) 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       return aValue!=null;
789     }
790
791     private int interpretAsInteger(Object aValue) {
792       if (aValue instanceof Integer)
793         return ((Integer) aValue).intValue();
794
795       if (aValue instanceof String) {
796         try {
797           return Integer.parseInt((String) aValue);
798         }
799         catch (Throwable t) {
800         }
801       }
802
803       throw new RuntimeException("Not an integer");
804     }
805
806     private String interpretAsString(Object aValue) {
807       if (aValue instanceof String)
808         return (String) aValue;
809       if (aValue instanceof Integer)
810         return ((Integer) aValue).toString();
811
812       throw new RuntimeException("Not a string");
813     }
814
815     private Object expandOperatorExpression(Token aToken, Object aValue) {
816       if (aToken instanceof NOTToken)
817         return new Boolean(!interpretAsBoolean(aValue));
818       else if (aToken instanceof MinusToken)
819         return new Integer(-interpretAsInteger(aValue));
820
821       throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
822     }
823
824     private boolean areEqual(Object aValue1, Object aValue2) {
825       if (aValue1==null || aValue2==null)
826         return (aValue1==null) && (aValue2==null);
827       else
828         return aValue1.equals(aValue2);
829     }
830
831     private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
832       if (isINOperator(aToken)) {
833         if (!(aValue2 instanceof List)) {
834           throw new RuntimeException("Internal error: List expected");
835         }
836
837         Iterator i = ((List) aValue2).iterator();
838
839         while (i.hasNext()) {
840           if (areEqual(aValue1, i.next()))
841             return Boolean.TRUE;
842         }
843
844         return Boolean.FALSE;
845       }
846
847       if (isANDOperator(aToken))
848         return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
849       if (isOROperator(aToken))
850         return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
851       if (aToken instanceof EqualsToken) {
852         return new Boolean(areEqual(aValue1, aValue2));
853       }
854       if (aToken instanceof EqualsNotToken)
855         return new Boolean(!areEqual(aValue1, aValue2));
856       if (aToken instanceof PlusToken)
857         return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
858       if (aToken instanceof TimesToken)
859         return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
860       if (aToken instanceof DivideToken)
861         return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
862       if (aToken instanceof MinusToken)
863         return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
864
865       if (aToken instanceof ConcatenateToken)
866         return interpretAsString(aValue1) + interpretAsString(aValue2);
867
868       if (aToken instanceof LessThanOrEqualsToken)
869         return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
870       if (aToken instanceof LessThanToken)
871         return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
872       if (aToken instanceof GreaterThanOrEqualsToken)
873         return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
874       if (aToken instanceof GreaterThanToken)
875         return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
876
877       throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
878     }
879   }
880
881   public static class ParameterExpanderExc extends Exc {
882     public ParameterExpanderExc(String msg, Object[] objects) {
883       super(msg, objects);
884     }
885   }
886 }