2 * Copyright (C) 2001, 2002 The Mir-coders group
4 * This file is part of Mir.
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.
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.
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
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.
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
38 import mir.generator.Generator;
39 import mir.generator.GeneratorExc;
42 import org.apache.commons.beanutils.MethodUtils;
43 import org.apache.commons.beanutils.PropertyUtils;
45 public class ParameterExpander {
46 final static String NODE_SEPARATOR = ".";
47 final static char STRING_ESCAPE_CHARACTER = '\\';
50 * Fundamental method to retrieve a field of an object. Supported are
51 * maps, beans and objects with a generic get method
53 public static Object getObjectField(Object anObject, Object aField) {
54 if (anObject instanceof Map) {
55 return ((Map) anObject).get(aField);
57 else if ((aField instanceof String) && PropertyUtils.isReadable(anObject, (String) aField)) {
59 return PropertyUtils.getProperty(anObject, (String) aField);
62 throw new RuntimeException(t.getMessage());
67 return MethodUtils.invokeExactMethod(anObject, "get", aField);
70 throw new RuntimeException("Invalid reference of " + aField + " into " + anObject);
75 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
81 i = aParts.iterator();
84 String part = (String) i.next();
86 if (!(node instanceof Map)) {
87 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
90 if (location.length()>0) {
91 location=location + NODE_SEPARATOR;
93 location = location + part;
95 newNode = ((Map) node).get(part);
98 if (aMakeIfNotPresent) {
99 newNode = new HashMap();
100 ((Map) node).put(part, newNode);
103 throw new ParameterExpanderExc( "Can't expand key " + aKey + ": " + location + " does not exist");
111 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
113 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
115 node = findNode(aKey, aMap, parts, false);
120 public static String findStringForKey(Map aMap, String aKey) throws Exception {
121 Object expandedValue = findValueForKey(aMap, aKey);
123 if (!(expandedValue instanceof String))
124 throw new ParameterExpanderExc( "Value of key is not a string but a " + expandedValue.getClass().getName());
126 return (String) expandedValue;
129 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
130 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
132 String key = (String) parts.get(parts.size()-1);
133 parts.remove(parts.size()-1);
135 Object node=findNode(aKey, aMap, parts, true);
137 // todo: bean support
138 if (node instanceof Map) {
139 ((Map) node).put(key, aValue);
142 throw new ParameterExpanderExc( "Can't set key " + aKey + " : not inside a Map");
145 public static String expandExpression(Object aContext, String anExpression) throws Exception {
146 int previousPosition = 0;
148 int endOfExpressionPosition;
149 StringBuffer result = new StringBuffer();
151 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
152 result.append(anExpression.substring(previousPosition, position));
154 if (position>=anExpression.length()-1) {
155 result.append(anExpression.substring(position, anExpression.length()));
156 previousPosition=anExpression.length();
160 if (anExpression.charAt(position+1) == '{') {
161 endOfExpressionPosition=position+2;
162 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
163 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
164 char boundary = anExpression.charAt(endOfExpressionPosition);
166 endOfExpressionPosition++;
167 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
168 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
169 endOfExpressionPosition++;
170 endOfExpressionPosition++;
172 if (endOfExpressionPosition>=anExpression.length()) {
173 throw new ParameterExpanderExc("Unterminated string in '" +anExpression+"'");
176 endOfExpressionPosition++;
178 if (endOfExpressionPosition<anExpression.length()) {
179 result.append(evaluateStringExpression(aContext, anExpression.substring(position+2, endOfExpressionPosition)));
180 previousPosition=endOfExpressionPosition+1;
183 throw new ParameterExpanderExc("Missing } in " + anExpression);
188 previousPosition=position+2;
189 result.append(anExpression.charAt(position+1));
193 result.append(anExpression.substring(previousPosition, anExpression.length()));
195 return result.toString();
198 public static boolean evaluateBooleanExpression(Object aMap, String anExpression) throws Exception {
199 Parser parser = new Parser(anExpression, aMap);
201 return parser.parseBoolean();
204 public static String evaluateStringExpression(Object aMap, String anExpression) throws Exception {
205 Parser parser = new Parser(anExpression, aMap);
207 return parser.parseString();
210 public static int evaluateIntegerExpressionWithDefault(Object aMap, String anExpression, int aDefault) throws Exception {
211 if (anExpression == null || anExpression.trim().equals(""))
214 return evaluateIntegerExpression(aMap, anExpression);
217 public static int evaluateIntegerExpression(Object aMap, String anExpression) throws Exception {
218 Parser parser = new Parser(anExpression, aMap);
220 return parser.parseInteger();
223 public static Object evaluateExpression(Object aRoot, String anExpression) throws Exception {
224 Parser parser = new Parser(anExpression, aRoot);
226 return parser.parseWhole();
229 private static class Reader {
231 private int position;
233 public Reader(String aData) {
238 public Character peek() {
239 if (position<data.length()) {
240 return (new Character(data.charAt(position)));
246 public boolean hasNext() {
250 public Character getNext() {
251 Character result = peek();
259 public String getPositionString() {
260 return data.substring(0, position) + "<__>" + data.substring(position) ;
264 private static abstract class Token {
267 public static abstract class PunctuationToken extends Token { public PunctuationToken() { } }
268 private static class LeftSquareBraceToken extends PunctuationToken {}
269 private static class RightSquareBraceToken extends PunctuationToken {}
270 private static class EqualsToken extends PunctuationToken {}
271 private static class EqualsNotToken extends PunctuationToken {}
272 private static class NOTToken extends PunctuationToken {}
273 private static class LeftParenthesisToken extends PunctuationToken {}
274 private static class RightParenthesisToken extends PunctuationToken {}
275 private static class CommaToken extends PunctuationToken {}
276 private static class PeriodToken extends PunctuationToken {}
277 private static class PlusToken extends PunctuationToken {}
278 private static class TimesToken extends PunctuationToken {}
279 private static class DivideToken extends PunctuationToken {}
280 private static class MinusToken extends PunctuationToken {}
281 private static class ConcatenateToken extends PunctuationToken {}
282 private static class LessThanOrEqualsToken extends PunctuationToken {}
283 private static class GreaterThanOrEqualsToken extends PunctuationToken {}
284 private static class LessThanToken extends PunctuationToken {}
285 private static class GreaterThanToken extends PunctuationToken {}
288 private static class IdentifierToken extends Token {
291 public IdentifierToken(String aName) {
295 public String getName() {
301 private static class LiteralToken extends Token {
302 private Object value;
304 public LiteralToken(Object aValue) {
308 public Object getValue() {
313 private static class Scanner {
314 private Reader reader;
315 private Token nextToken;
316 private String positionString;
318 public Scanner(Reader aReader) {
321 positionString = reader.getPositionString();
324 public Token scanStringLiteral() {
325 StringBuffer result = new StringBuffer();
328 delimiter = reader.getNext();
330 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
331 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
333 if (reader.hasNext())
334 result.append(reader.getNext());
337 result.append(reader.getNext());
341 if (!reader.hasNext())
342 throw new RuntimeException("unterminated string");
346 return new LiteralToken(result.toString());
349 public String getPositionString() {
350 return positionString;
353 private Token scanNumber() {
354 StringBuffer result = new StringBuffer();
355 result.append(reader.getNext());
357 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
358 result.append(reader.getNext());
362 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
364 catch (NumberFormatException e) {
365 throw new RuntimeException("Invalid number: " + e.getMessage());
369 private Token scanIdentifierKeyword() {
370 StringBuffer result = new StringBuffer();
371 result.append(reader.getNext());
373 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
374 result.append(reader.getNext());
377 return new IdentifierToken(result.toString());
380 private Token scanPunctuation() {
383 c = reader.getNext();
385 switch(c.charValue()) {
386 case '[': return new LeftSquareBraceToken();
387 case ']': return new RightSquareBraceToken();
389 if (reader.hasNext() && reader.peek().charValue() == '=') {
391 return new EqualsToken();
394 throw new RuntimeException("Unknown character: '='");
398 if (reader.hasNext() && reader.peek().charValue() == '=') {
400 return new EqualsNotToken();
403 return new NOTToken();
406 case '(': return new LeftParenthesisToken ();
408 case ')': return new RightParenthesisToken ();
409 case ',': return new CommaToken ();
410 case '.': return new PeriodToken ();
412 if (reader.hasNext() && reader.peek().charValue() == '+') {
414 return new ConcatenateToken();
417 return new PlusToken ();
419 case '*': return new TimesToken ();
420 case '/': return new DivideToken ();
421 case '-': return new MinusToken ();
423 if (reader.hasNext() && reader.peek().charValue() == '=') {
425 return new LessThanOrEqualsToken();
428 return new LessThanToken();
432 if (reader.hasNext() && reader.peek().charValue() == '=') {
434 return new GreaterThanOrEqualsToken();
437 return new GreaterThanToken();
440 throw new RuntimeException("Unexpected character: "+c);
444 public void skipWhitespace() {
445 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
449 private boolean isIdentifierStart(char c) {
450 return Character.isLetter(c) || (c == '_');
453 private boolean isIdentifierRest(char c) {
454 return Character.isLetterOrDigit(c) || (c == '_');
457 private boolean isNumberStart(char c) {
458 return Character.isDigit(c);
461 private boolean isNumberRest(char c) {
462 return Character.isDigit(c);
465 public Token scanNext() {
470 if (reader.hasNext()) {
471 Character c = reader.peek();
473 switch(c.charValue()) {
476 result = scanStringLiteral();
480 if (isIdentifierStart(c.charValue())) {
481 result = scanIdentifierKeyword();
483 else if (isNumberStart(c.charValue())) {
484 result = scanNumber();
487 result = scanPunctuation();
497 public Token scan() {
498 Token result = peek();
500 positionString = reader.getPositionString();
505 public Token peek() {
506 if (nextToken==null) {
507 nextToken = scanNext();
513 public boolean hasToken() {
518 private static class Parser {
519 private Scanner scanner;
520 private Object valueMap;
522 public Parser(String anExpression, Object aValueMap) {
523 scanner = new Scanner(new Reader(anExpression));
524 valueMap = aValueMap;
527 public boolean parseBoolean() {
529 return interpretAsBoolean(parseWhole());
531 catch (Throwable t) {
532 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
536 public int parseInteger() {
538 return interpretAsInteger(parseWhole());
540 catch (Throwable t) {
541 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
545 public String parseString() {
547 return interpretAsString(parseWhole());
549 catch (Throwable t) {
550 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
554 private String getLocation() {
555 return scanner.getPositionString();
558 private Object parseWhole() {
559 Object result = parse();
561 if (scanner.hasToken()) {
562 throw new RuntimeException("Operator expected");
568 private Object parse() {
569 return parseUntil(MAX_OPERATOR_LEVEL);
572 private List parseList() {
575 List result = new ArrayList();
577 token = scanner.scan();
578 if (!(token instanceof LeftParenthesisToken)) {
579 throw new RuntimeException("( expected");
582 if (scanner.peek() instanceof RightParenthesisToken) {
588 expression = parse();
590 result.add(expression);
592 token = scanner.scan();
594 while (token instanceof CommaToken);
596 if (!(token instanceof RightParenthesisToken)) {
597 throw new RuntimeException(") or , expected");
603 private Object parseVariable() {
606 Object currentValue = valueMap;
611 token = scanner.peek();
613 if (token instanceof LeftSquareBraceToken) {
615 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
616 token = scanner.scan();
617 if (!(token instanceof RightSquareBraceToken))
618 throw new RuntimeException("] expected");
620 currentValue = getObjectField(currentValue, qualifier);
622 else if (token instanceof IdentifierToken) {
624 qualifier = ((IdentifierToken) token).getName();
626 currentValue = getObjectField(currentValue, qualifier);
628 else if (token instanceof LeftParenthesisToken) {
629 if (currentValue instanceof Generator.Function) {
630 parameters = parseList();
632 currentValue = ((Generator.Function) currentValue).perform(parameters);
634 catch (GeneratorExc t) {
635 throw new RuntimeException(t.getMessage());
639 throw new RuntimeException("not a function");
642 throw new RuntimeException("fieldname or [ expected");
644 if (scanner.peek() instanceof PeriodToken ||
645 scanner.peek() instanceof LeftSquareBraceToken ||
646 scanner.peek() instanceof LeftParenthesisToken) {
649 if (scanner.peek() instanceof PeriodToken)
660 private Object parseUntil(int aMaxOperatorLevel) {
661 Token token = scanner.peek();
664 if (token instanceof LeftParenthesisToken) {
667 token = scanner.peek();
668 if (!(token instanceof RightParenthesisToken))
669 throw new RuntimeException(") expected");
672 else if (isUnaryOperator(token)) {
674 value = parseUntil(unaryOperatorLevel(token));
675 value = expandOperatorExpression(token, value);
677 else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
678 value = parseVariable();
680 else if (token instanceof LiteralToken) {
682 value = ((LiteralToken) token).getValue();
685 throw new RuntimeException("Expression expected");
687 token = scanner.peek();
689 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
693 if (isINOperator(token)) {
694 value2 = parseList();
697 value2 = parseUntil(binaryOperatorLevel(token));
700 value = expandOperatorExpression(token, value, value2);
702 token = scanner.peek();
708 private static final int MAX_OPERATOR_LEVEL = 1000; //
709 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
710 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
711 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
712 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
714 private int unaryOperatorLevel(Token aToken) {
715 if (aToken instanceof NOTToken)
716 return LOGICAL_OPERATOR_LEVEL;
717 else if (aToken instanceof MinusToken)
718 return ADDITION_OPERATOR_LEVEL;
720 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
723 private boolean isUnaryOperator(Token aToken) {
725 ((aToken instanceof NOTToken) ||
726 (aToken instanceof MinusToken));
729 private int binaryOperatorLevel(Token aToken) {
730 if (isANDOperator(aToken) ||
731 isOROperator(aToken))
732 return LOGICAL_OPERATOR_LEVEL;
734 if ((aToken instanceof EqualsToken) ||
735 (aToken instanceof EqualsNotToken) ||
736 (aToken instanceof LessThanOrEqualsToken) ||
737 (aToken instanceof LessThanToken) ||
738 (aToken instanceof GreaterThanOrEqualsToken) ||
739 (aToken instanceof GreaterThanToken) ||
740 isINOperator(aToken))
741 return COMPARISON_OPERATOR_LEVEL;
743 if ((aToken instanceof PlusToken) ||
744 (aToken instanceof ConcatenateToken) ||
745 (aToken instanceof MinusToken))
746 return ADDITION_OPERATOR_LEVEL;
748 if ((aToken instanceof TimesToken) ||
749 (aToken instanceof DivideToken))
750 return MULTIPLICATION_OPERATOR_LEVEL;
752 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
755 private boolean isINOperator(Token aToken) {
756 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
759 private boolean isANDOperator(Token aToken) {
760 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
763 private boolean isOROperator(Token aToken) {
764 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
767 private boolean isBinaryOperator(Token aToken) {
769 (aToken instanceof EqualsToken) ||
770 (aToken instanceof EqualsNotToken) ||
771 (aToken instanceof PlusToken) ||
772 (aToken instanceof TimesToken) ||
773 (aToken instanceof DivideToken) ||
774 (aToken instanceof MinusToken) ||
775 (aToken instanceof ConcatenateToken) ||
776 (aToken instanceof LessThanOrEqualsToken) ||
777 (aToken instanceof LessThanToken) ||
778 (aToken instanceof GreaterThanOrEqualsToken) ||
779 (aToken instanceof GreaterThanToken) ||
780 isINOperator(aToken) ||
781 isOROperator(aToken) ||
782 isANDOperator(aToken);
785 private boolean interpretAsBoolean(Object aValue) {
786 if (aValue instanceof Boolean)
787 return ((Boolean) aValue).booleanValue();
789 if (aValue instanceof RewindableIterator) {
790 ((RewindableIterator) aValue).rewind();
793 if (aValue instanceof Iterator) {
794 return ((Iterator) aValue).hasNext();
797 if (aValue instanceof List) {
798 return ((List) aValue).size()>0;
801 if (aValue instanceof String) {
802 return ((String) aValue).length()>0;
808 private int interpretAsInteger(Object aValue) {
809 if (aValue instanceof Integer) {
810 return ((Integer) aValue).intValue();
814 return Integer.parseInt(aValue.toString());
816 catch (NumberFormatException t) {
817 throw new RuntimeException("Not an integer");
821 private String interpretAsString(Object aValue) {
824 if (aValue instanceof String)
825 return (String) aValue;
826 if (aValue instanceof Integer)
827 return aValue.toString();
829 throw new RuntimeException("Not a string");
832 private Object expandOperatorExpression(Token aToken, Object aValue) {
833 if (aToken instanceof NOTToken)
834 return new Boolean(!interpretAsBoolean(aValue));
835 else if (aToken instanceof MinusToken)
836 return new Integer(-interpretAsInteger(aValue));
838 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
841 private boolean areEqual(Object aValue1, Object aValue2) {
842 if (aValue1==null || aValue2==null)
843 return (aValue1==null) && (aValue2==null);
845 return aValue1.equals(aValue2);
848 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
849 if (isINOperator(aToken)) {
850 if (!(aValue2 instanceof List)) {
851 throw new RuntimeException("Internal error: List expected");
854 Iterator i = ((List) aValue2).iterator();
856 while (i.hasNext()) {
857 if (areEqual(aValue1, i.next()))
861 return Boolean.FALSE;
864 if (isANDOperator(aToken))
865 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
866 if (isOROperator(aToken))
867 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
868 if (aToken instanceof EqualsToken) {
869 return new Boolean(areEqual(aValue1, aValue2));
871 if (aToken instanceof EqualsNotToken)
872 return new Boolean(!areEqual(aValue1, aValue2));
873 if (aToken instanceof PlusToken)
874 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
875 if (aToken instanceof TimesToken)
876 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
877 if (aToken instanceof DivideToken)
878 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
879 if (aToken instanceof MinusToken)
880 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
882 if (aToken instanceof ConcatenateToken)
883 return interpretAsString(aValue1) + interpretAsString(aValue2);
885 if (aToken instanceof LessThanOrEqualsToken) {
886 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
888 if (aToken instanceof LessThanToken) {
889 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
891 if (aToken instanceof GreaterThanOrEqualsToken) {
892 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
894 if (aToken instanceof GreaterThanToken) {
895 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
898 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
902 public static class ParameterExpanderExc extends Exc {
903 public ParameterExpanderExc(String msg) {