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 mir.generator.Generator;
33 import mir.generator.GeneratorExc;
35 import org.apache.commons.beanutils.MethodUtils;
36 import org.apache.commons.beanutils.PropertyUtils;
41 * Class to work with expressions. Will be gradually phased out and replaced
42 * with {@link mir.util.expressions.ExpressionParser}
44 public class ParameterExpander {
45 final static String NODE_SEPARATOR = ".";
46 final static char STRING_ESCAPE_CHARACTER = '\\';
49 * Fundamental method to retrieve a field of an object. Supported are
50 * maps, beans and objects with a generic get method
52 public static Object getObjectField(Object anObject, Object aField) {
53 if (anObject instanceof Map) {
54 return ((Map) anObject).get(aField);
56 else if ((aField instanceof String) && PropertyUtils.isReadable(anObject, (String) aField)) {
58 return PropertyUtils.getProperty(anObject, (String) aField);
61 throw new RuntimeException(t.getMessage());
66 return MethodUtils.invokeExactMethod(anObject, "get", aField);
69 throw new RuntimeException("Invalid reference of " + aField + " into " + anObject);
74 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
80 i = aParts.iterator();
83 String part = (String) i.next();
85 if (!(node instanceof Map)) {
86 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
89 if (location.length()>0) {
90 location=location + NODE_SEPARATOR;
92 location = location + part;
94 newNode = ((Map) node).get(part);
97 if (aMakeIfNotPresent) {
98 newNode = new HashMap();
99 ((Map) node).put(part, newNode);
102 throw new ParameterExpanderExc( "Can't expand key " + aKey + ": " + location + " does not exist");
110 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
112 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
114 node = findNode(aKey, aMap, parts, false);
119 public static String findStringForKey(Map aMap, String aKey) throws Exception {
120 Object expandedValue = findValueForKey(aMap, aKey);
122 if (!(expandedValue instanceof String))
123 throw new ParameterExpanderExc( "Value of key is not a string but a " + expandedValue.getClass().getName());
125 return (String) expandedValue;
128 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
129 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
131 String key = (String) parts.get(parts.size()-1);
132 parts.remove(parts.size()-1);
134 Object node=findNode(aKey, aMap, parts, true);
136 // todo: bean support
137 if (node instanceof Map) {
138 ((Map) node).put(key, aValue);
141 throw new ParameterExpanderExc( "Can't set key " + aKey + " : not inside a Map");
144 public static String expandExpression(Object aContext, String anExpression) throws Exception {
145 int previousPosition = 0;
147 int endOfExpressionPosition;
148 StringBuffer result = new StringBuffer();
150 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
151 result.append(anExpression.substring(previousPosition, position));
153 if (position>=anExpression.length()-1) {
154 result.append(anExpression.substring(position, anExpression.length()));
155 previousPosition=anExpression.length();
159 if (anExpression.charAt(position+1) == '{') {
160 endOfExpressionPosition=position+2;
161 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
162 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
163 char boundary = anExpression.charAt(endOfExpressionPosition);
165 endOfExpressionPosition++;
166 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
167 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
168 endOfExpressionPosition++;
169 endOfExpressionPosition++;
171 if (endOfExpressionPosition>=anExpression.length()) {
172 throw new ParameterExpanderExc("Unterminated string in '" +anExpression+"'");
175 endOfExpressionPosition++;
177 if (endOfExpressionPosition<anExpression.length()) {
178 result.append(evaluateStringExpression(aContext, anExpression.substring(position+2, endOfExpressionPosition)));
179 previousPosition=endOfExpressionPosition+1;
182 throw new ParameterExpanderExc("Missing } in " + anExpression);
187 previousPosition=position+2;
188 result.append(anExpression.charAt(position+1));
192 result.append(anExpression.substring(previousPosition, anExpression.length()));
194 return result.toString();
197 public static boolean evaluateBooleanExpression(Object aMap, String anExpression) throws Exception {
198 Parser parser = new Parser(anExpression, aMap);
200 return parser.parseBoolean();
203 public static String evaluateStringExpression(Object aMap, String anExpression) throws Exception {
204 Parser parser = new Parser(anExpression, aMap);
206 return parser.parseString();
209 public static int evaluateIntegerExpressionWithDefault(Object aMap, String anExpression, int aDefault) throws Exception {
210 if (anExpression == null || anExpression.trim().equals(""))
213 return evaluateIntegerExpression(aMap, anExpression);
216 public static int evaluateIntegerExpression(Object aMap, String anExpression) throws Exception {
217 Parser parser = new Parser(anExpression, aMap);
219 return parser.parseInteger();
222 public static Object evaluateExpression(Object aRoot, String anExpression) throws Exception {
223 Parser parser = new Parser(anExpression, aRoot);
225 return parser.parseWhole();
228 private static class Reader {
230 private int position;
232 public Reader(String aData) {
237 public Character peek() {
238 if (position<data.length()) {
239 return (new Character(data.charAt(position)));
245 public boolean hasNext() {
249 public Character getNext() {
250 Character result = peek();
258 public String getPositionString() {
259 return data.substring(0, position) + "<__>" + data.substring(position) ;
263 private static abstract class Token {
266 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
267 private static class LeftSquareBraceToken extends PunctuationToken {};
268 private static class RightSquareBraceToken extends PunctuationToken {};
269 private static class EqualsToken extends PunctuationToken {};
270 private static class EqualsNotToken extends PunctuationToken {};
271 private static class NOTToken extends PunctuationToken {};
272 private static class LeftParenthesisToken extends PunctuationToken {};
273 private static class RightParenthesisToken extends PunctuationToken {};
274 private static class CommaToken extends PunctuationToken {};
275 private static class PeriodToken extends PunctuationToken {};
276 private static class PlusToken extends PunctuationToken {};
277 private static class TimesToken extends PunctuationToken {};
278 private static class DivideToken extends PunctuationToken {};
279 private static class MinusToken extends PunctuationToken {};
280 private static class ConcatenateToken extends PunctuationToken {};
281 private static class LessThanOrEqualsToken extends PunctuationToken {};
282 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
283 private static class LessThanToken extends PunctuationToken {};
284 private static class GreaterThanToken extends PunctuationToken {};
287 private static class IdentifierToken extends Token {
290 public IdentifierToken(String aName) {
294 public String getName() {
300 private static class LiteralToken extends Token {
301 private Object value;
303 public LiteralToken(Object aValue) {
307 public Object getValue() {
312 private static class Scanner {
313 private Reader reader;
314 private Token nextToken;
315 private String positionString;
317 public Scanner(Reader aReader) {
320 positionString = reader.getPositionString();
323 public Token scanStringLiteral() {
324 StringBuffer result = new StringBuffer();
327 delimiter = reader.getNext();
329 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
330 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
332 if (reader.hasNext())
333 result.append(reader.getNext());
336 result.append(reader.getNext());
340 if (!reader.hasNext())
341 throw new RuntimeException("unterminated string");
345 return new LiteralToken(result.toString());
348 public String getPositionString() {
349 return positionString;
352 private Token scanNumber() {
353 StringBuffer result = new StringBuffer();
354 result.append(reader.getNext());
356 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
357 result.append(reader.getNext());
361 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
363 catch (NumberFormatException e) {
364 throw new RuntimeException("Invalid number: " + e.getMessage());
368 private Token scanIdentifierKeyword() {
369 StringBuffer result = new StringBuffer();
370 result.append(reader.getNext());
372 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
373 result.append(reader.getNext());
376 return new IdentifierToken(result.toString());
379 private Token scanPunctuation() {
382 c = reader.getNext();
384 switch(c.charValue()) {
385 case '[': return new LeftSquareBraceToken();
386 case ']': return new RightSquareBraceToken();
388 if (reader.hasNext() && reader.peek().charValue() == '=') {
390 return new EqualsToken();
393 throw new RuntimeException("Unknown character: '='");
397 if (reader.hasNext() && reader.peek().charValue() == '=') {
399 return new EqualsNotToken();
402 return new NOTToken();
405 case '(': return new LeftParenthesisToken ();
407 case ')': return new RightParenthesisToken ();
408 case ',': return new CommaToken ();
409 case '.': return new PeriodToken ();
411 if (reader.hasNext() && reader.peek().charValue() == '+') {
413 return new ConcatenateToken();
416 return new PlusToken ();
418 case '*': return new TimesToken ();
419 case '/': return new DivideToken ();
420 case '-': return new MinusToken ();
422 if (reader.hasNext() && reader.peek().charValue() == '=') {
424 return new LessThanOrEqualsToken();
427 return new LessThanToken();
431 if (reader.hasNext() && reader.peek().charValue() == '=') {
433 return new GreaterThanOrEqualsToken();
436 return new GreaterThanToken();
439 throw new RuntimeException("Unexpected character: "+c);
443 public void skipWhitespace() {
444 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
448 private boolean isIdentifierStart(char c) {
449 return Character.isLetter(c) || (c == '_');
452 private boolean isIdentifierRest(char c) {
453 return Character.isLetterOrDigit(c) || (c == '_');
456 private boolean isNumberStart(char c) {
457 return Character.isDigit(c);
460 private boolean isNumberRest(char c) {
461 return Character.isDigit(c);
464 public Token scanNext() {
469 if (reader.hasNext()) {
470 Character c = reader.peek();
472 switch(c.charValue()) {
475 result = scanStringLiteral();
479 if (isIdentifierStart(c.charValue())) {
480 result = scanIdentifierKeyword();
482 else if (isNumberStart(c.charValue())) {
483 result = scanNumber();
486 result = scanPunctuation();
496 public Token scan() {
497 Token result = peek();
499 positionString = reader.getPositionString();
504 public Token peek() {
505 if (nextToken==null) {
506 nextToken = scanNext();
512 public boolean hasToken() {
517 private static class Parser {
518 private Scanner scanner;
519 private Object valueMap;
521 public Parser(String anExpression, Object aValueMap) {
522 scanner = new Scanner(new Reader(anExpression));
523 valueMap = aValueMap;
526 public boolean parseBoolean() {
528 return interpretAsBoolean(parseWhole());
530 catch (Throwable t) {
531 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
535 public int parseInteger() {
537 return interpretAsInteger(parseWhole());
539 catch (Throwable t) {
540 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
544 public String parseString() {
546 return interpretAsString(parseWhole());
548 catch (Throwable t) {
549 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
553 private String getLocation() {
554 return scanner.getPositionString();
557 private Object parseWhole() {
558 Object result = parse();
560 if (scanner.hasToken()) {
561 throw new RuntimeException("Operator expected");
567 private Object parse() {
568 return parseUntil(MAX_OPERATOR_LEVEL);
571 private List parseList() {
574 List result = new ArrayList();
576 token = scanner.scan();
577 if (!(token instanceof LeftParenthesisToken)) {
578 throw new RuntimeException("( expected");
581 if (scanner.peek() instanceof RightParenthesisToken) {
587 expression = parse();
589 result.add(expression);
591 token = scanner.scan();
593 while (token instanceof CommaToken);
595 if (!(token instanceof RightParenthesisToken)) {
596 throw new RuntimeException(") or , expected");
602 private Object parseVariable() {
605 Object currentValue = valueMap;
610 token = scanner.peek();
612 if (token instanceof LeftSquareBraceToken) {
614 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
615 token = scanner.scan();
616 if (!(token instanceof RightSquareBraceToken))
617 throw new RuntimeException("] expected");
619 currentValue = getObjectField(currentValue, qualifier);
621 else if (token instanceof IdentifierToken) {
623 qualifier = ((IdentifierToken) token).getName();
625 currentValue = getObjectField(currentValue, qualifier);
627 else if (token instanceof LeftParenthesisToken) {
628 if (currentValue instanceof Generator.Function) {
629 parameters = parseList();
631 currentValue = ((Generator.Function) currentValue).perform(parameters);
633 catch (GeneratorExc t) {
634 throw new RuntimeException(t.getMessage());
638 throw new RuntimeException("not a function");
641 throw new RuntimeException("fieldname or [ expected");
643 if (scanner.peek() instanceof PeriodToken ||
644 scanner.peek() instanceof LeftSquareBraceToken ||
645 scanner.peek() instanceof LeftParenthesisToken) {
648 if (scanner.peek() instanceof PeriodToken)
659 private Object parseUntil(int aMaxOperatorLevel) {
660 Token token = scanner.peek();
663 if (token instanceof LeftParenthesisToken) {
666 token = scanner.peek();
667 if (!(token instanceof RightParenthesisToken))
668 throw new RuntimeException(") expected");
671 else if (isUnaryOperator(token)) {
673 value = parseUntil(unaryOperatorLevel(token));
674 value = expandOperatorExpression(token, value);
676 else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
677 value = parseVariable();
679 else if (token instanceof LiteralToken) {
681 value = ((LiteralToken) token).getValue();
684 throw new RuntimeException("Expression expected");
686 token = scanner.peek();
688 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
692 if (isINOperator(token)) {
693 value2 = parseList();
696 value2 = parseUntil(binaryOperatorLevel(token));
699 value = expandOperatorExpression(token, value, value2);
701 token = scanner.peek();
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; // * /
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;
719 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
722 private boolean isUnaryOperator(Token aToken) {
724 ((aToken instanceof NOTToken) ||
725 (aToken instanceof MinusToken));
728 private int binaryOperatorLevel(Token aToken) {
729 if (isANDOperator(aToken) ||
730 isOROperator(aToken))
731 return LOGICAL_OPERATOR_LEVEL;
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;
742 if ((aToken instanceof PlusToken) ||
743 (aToken instanceof ConcatenateToken) ||
744 (aToken instanceof MinusToken))
745 return ADDITION_OPERATOR_LEVEL;
747 if ((aToken instanceof TimesToken) ||
748 (aToken instanceof DivideToken))
749 return MULTIPLICATION_OPERATOR_LEVEL;
751 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
754 private boolean isINOperator(Token aToken) {
755 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
758 private boolean isANDOperator(Token aToken) {
759 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
762 private boolean isOROperator(Token aToken) {
763 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
766 private boolean isBinaryOperator(Token aToken) {
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);
784 private boolean interpretAsBoolean(Object aValue) {
785 if (aValue instanceof Boolean)
786 return ((Boolean) aValue).booleanValue();
788 if (aValue instanceof RewindableIterator) {
789 ((RewindableIterator) aValue).rewind();
792 if (aValue instanceof Iterator) {
793 return ((Iterator) aValue).hasNext();
796 if (aValue instanceof List) {
797 return ((List) aValue).size()>0;
800 if (aValue instanceof String) {
801 return ((String) aValue).length()>0;
807 private int interpretAsInteger(Object aValue) {
808 if (aValue instanceof Integer)
809 return ((Integer) aValue).intValue();
811 if (aValue instanceof String) {
813 return Integer.parseInt((String) aValue);
815 catch (Throwable t) {
819 throw new RuntimeException("Not an integer");
822 private String interpretAsString(Object aValue) {
825 if (aValue instanceof String)
826 return (String) aValue;
827 if (aValue instanceof Integer)
828 return aValue.toString();
830 throw new RuntimeException("Not a string");
833 private Object expandOperatorExpression(Token aToken, Object aValue) {
834 if (aToken instanceof NOTToken)
835 return new Boolean(!interpretAsBoolean(aValue));
836 else if (aToken instanceof MinusToken)
837 return new Integer(-interpretAsInteger(aValue));
839 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
842 private boolean areEqual(Object aValue1, Object aValue2) {
843 if (aValue1==null || aValue2==null)
844 return (aValue1==null) && (aValue2==null);
846 return aValue1.equals(aValue2);
849 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
850 if (isINOperator(aToken)) {
851 if (!(aValue2 instanceof List)) {
852 throw new RuntimeException("Internal error: List expected");
855 Iterator i = ((List) aValue2).iterator();
857 while (i.hasNext()) {
858 if (areEqual(aValue1, i.next()))
862 return Boolean.FALSE;
865 if (isANDOperator(aToken))
866 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
867 if (isOROperator(aToken))
868 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
869 if (aToken instanceof EqualsToken) {
870 return new Boolean(areEqual(aValue1, aValue2));
872 if (aToken instanceof EqualsNotToken)
873 return new Boolean(!areEqual(aValue1, aValue2));
874 if (aToken instanceof PlusToken)
875 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
876 if (aToken instanceof TimesToken)
877 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
878 if (aToken instanceof DivideToken)
879 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
880 if (aToken instanceof MinusToken)
881 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
883 if (aToken instanceof ConcatenateToken)
884 return interpretAsString(aValue1) + interpretAsString(aValue2);
886 if (aToken instanceof LessThanOrEqualsToken)
887 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
888 if (aToken instanceof LessThanToken)
889 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
890 if (aToken instanceof GreaterThanOrEqualsToken)
891 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
892 if (aToken instanceof GreaterThanToken)
893 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
895 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
899 public static class ParameterExpanderExc extends Exc {
900 public ParameterExpanderExc(String msg) {