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;
46 * Class to work with expressions. Will be gradually phased out and replaced
47 * with {@link mir.util.expressions.ExpressionParser}
49 public class ParameterExpander {
50 final static String NODE_SEPARATOR = ".";
51 final static char STRING_ESCAPE_CHARACTER = '\\';
54 * Fundamental method to retrieve a field of an object. Supported are
55 * maps, beans and objects with a generic get method
57 public static Object getObjectField(Object anObject, Object aField) {
58 if (anObject instanceof Map) {
59 return ((Map) anObject).get(aField);
61 else if ((aField instanceof String) && PropertyUtils.isReadable(anObject, (String) aField)) {
63 return PropertyUtils.getProperty(anObject, (String) aField);
66 throw new RuntimeException(t.getMessage());
71 return MethodUtils.invokeExactMethod(anObject, "get", aField);
74 throw new RuntimeException("Invalid reference of " + aField + " into " + anObject);
79 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
85 i = aParts.iterator();
88 String part = (String) i.next();
90 if (!(node instanceof Map)) {
91 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
94 if (location.length()>0) {
95 location=location + NODE_SEPARATOR;
97 location = location + part;
99 newNode = ((Map) node).get(part);
102 if (aMakeIfNotPresent) {
103 newNode = new HashMap();
104 ((Map) node).put(part, newNode);
107 throw new ParameterExpanderExc( "Can't expand key " + aKey + ": " + location + " does not exist");
115 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
117 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
119 node = findNode(aKey, aMap, parts, false);
124 public static String findStringForKey(Map aMap, String aKey) throws Exception {
125 Object expandedValue = findValueForKey(aMap, aKey);
127 if (!(expandedValue instanceof String))
128 throw new ParameterExpanderExc( "Value of key is not a string but a " + expandedValue.getClass().getName());
130 return (String) expandedValue;
133 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
134 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
136 String key = (String) parts.get(parts.size()-1);
137 parts.remove(parts.size()-1);
139 Object node=findNode(aKey, aMap, parts, true);
141 // todo: bean support
142 if (node instanceof Map) {
143 ((Map) node).put(key, aValue);
146 throw new ParameterExpanderExc( "Can't set key " + aKey + " : not inside a Map");
149 public static String expandExpression(Object aContext, String anExpression) throws Exception {
150 int previousPosition = 0;
152 int endOfExpressionPosition;
153 StringBuffer result = new StringBuffer();
155 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
156 result.append(anExpression.substring(previousPosition, position));
158 if (position>=anExpression.length()-1) {
159 result.append(anExpression.substring(position, anExpression.length()));
160 previousPosition=anExpression.length();
164 if (anExpression.charAt(position+1) == '{') {
165 endOfExpressionPosition=position+2;
166 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
167 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
168 char boundary = anExpression.charAt(endOfExpressionPosition);
170 endOfExpressionPosition++;
171 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
172 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
173 endOfExpressionPosition++;
174 endOfExpressionPosition++;
176 if (endOfExpressionPosition>=anExpression.length()) {
177 throw new ParameterExpanderExc("Unterminated string in '" +anExpression+"'");
180 endOfExpressionPosition++;
182 if (endOfExpressionPosition<anExpression.length()) {
183 result.append(evaluateStringExpression(aContext, anExpression.substring(position+2, endOfExpressionPosition)));
184 previousPosition=endOfExpressionPosition+1;
187 throw new ParameterExpanderExc("Missing } in " + anExpression);
192 previousPosition=position+2;
193 result.append(anExpression.charAt(position+1));
197 result.append(anExpression.substring(previousPosition, anExpression.length()));
199 return result.toString();
202 public static boolean evaluateBooleanExpression(Object aMap, String anExpression) throws Exception {
203 Parser parser = new Parser(anExpression, aMap);
205 return parser.parseBoolean();
208 public static String evaluateStringExpression(Object aMap, String anExpression) throws Exception {
209 Parser parser = new Parser(anExpression, aMap);
211 return parser.parseString();
214 public static int evaluateIntegerExpressionWithDefault(Object aMap, String anExpression, int aDefault) throws Exception {
215 if (anExpression == null || anExpression.trim().equals(""))
218 return evaluateIntegerExpression(aMap, anExpression);
221 public static int evaluateIntegerExpression(Object aMap, String anExpression) throws Exception {
222 Parser parser = new Parser(anExpression, aMap);
224 return parser.parseInteger();
227 public static Object evaluateExpression(Object aRoot, String anExpression) throws Exception {
228 Parser parser = new Parser(anExpression, aRoot);
230 return parser.parseWhole();
233 private static class Reader {
235 private int position;
237 public Reader(String aData) {
242 public Character peek() {
243 if (position<data.length()) {
244 return (new Character(data.charAt(position)));
250 public boolean hasNext() {
254 public Character getNext() {
255 Character result = peek();
263 public String getPositionString() {
264 return data.substring(0, position) + "<__>" + data.substring(position) ;
268 private static abstract class Token {
271 public static abstract class PunctuationToken extends Token { public PunctuationToken() { } }
272 private static class LeftSquareBraceToken extends PunctuationToken {}
273 private static class RightSquareBraceToken extends PunctuationToken {}
274 private static class EqualsToken extends PunctuationToken {}
275 private static class EqualsNotToken extends PunctuationToken {}
276 private static class NOTToken extends PunctuationToken {}
277 private static class LeftParenthesisToken extends PunctuationToken {}
278 private static class RightParenthesisToken extends PunctuationToken {}
279 private static class CommaToken extends PunctuationToken {}
280 private static class PeriodToken extends PunctuationToken {}
281 private static class PlusToken extends PunctuationToken {}
282 private static class TimesToken extends PunctuationToken {}
283 private static class DivideToken extends PunctuationToken {}
284 private static class MinusToken extends PunctuationToken {}
285 private static class ConcatenateToken extends PunctuationToken {}
286 private static class LessThanOrEqualsToken extends PunctuationToken {}
287 private static class GreaterThanOrEqualsToken extends PunctuationToken {}
288 private static class LessThanToken extends PunctuationToken {}
289 private static class GreaterThanToken extends PunctuationToken {}
292 private static class IdentifierToken extends Token {
295 public IdentifierToken(String aName) {
299 public String getName() {
305 private static class LiteralToken extends Token {
306 private Object value;
308 public LiteralToken(Object aValue) {
312 public Object getValue() {
317 private static class Scanner {
318 private Reader reader;
319 private Token nextToken;
320 private String positionString;
322 public Scanner(Reader aReader) {
325 positionString = reader.getPositionString();
328 public Token scanStringLiteral() {
329 StringBuffer result = new StringBuffer();
332 delimiter = reader.getNext();
334 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
335 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
337 if (reader.hasNext())
338 result.append(reader.getNext());
341 result.append(reader.getNext());
345 if (!reader.hasNext())
346 throw new RuntimeException("unterminated string");
350 return new LiteralToken(result.toString());
353 public String getPositionString() {
354 return positionString;
357 private Token scanNumber() {
358 StringBuffer result = new StringBuffer();
359 result.append(reader.getNext());
361 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
362 result.append(reader.getNext());
366 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
368 catch (NumberFormatException e) {
369 throw new RuntimeException("Invalid number: " + e.getMessage());
373 private Token scanIdentifierKeyword() {
374 StringBuffer result = new StringBuffer();
375 result.append(reader.getNext());
377 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
378 result.append(reader.getNext());
381 return new IdentifierToken(result.toString());
384 private Token scanPunctuation() {
387 c = reader.getNext();
389 switch(c.charValue()) {
390 case '[': return new LeftSquareBraceToken();
391 case ']': return new RightSquareBraceToken();
393 if (reader.hasNext() && reader.peek().charValue() == '=') {
395 return new EqualsToken();
398 throw new RuntimeException("Unknown character: '='");
402 if (reader.hasNext() && reader.peek().charValue() == '=') {
404 return new EqualsNotToken();
407 return new NOTToken();
410 case '(': return new LeftParenthesisToken ();
412 case ')': return new RightParenthesisToken ();
413 case ',': return new CommaToken ();
414 case '.': return new PeriodToken ();
416 if (reader.hasNext() && reader.peek().charValue() == '+') {
418 return new ConcatenateToken();
421 return new PlusToken ();
423 case '*': return new TimesToken ();
424 case '/': return new DivideToken ();
425 case '-': return new MinusToken ();
427 if (reader.hasNext() && reader.peek().charValue() == '=') {
429 return new LessThanOrEqualsToken();
432 return new LessThanToken();
436 if (reader.hasNext() && reader.peek().charValue() == '=') {
438 return new GreaterThanOrEqualsToken();
441 return new GreaterThanToken();
444 throw new RuntimeException("Unexpected character: "+c);
448 public void skipWhitespace() {
449 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
453 private boolean isIdentifierStart(char c) {
454 return Character.isLetter(c) || (c == '_');
457 private boolean isIdentifierRest(char c) {
458 return Character.isLetterOrDigit(c) || (c == '_');
461 private boolean isNumberStart(char c) {
462 return Character.isDigit(c);
465 private boolean isNumberRest(char c) {
466 return Character.isDigit(c);
469 public Token scanNext() {
474 if (reader.hasNext()) {
475 Character c = reader.peek();
477 switch(c.charValue()) {
480 result = scanStringLiteral();
484 if (isIdentifierStart(c.charValue())) {
485 result = scanIdentifierKeyword();
487 else if (isNumberStart(c.charValue())) {
488 result = scanNumber();
491 result = scanPunctuation();
501 public Token scan() {
502 Token result = peek();
504 positionString = reader.getPositionString();
509 public Token peek() {
510 if (nextToken==null) {
511 nextToken = scanNext();
517 public boolean hasToken() {
522 private static class Parser {
523 private Scanner scanner;
524 private Object valueMap;
526 public Parser(String anExpression, Object aValueMap) {
527 scanner = new Scanner(new Reader(anExpression));
528 valueMap = aValueMap;
531 public boolean parseBoolean() {
533 return interpretAsBoolean(parseWhole());
535 catch (Throwable t) {
536 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
540 public int parseInteger() {
542 return interpretAsInteger(parseWhole());
544 catch (Throwable t) {
545 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
549 public String parseString() {
551 return interpretAsString(parseWhole());
553 catch (Throwable t) {
554 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
558 private String getLocation() {
559 return scanner.getPositionString();
562 private Object parseWhole() {
563 Object result = parse();
565 if (scanner.hasToken()) {
566 throw new RuntimeException("Operator expected");
572 private Object parse() {
573 return parseUntil(MAX_OPERATOR_LEVEL);
576 private List parseList() {
579 List result = new ArrayList();
581 token = scanner.scan();
582 if (!(token instanceof LeftParenthesisToken)) {
583 throw new RuntimeException("( expected");
586 if (scanner.peek() instanceof RightParenthesisToken) {
592 expression = parse();
594 result.add(expression);
596 token = scanner.scan();
598 while (token instanceof CommaToken);
600 if (!(token instanceof RightParenthesisToken)) {
601 throw new RuntimeException(") or , expected");
607 private Object parseVariable() {
610 Object currentValue = valueMap;
615 token = scanner.peek();
617 if (token instanceof LeftSquareBraceToken) {
619 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
620 token = scanner.scan();
621 if (!(token instanceof RightSquareBraceToken))
622 throw new RuntimeException("] expected");
624 currentValue = getObjectField(currentValue, qualifier);
626 else if (token instanceof IdentifierToken) {
628 qualifier = ((IdentifierToken) token).getName();
630 currentValue = getObjectField(currentValue, qualifier);
632 else if (token instanceof LeftParenthesisToken) {
633 if (currentValue instanceof Generator.Function) {
634 parameters = parseList();
636 currentValue = ((Generator.Function) currentValue).perform(parameters);
638 catch (GeneratorExc t) {
639 throw new RuntimeException(t.getMessage());
643 throw new RuntimeException("not a function");
646 throw new RuntimeException("fieldname or [ expected");
648 if (scanner.peek() instanceof PeriodToken ||
649 scanner.peek() instanceof LeftSquareBraceToken ||
650 scanner.peek() instanceof LeftParenthesisToken) {
653 if (scanner.peek() instanceof PeriodToken)
664 private Object parseUntil(int aMaxOperatorLevel) {
665 Token token = scanner.peek();
668 if (token instanceof LeftParenthesisToken) {
671 token = scanner.peek();
672 if (!(token instanceof RightParenthesisToken))
673 throw new RuntimeException(") expected");
676 else if (isUnaryOperator(token)) {
678 value = parseUntil(unaryOperatorLevel(token));
679 value = expandOperatorExpression(token, value);
681 else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
682 value = parseVariable();
684 else if (token instanceof LiteralToken) {
686 value = ((LiteralToken) token).getValue();
689 throw new RuntimeException("Expression expected");
691 token = scanner.peek();
693 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
697 if (isINOperator(token)) {
698 value2 = parseList();
701 value2 = parseUntil(binaryOperatorLevel(token));
704 value = expandOperatorExpression(token, value, value2);
706 token = scanner.peek();
712 private static final int MAX_OPERATOR_LEVEL = 1000; //
713 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
714 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
715 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
716 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
718 private int unaryOperatorLevel(Token aToken) {
719 if (aToken instanceof NOTToken)
720 return LOGICAL_OPERATOR_LEVEL;
721 else if (aToken instanceof MinusToken)
722 return ADDITION_OPERATOR_LEVEL;
724 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
727 private boolean isUnaryOperator(Token aToken) {
729 ((aToken instanceof NOTToken) ||
730 (aToken instanceof MinusToken));
733 private int binaryOperatorLevel(Token aToken) {
734 if (isANDOperator(aToken) ||
735 isOROperator(aToken))
736 return LOGICAL_OPERATOR_LEVEL;
738 if ((aToken instanceof EqualsToken) ||
739 (aToken instanceof EqualsNotToken) ||
740 (aToken instanceof LessThanOrEqualsToken) ||
741 (aToken instanceof LessThanToken) ||
742 (aToken instanceof GreaterThanOrEqualsToken) ||
743 (aToken instanceof GreaterThanToken) ||
744 isINOperator(aToken))
745 return COMPARISON_OPERATOR_LEVEL;
747 if ((aToken instanceof PlusToken) ||
748 (aToken instanceof ConcatenateToken) ||
749 (aToken instanceof MinusToken))
750 return ADDITION_OPERATOR_LEVEL;
752 if ((aToken instanceof TimesToken) ||
753 (aToken instanceof DivideToken))
754 return MULTIPLICATION_OPERATOR_LEVEL;
756 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
759 private boolean isINOperator(Token aToken) {
760 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
763 private boolean isANDOperator(Token aToken) {
764 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
767 private boolean isOROperator(Token aToken) {
768 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
771 private boolean isBinaryOperator(Token aToken) {
773 (aToken instanceof EqualsToken) ||
774 (aToken instanceof EqualsNotToken) ||
775 (aToken instanceof PlusToken) ||
776 (aToken instanceof TimesToken) ||
777 (aToken instanceof DivideToken) ||
778 (aToken instanceof MinusToken) ||
779 (aToken instanceof ConcatenateToken) ||
780 (aToken instanceof LessThanOrEqualsToken) ||
781 (aToken instanceof LessThanToken) ||
782 (aToken instanceof GreaterThanOrEqualsToken) ||
783 (aToken instanceof GreaterThanToken) ||
784 isINOperator(aToken) ||
785 isOROperator(aToken) ||
786 isANDOperator(aToken);
789 private boolean interpretAsBoolean(Object aValue) {
790 if (aValue instanceof Boolean)
791 return ((Boolean) aValue).booleanValue();
793 if (aValue instanceof RewindableIterator) {
794 ((RewindableIterator) aValue).rewind();
797 if (aValue instanceof Iterator) {
798 return ((Iterator) aValue).hasNext();
801 if (aValue instanceof List) {
802 return ((List) aValue).size()>0;
805 if (aValue instanceof String) {
806 return ((String) aValue).length()>0;
812 private int interpretAsInteger(Object aValue) {
813 if (aValue instanceof Integer)
814 return ((Integer) aValue).intValue();
816 if (aValue instanceof String) {
818 return Integer.parseInt((String) aValue);
820 catch (Throwable t) {
824 throw new RuntimeException("Not an integer");
827 private String interpretAsString(Object aValue) {
830 if (aValue instanceof String)
831 return (String) aValue;
832 if (aValue instanceof Integer)
833 return aValue.toString();
835 throw new RuntimeException("Not a string");
838 private Object expandOperatorExpression(Token aToken, Object aValue) {
839 if (aToken instanceof NOTToken)
840 return new Boolean(!interpretAsBoolean(aValue));
841 else if (aToken instanceof MinusToken)
842 return new Integer(-interpretAsInteger(aValue));
844 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
847 private boolean areEqual(Object aValue1, Object aValue2) {
848 if (aValue1==null || aValue2==null)
849 return (aValue1==null) && (aValue2==null);
851 return aValue1.equals(aValue2);
854 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
855 if (isINOperator(aToken)) {
856 if (!(aValue2 instanceof List)) {
857 throw new RuntimeException("Internal error: List expected");
860 Iterator i = ((List) aValue2).iterator();
862 while (i.hasNext()) {
863 if (areEqual(aValue1, i.next()))
867 return Boolean.FALSE;
870 if (isANDOperator(aToken))
871 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
872 if (isOROperator(aToken))
873 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
874 if (aToken instanceof EqualsToken) {
875 return new Boolean(areEqual(aValue1, aValue2));
877 if (aToken instanceof EqualsNotToken)
878 return new Boolean(!areEqual(aValue1, aValue2));
879 if (aToken instanceof PlusToken)
880 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
881 if (aToken instanceof TimesToken)
882 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
883 if (aToken instanceof DivideToken)
884 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
885 if (aToken instanceof MinusToken)
886 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
888 if (aToken instanceof ConcatenateToken)
889 return interpretAsString(aValue1) + interpretAsString(aValue2);
891 if (aToken instanceof LessThanOrEqualsToken)
892 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
893 if (aToken instanceof LessThanToken)
894 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
895 if (aToken instanceof GreaterThanOrEqualsToken)
896 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
897 if (aToken instanceof GreaterThanToken)
898 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
900 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
904 public static class ParameterExpanderExc extends Exc {
905 public ParameterExpanderExc(String msg) {