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.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
36 import java.util.Vector;
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 = '\\';
49 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
55 i = aParts.iterator();
58 String part = (String) i.next();
60 if (!(node instanceof Map)) {
61 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
64 if (location.length()>0) {
65 location=location + NODE_SEPARATOR;
67 location = location + part;
69 newNode = ((Map) node).get(part);
72 if (aMakeIfNotPresent) {
73 newNode = new HashMap();
74 ((Map) node).put(part, newNode);
77 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
85 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
87 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
89 node = findNode(aKey, aMap, parts, false);
94 public static String findStringForKey(Map aMap, String aKey) throws Exception {
95 Object expandedValue = findValueForKey(aMap, aKey);
97 if (!(expandedValue instanceof String))
98 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
100 return (String) expandedValue;
103 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
104 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
106 String key = (String) parts.get(parts.size()-1);
107 parts.remove(parts.size()-1);
109 Object node=findNode(aKey, aMap, parts, true);
111 if (node instanceof Map) {
112 ((Map) node).put(key, aValue);
115 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
118 public static String expandExpression(Map aMap, String anExpression) throws Exception {
119 int previousPosition = 0;
121 int endOfExpressionPosition;
122 StringBuffer result = new StringBuffer();
124 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
125 result.append(anExpression.substring(previousPosition, position));
127 if (position>=anExpression.length()-1) {
128 result.append(anExpression.substring(position, anExpression.length()));
129 previousPosition=anExpression.length();
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);
139 endOfExpressionPosition++;
140 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
141 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
142 endOfExpressionPosition++;
143 endOfExpressionPosition++;
145 if (endOfExpressionPosition>=anExpression.length()) {
146 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
149 endOfExpressionPosition++;
151 if (endOfExpressionPosition<anExpression.length()) {
152 result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
153 previousPosition=endOfExpressionPosition+1;
156 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
161 previousPosition=position+2;
162 result.append(anExpression.charAt(position+1));
166 result.append(anExpression.substring(previousPosition, anExpression.length()));
168 return result.toString();
171 public static boolean evaluateBooleanExpression(Object aMap, String anExpression) throws Exception {
172 Parser parser = new Parser(anExpression, aMap);
174 return parser.parseBoolean();
177 public static String evaluateStringExpression(Object aMap, String anExpression) throws Exception {
178 Parser parser = new Parser(anExpression, aMap);
180 return parser.parseString();
183 public static int evaluateIntegerExpressionWithDefault(Object aMap, String anExpression, int aDefault) throws Exception {
184 if (anExpression == null || anExpression.trim().equals(""))
187 return evaluateIntegerExpression(aMap, anExpression);
190 public static int evaluateIntegerExpression(Object aMap, String anExpression) throws Exception {
191 Parser parser = new Parser(anExpression, aMap);
193 return parser.parseInteger();
196 public static Object evaluateExpression(Object aRoot, String anExpression) throws Exception {
197 Parser parser = new Parser(anExpression, aRoot);
199 return parser.parseWhole();
202 private static class Reader {
204 private int position;
206 public Reader(String aData) {
211 public Character peek() {
212 if (position<data.length()) {
213 return (new Character(data.charAt(position)));
219 public boolean hasNext() {
223 public Character getNext() {
224 Character result = peek();
232 public String getPositionString() {
233 return data.substring(0, position) + "<__>" + data.substring(position) ;
237 private static abstract class Token {
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 {};
261 private static class IdentifierToken extends Token {
264 public IdentifierToken(String aName) {
268 public String getName() {
274 private static class LiteralToken extends Token {
275 private Object value;
277 public LiteralToken(Object aValue) {
281 public Object getValue() {
286 private static class Scanner {
287 private Reader reader;
288 private Token nextToken;
289 private String positionString;
291 public Scanner(Reader aReader) {
294 positionString = reader.getPositionString();
297 public Token scanStringLiteral() {
298 StringBuffer result = new StringBuffer();
301 delimiter = reader.getNext();
303 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
304 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
306 if (reader.hasNext())
307 result.append(reader.getNext());
310 result.append(reader.getNext());
314 if (!reader.hasNext())
315 throw new RuntimeException("unterminated string");
319 return new LiteralToken(result.toString());
322 public String getPositionString() {
323 return positionString;
326 private Token scanNumber() {
327 StringBuffer result = new StringBuffer();
328 result.append(reader.getNext());
330 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
331 result.append(reader.getNext());
335 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
337 catch (NumberFormatException e) {
338 throw new RuntimeException("Invalid number: " + e.getMessage());
342 private Token scanIdentifierKeyword() {
343 StringBuffer result = new StringBuffer();
344 result.append(reader.getNext());
346 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
347 result.append(reader.getNext());
350 return new IdentifierToken(result.toString());
353 private Token scanPunctuation() {
356 c = reader.getNext();
358 switch(c.charValue()) {
359 case '[': return new LeftSquareBraceToken();
360 case ']': return new RightSquareBraceToken();
362 if (reader.hasNext() && reader.peek().charValue() == '=') {
364 return new EqualsToken();
367 throw new RuntimeException("Unknown character: '='");
371 if (reader.hasNext() && reader.peek().charValue() == '=') {
373 return new EqualsNotToken();
376 return new NOTToken();
379 case '(': return new LeftParenthesisToken ();
381 case ')': return new RightParenthesisToken ();
382 case ',': return new CommaToken ();
383 case '.': return new PeriodToken ();
385 if (reader.hasNext() && reader.peek().charValue() == '+') {
387 return new ConcatenateToken();
390 return new PlusToken ();
392 case '*': return new TimesToken ();
393 case '/': return new DivideToken ();
394 case '-': return new MinusToken ();
396 if (reader.hasNext() && reader.peek().charValue() == '=') {
398 return new LessThanOrEqualsToken();
401 return new LessThanToken();
405 if (reader.hasNext() && reader.peek().charValue() == '=') {
407 return new GreaterThanOrEqualsToken();
410 return new GreaterThanToken();
413 throw new RuntimeException("Unexpected character: "+c);
417 public void skipWhitespace() {
418 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
422 private boolean isIdentifierStart(char c) {
423 return Character.isLetter(c) || (c == '_');
426 private boolean isIdentifierRest(char c) {
427 return Character.isLetterOrDigit(c) || (c == '_');
430 private boolean isNumberStart(char c) {
431 return Character.isDigit(c);
434 private boolean isNumberRest(char c) {
435 return Character.isDigit(c);
438 public Token scanNext() {
443 if (reader.hasNext()) {
444 Character c = reader.peek();
446 switch(c.charValue()) {
449 result = scanStringLiteral();
453 if (isIdentifierStart(c.charValue())) {
454 result = scanIdentifierKeyword();
456 else if (isNumberStart(c.charValue())) {
457 result = scanNumber();
460 result = scanPunctuation();
470 public Token scan() {
471 Token result = peek();
473 positionString = reader.getPositionString();
478 public Token peek() {
479 if (nextToken==null) {
480 nextToken = scanNext();
486 public boolean hasToken() {
491 private static class Parser {
492 private Scanner scanner;
493 private Object valueMap;
495 public Parser(String anExpression, Object aValueMap) {
496 scanner = new Scanner(new Reader(anExpression));
497 valueMap = aValueMap;
500 public boolean parseBoolean() {
502 return interpretAsBoolean(parseWhole());
504 catch (Throwable t) {
505 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
509 public int parseInteger() {
511 return interpretAsInteger(parseWhole());
513 catch (Throwable t) {
514 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
518 public String parseString() {
520 return interpretAsString(parseWhole());
522 catch (Throwable t) {
523 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
527 private String getLocation() {
528 return scanner.getPositionString();
531 private Object parseWhole() {
532 Object result = parse();
534 if (scanner.hasToken()) {
535 throw new RuntimeException("Operator expected");
541 private Object parse() {
542 return parseUntil(MAX_OPERATOR_LEVEL);
545 private List parseList() {
548 List result = new Vector();
550 token = scanner.scan();
551 if (!(token instanceof LeftParenthesisToken)) {
552 throw new RuntimeException("( expected");
555 if (scanner.peek() instanceof RightParenthesisToken) {
561 expression = parse();
563 result.add(expression);
565 token = scanner.scan();
567 while (token instanceof CommaToken);
569 if (!(token instanceof RightParenthesisToken)) {
570 throw new RuntimeException(") or , expected");
576 private Object evaluateObjectField(Object anObject, Object aField) {
577 if (anObject instanceof Map) {
578 return ((Map) anObject).get(aField);
580 else if ((aField instanceof String) && PropertyUtils.isReadable(anObject, (String) aField)) {
582 return PropertyUtils.getProperty(anObject, (String) aField);
584 catch (Throwable t) {
585 throw new RuntimeException(t.getMessage());
590 return MethodUtils.invokeExactMethod(anObject, "get", aField);
592 catch (Throwable t) {
593 throw new RuntimeException("Invalid reference of " + aField + " into " + anObject);
598 private Object parseVariable() {
601 Object currentValue = valueMap;
606 token = scanner.peek();
608 if (token instanceof LeftSquareBraceToken) {
610 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
611 token = scanner.scan();
612 if (!(token instanceof RightSquareBraceToken))
613 throw new RuntimeException("] expected");
615 currentValue = evaluateObjectField(currentValue, qualifier);
617 else if (token instanceof IdentifierToken) {
619 qualifier = ((IdentifierToken) token).getName();
621 currentValue = evaluateObjectField(currentValue, qualifier);
623 else if (token instanceof LeftParenthesisToken) {
624 if (currentValue instanceof Generator.GeneratorFunction) {
625 parameters = parseList();
627 currentValue = ((Generator.GeneratorFunction) currentValue).perform(parameters);
629 catch (GeneratorExc t) {
630 throw new RuntimeException(t.getMessage());
634 throw new RuntimeException("not a function");
637 throw new RuntimeException("fieldname or [ expected");
639 if (scanner.peek() instanceof PeriodToken ||
640 scanner.peek() instanceof LeftSquareBraceToken ||
641 scanner.peek() instanceof LeftParenthesisToken) {
644 if (scanner.peek() instanceof PeriodToken)
655 private Object parseUntil(int aMaxOperatorLevel) {
656 Token token = scanner.peek();
659 if (token instanceof LeftParenthesisToken) {
662 token = scanner.peek();
663 if (!(token instanceof RightParenthesisToken))
664 throw new RuntimeException(") expected");
667 else if (isUnaryOperator(token)) {
669 value = parseUntil(unaryOperatorLevel(token));
670 value = expandOperatorExpression(token, value);
672 else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
673 value = parseVariable();
675 else if (token instanceof LiteralToken) {
677 value = ((LiteralToken) token).getValue();
680 throw new RuntimeException("Expression expected");
682 token = scanner.peek();
684 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
688 if (isINOperator(token)) {
689 value2 = parseList();
692 value2 = parseUntil(binaryOperatorLevel(token));
695 value = expandOperatorExpression(token, value, value2);
697 token = scanner.peek();
703 private static final int MAX_OPERATOR_LEVEL = 1000; //
704 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
705 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
706 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
707 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
709 private int unaryOperatorLevel(Token aToken) {
710 if (aToken instanceof NOTToken)
711 return LOGICAL_OPERATOR_LEVEL;
712 else if (aToken instanceof MinusToken)
713 return ADDITION_OPERATOR_LEVEL;
715 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
718 private boolean isUnaryOperator(Token aToken) {
720 ((aToken instanceof NOTToken) ||
721 (aToken instanceof MinusToken));
724 private int binaryOperatorLevel(Token aToken) {
725 if (isANDOperator(aToken) ||
726 isOROperator(aToken))
727 return LOGICAL_OPERATOR_LEVEL;
729 if ((aToken instanceof EqualsToken) ||
730 (aToken instanceof EqualsNotToken) ||
731 (aToken instanceof LessThanOrEqualsToken) ||
732 (aToken instanceof LessThanToken) ||
733 (aToken instanceof GreaterThanOrEqualsToken) ||
734 (aToken instanceof GreaterThanToken) ||
735 isINOperator(aToken))
736 return COMPARISON_OPERATOR_LEVEL;
738 if ((aToken instanceof PlusToken) ||
739 (aToken instanceof ConcatenateToken) ||
740 (aToken instanceof MinusToken))
741 return ADDITION_OPERATOR_LEVEL;
743 if ((aToken instanceof TimesToken) ||
744 (aToken instanceof DivideToken))
745 return MULTIPLICATION_OPERATOR_LEVEL;
747 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
750 private boolean isINOperator(Token aToken) {
751 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
754 private boolean isANDOperator(Token aToken) {
755 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
758 private boolean isOROperator(Token aToken) {
759 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
762 private boolean isBinaryOperator(Token aToken) {
764 (aToken instanceof EqualsToken) ||
765 (aToken instanceof EqualsNotToken) ||
766 (aToken instanceof PlusToken) ||
767 (aToken instanceof TimesToken) ||
768 (aToken instanceof DivideToken) ||
769 (aToken instanceof MinusToken) ||
770 (aToken instanceof ConcatenateToken) ||
771 (aToken instanceof LessThanOrEqualsToken) ||
772 (aToken instanceof LessThanToken) ||
773 (aToken instanceof GreaterThanOrEqualsToken) ||
774 (aToken instanceof GreaterThanToken) ||
775 isINOperator(aToken) ||
776 isOROperator(aToken) ||
777 isANDOperator(aToken);
780 private boolean interpretAsBoolean(Object aValue) {
781 if (aValue instanceof Boolean)
782 return ((Boolean) aValue).booleanValue();
787 private int interpretAsInteger(Object aValue) {
788 if (aValue instanceof Integer)
789 return ((Integer) aValue).intValue();
791 if (aValue instanceof String) {
793 return Integer.parseInt((String) aValue);
795 catch (Throwable t) {
799 throw new RuntimeException("Not an integer");
802 private String interpretAsString(Object aValue) {
805 if (aValue instanceof String)
806 return (String) aValue;
807 if (aValue instanceof Integer)
808 return ((Integer) aValue).toString();
810 throw new RuntimeException("Not a string");
813 private Object expandOperatorExpression(Token aToken, Object aValue) {
814 if (aToken instanceof NOTToken)
815 return new Boolean(!interpretAsBoolean(aValue));
816 else if (aToken instanceof MinusToken)
817 return new Integer(-interpretAsInteger(aValue));
819 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
822 private boolean areEqual(Object aValue1, Object aValue2) {
823 if (aValue1==null || aValue2==null)
824 return (aValue1==null) && (aValue2==null);
826 return aValue1.equals(aValue2);
829 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
830 if (isINOperator(aToken)) {
831 if (!(aValue2 instanceof List)) {
832 throw new RuntimeException("Internal error: List expected");
835 Iterator i = ((List) aValue2).iterator();
837 while (i.hasNext()) {
838 if (areEqual(aValue1, i.next()))
842 return Boolean.FALSE;
845 if (isANDOperator(aToken))
846 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
847 if (isOROperator(aToken))
848 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
849 if (aToken instanceof EqualsToken) {
850 return new Boolean(areEqual(aValue1, aValue2));
852 if (aToken instanceof EqualsNotToken)
853 return new Boolean(!areEqual(aValue1, aValue2));
854 if (aToken instanceof PlusToken)
855 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
856 if (aToken instanceof TimesToken)
857 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
858 if (aToken instanceof DivideToken)
859 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
860 if (aToken instanceof MinusToken)
861 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
863 if (aToken instanceof ConcatenateToken)
864 return interpretAsString(aValue1) + interpretAsString(aValue2);
866 if (aToken instanceof LessThanOrEqualsToken)
867 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
868 if (aToken instanceof LessThanToken)
869 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
870 if (aToken instanceof GreaterThanOrEqualsToken)
871 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
872 if (aToken instanceof GreaterThanToken)
873 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
875 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
879 public static class ParameterExpanderExc extends Exc {
880 public ParameterExpanderExc(String msg, Object[] objects) {