8 public class ParameterExpander {
9 final static String NODE_SEPARATOR = ".";
10 final static char STRING_ESCAPE_CHARACTER = '\\';
12 public static List splitString(String aString, String aSeparator) {
13 List result= new Vector();
14 int previousPosition = 0;
16 int endOfNamePosition;
18 while ((position = aString.indexOf(aSeparator, previousPosition))>=0) {
19 result.add(aString.substring(previousPosition, position));
20 previousPosition = position + aSeparator.length();
23 result.add(aString.substring(previousPosition, aString.length()));
28 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
34 i = aParts.iterator();
37 String part = (String) i.next();
39 if (!(node instanceof Map)) {
40 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
43 if (location.length()>0) {
44 location=location + NODE_SEPARATOR;
46 location = location + part;
48 newNode = ((Map) node).get(part);
51 if (aMakeIfNotPresent) {
52 newNode = new HashMap();
53 ((Map) node).put(part, newNode);
56 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
64 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
66 List parts = splitString(aKey, NODE_SEPARATOR);
68 node = findNode(aKey, aMap, parts, false);
73 public static String findStringForKey(Map aMap, String aKey) throws Exception {
74 Object expandedValue = findValueForKey(aMap, aKey);
76 if (!(expandedValue instanceof String))
77 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
79 return (String) expandedValue;
82 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
83 List parts = splitString(aKey, NODE_SEPARATOR);
85 String key = (String) parts.get(parts.size()-1);
86 parts.remove(parts.size()-1);
88 Object node=findNode(aKey, aMap, parts, true);
90 if (node instanceof Map) {
91 ((Map) node).put(key, aValue);
94 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
97 public static String expandExpression(Map aMap, String anExpression) throws Exception {
98 int previousPosition = 0;
100 int endOfExpressionPosition;
101 StringBuffer result = new StringBuffer();
103 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
104 result.append(anExpression.substring(previousPosition, position));
106 if (position>=anExpression.length()-1) {
107 result.append(anExpression.substring(position, anExpression.length()));
108 previousPosition=anExpression.length();
112 if (anExpression.charAt(position+1) == '{') {
113 endOfExpressionPosition=position+2;
114 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
115 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
116 char boundary = anExpression.charAt(endOfExpressionPosition);
118 endOfExpressionPosition++;
119 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
120 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
121 endOfExpressionPosition++;
122 endOfExpressionPosition++;
124 if (endOfExpressionPosition<anExpression.length()) {
125 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
128 endOfExpressionPosition++;
130 if (endOfExpressionPosition<anExpression.length()) {
131 result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
132 previousPosition=endOfExpressionPosition+1;
135 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
140 previousPosition=position+2;
141 result.append(anExpression.charAt(position+1));
145 result.append(anExpression.substring(previousPosition, anExpression.length()));
147 return result.toString();
150 public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
151 Parser parser = new Parser(anExpression, aMap);
153 return parser.parseBoolean();
156 public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
157 Parser parser = new Parser(anExpression, aMap);
159 return parser.parseString();
162 public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
163 if (anExpression == null || anExpression.trim().equals(""))
166 return evaluateIntegerExpression(aMap, anExpression);
169 public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
170 Parser parser = new Parser(anExpression, aMap);
172 return parser.parseInteger();
175 public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
176 Parser parser = new Parser(anExpression, aMap);
178 return parser.parseWhole();
181 private static class Reader {
183 private int position;
185 public Reader(String aData) {
190 public Character peek() {
191 if (position<data.length()) {
192 return (new Character(data.charAt(position)));
198 public boolean hasNext() {
202 public Character getNext() {
203 Character result = peek();
211 public String getPositionString() {
212 return data.substring(0, position) + "<__>" + data.substring(position) ;
216 private static abstract class Token {
219 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
220 private static class LeftSquareBraceToken extends PunctuationToken {};
221 private static class RightSquareBraceToken extends PunctuationToken {};
222 private static class EqualsToken extends PunctuationToken {};
223 private static class EqualsNotToken extends PunctuationToken {};
224 private static class NOTToken extends PunctuationToken {};
225 private static class LeftParenthesisToken extends PunctuationToken {};
226 private static class RightParenthesisToken extends PunctuationToken {};
227 private static class CommaToken extends PunctuationToken {};
228 private static class PeriodToken extends PunctuationToken {};
229 private static class PlusToken extends PunctuationToken {};
230 private static class TimesToken extends PunctuationToken {};
231 private static class DivideToken extends PunctuationToken {};
232 private static class MinusToken extends PunctuationToken {};
233 private static class ConcatenateToken extends PunctuationToken {};
234 private static class LessThanOrEqualsToken extends PunctuationToken {};
235 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
236 private static class LessThanToken extends PunctuationToken {};
237 private static class GreaterThanToken extends PunctuationToken {};
240 private static class IdentifierToken extends Token {
243 public IdentifierToken(String aName) {
247 public String getName() {
253 private static class LiteralToken extends Token {
254 private Object value;
256 public LiteralToken(Object aValue) {
260 public Object getValue() {
265 private static class Scanner {
266 private Reader reader;
267 private Token nextToken;
268 private String positionString;
270 public Scanner(Reader aReader) {
273 positionString = reader.getPositionString();
276 public Token scanStringLiteral() {
277 StringBuffer result = new StringBuffer();
280 delimiter = reader.getNext();
282 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
283 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
285 if (reader.hasNext())
286 result.append(reader.getNext());
289 result.append(reader.getNext());
293 if (!reader.hasNext())
294 throw new RuntimeException("unterminated string");
298 return new LiteralToken(result.toString());
301 public String getPositionString() {
302 return positionString;
305 private Token scanNumber() {
306 StringBuffer result = new StringBuffer();
307 result.append(reader.getNext());
309 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
310 result.append(reader.getNext());
314 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
316 catch (NumberFormatException e) {
317 throw new RuntimeException("Invalid number: " + e.getMessage());
321 private Token scanIdentifierKeyword() {
322 StringBuffer result = new StringBuffer();
323 result.append(reader.getNext());
325 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
326 result.append(reader.getNext());
329 return new IdentifierToken(result.toString());
332 private Token scanPunctuation() {
335 c = reader.getNext();
337 switch(c.charValue()) {
338 case '[': return new LeftSquareBraceToken();
339 case ']': return new RightSquareBraceToken();
341 if (reader.hasNext() && reader.peek().charValue() == '=') {
343 return new EqualsToken();
346 throw new RuntimeException("Unknown character: '='");
350 if (reader.hasNext() && reader.peek().charValue() == '=') {
352 return new EqualsNotToken();
355 return new NOTToken();
358 case '(': return new LeftParenthesisToken ();
360 case ')': return new RightParenthesisToken ();
361 case ',': return new CommaToken ();
362 case '.': return new PeriodToken ();
364 if (reader.hasNext() && reader.peek().charValue() == '+') {
366 return new ConcatenateToken();
369 return new PlusToken ();
371 case '*': return new TimesToken ();
372 case '/': return new DivideToken ();
373 case '-': return new MinusToken ();
375 if (reader.hasNext() && reader.peek().charValue() == '=') {
377 return new LessThanOrEqualsToken();
380 return new LessThanToken();
384 if (reader.hasNext() && reader.peek().charValue() == '=') {
386 return new GreaterThanOrEqualsToken();
389 return new GreaterThanToken();
392 throw new RuntimeException("Unexpected character: "+c);
396 public void skipWhitespace() {
397 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
401 private boolean isIdentifierStart(char c) {
402 return Character.isLetter(c) || (c == '_');
405 private boolean isIdentifierRest(char c) {
406 return Character.isLetterOrDigit(c) || (c == '_');
409 private boolean isNumberStart(char c) {
410 return Character.isDigit(c);
413 private boolean isNumberRest(char c) {
414 return Character.isDigit(c);
417 public Token scanNext() {
422 if (reader.hasNext()) {
423 Character c = reader.peek();
425 switch(c.charValue()) {
428 result = scanStringLiteral();
432 if (isIdentifierStart(c.charValue())) {
433 result = scanIdentifierKeyword();
435 else if (isNumberStart(c.charValue())) {
436 result = scanNumber();
439 result = scanPunctuation();
449 public Token scan() {
450 Token result = peek();
452 positionString = reader.getPositionString();
457 public Token peek() {
458 if (nextToken==null) {
459 nextToken = scanNext();
465 public boolean hasToken() {
470 private static class Parser {
471 private Scanner scanner;
472 private Map valueMap;
474 public Parser(String anExpression, Map aValueMap) {
475 scanner = new Scanner(new Reader(anExpression));
476 valueMap = aValueMap;
479 public boolean parseBoolean() {
481 return interpretAsBoolean(parseWhole());
483 catch (Throwable t) {
484 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
488 public int parseInteger() {
490 return interpretAsInteger(parseWhole());
492 catch (Throwable t) {
493 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
497 public String parseString() {
499 return interpretAsString(parseWhole());
501 catch (Throwable t) {
502 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
506 private String getLocation() {
507 return scanner.getPositionString();
510 private Object parseWhole() {
511 Object result = parse();
513 if (scanner.hasToken()) {
514 throw new RuntimeException("Operator expected");
520 private Object parse() {
521 return parseUntil(MAX_OPERATOR_LEVEL);
524 private Object parseSet() {
527 Set result = new HashSet();
529 token = scanner.scan();
530 if (!(token instanceof LeftParenthesisToken)) {
531 throw new RuntimeException("( expected after in keyword");
534 if (scanner.peek() instanceof RightParenthesisToken) {
540 expression = parse();
542 if (expression==null) {
543 throw new RuntimeException("expression expected");
546 result.add(expression);
548 token = scanner.scan();
550 while (token instanceof CommaToken);
552 if (!(token instanceof RightParenthesisToken)) {
553 throw new RuntimeException(") or , expected");
559 private Object parseVariable() {
562 Object currentValue = valueMap;
566 token = scanner.scan();
567 if (token instanceof LeftSquareBraceToken) {
568 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
569 token = scanner.scan();
570 if (!(token instanceof RightSquareBraceToken))
571 throw new RuntimeException("] expected");
573 else if (token instanceof IdentifierToken) {
574 qualifier = ((IdentifierToken) token).getName();
577 throw new RuntimeException("fieldname or [ expected");
579 if (currentValue!=null) {
580 if (currentValue instanceof Map) {
581 currentValue = ((Map) currentValue).get(qualifier);
584 throw new RuntimeException("cannot reference into anything other than a map");
588 // throw? or allow null.null?
591 if (scanner.peek() instanceof PeriodToken)
604 private Object parseUntil(int aMaxOperatorLevel) {
605 Token token = scanner.peek();
608 if (token instanceof LeftParenthesisToken) {
611 token = scanner.peek();
612 if (!(token instanceof RightParenthesisToken))
613 throw new RuntimeException(") expected");
616 else if (isUnaryOperator(token)) {
618 value = parseUntil(unaryOperatorLevel(token));
619 value = expandOperatorExpression(token, value);
621 else if (token instanceof IdentifierToken) {
622 value = parseVariable();
624 else if (token instanceof LiteralToken) {
626 value = ((LiteralToken) token).getValue();
629 throw new RuntimeException("Expression expected");
631 token = scanner.peek();
633 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
637 if (isINOperator(token)) {
641 value2 = parseUntil(binaryOperatorLevel(token));
644 value = expandOperatorExpression(token, value, value2);
646 token = scanner.peek();
652 private static final int MAX_OPERATOR_LEVEL = 1000; // && || !
653 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
654 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
655 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
656 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
658 private int unaryOperatorLevel(Token aToken) {
659 if (aToken instanceof NOTToken)
660 return LOGICAL_OPERATOR_LEVEL;
661 else if (aToken instanceof MinusToken)
662 return ADDITION_OPERATOR_LEVEL;
664 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
667 private boolean isUnaryOperator(Token aToken) {
669 ((aToken instanceof NOTToken) ||
670 (aToken instanceof MinusToken));
673 private int binaryOperatorLevel(Token aToken) {
674 if (isANDOperator(aToken) ||
675 isOROperator(aToken))
676 return LOGICAL_OPERATOR_LEVEL;
678 if ((aToken instanceof EqualsToken) ||
679 (aToken instanceof EqualsNotToken) ||
680 (aToken instanceof LessThanOrEqualsToken) ||
681 (aToken instanceof LessThanToken) ||
682 (aToken instanceof GreaterThanOrEqualsToken) ||
683 (aToken instanceof GreaterThanToken) ||
684 isINOperator(aToken))
685 return COMPARISON_OPERATOR_LEVEL;
687 if ((aToken instanceof PlusToken) ||
688 (aToken instanceof ConcatenateToken) ||
689 (aToken instanceof MinusToken))
690 return ADDITION_OPERATOR_LEVEL;
692 if ((aToken instanceof TimesToken) ||
693 (aToken instanceof DivideToken))
694 return MULTIPLICATION_OPERATOR_LEVEL;
696 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
699 private boolean isINOperator(Token aToken) {
700 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
703 private boolean isANDOperator(Token aToken) {
704 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
707 private boolean isOROperator(Token aToken) {
708 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
711 private boolean isBinaryOperator(Token aToken) {
713 (aToken instanceof EqualsToken) ||
714 (aToken instanceof EqualsNotToken) ||
715 (aToken instanceof PlusToken) ||
716 (aToken instanceof TimesToken) ||
717 (aToken instanceof DivideToken) ||
718 (aToken instanceof MinusToken) ||
719 (aToken instanceof ConcatenateToken) ||
720 (aToken instanceof LessThanOrEqualsToken) ||
721 (aToken instanceof LessThanToken) ||
722 (aToken instanceof GreaterThanOrEqualsToken) ||
723 (aToken instanceof GreaterThanToken) ||
724 isINOperator(aToken) ||
725 isOROperator(aToken) ||
726 isANDOperator(aToken);
729 private boolean interpretAsBoolean(Object aValue) {
730 if (aValue instanceof Boolean)
731 return ((Boolean) aValue).booleanValue();
736 private int interpretAsInteger(Object aValue) {
737 if (aValue instanceof Integer)
738 return ((Integer) aValue).intValue();
740 if (aValue instanceof String) {
742 return Integer.parseInt((String) aValue);
744 catch (Throwable t) {
748 throw new RuntimeException("Not an integer");
751 private String interpretAsString(Object aValue) {
752 if (aValue instanceof String)
753 return (String) aValue;
754 if (aValue instanceof Integer)
755 return ((Integer) aValue).toString();
757 throw new RuntimeException("Not a string");
760 private Object expandOperatorExpression(Token aToken, Object aValue) {
761 if (aToken instanceof NOTToken)
762 return new Boolean(!interpretAsBoolean(aValue));
763 else if (aToken instanceof MinusToken)
764 return new Integer(-interpretAsInteger(aValue));
766 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
769 private boolean areEqual(Object aValue1, Object aValue2) {
770 if (aValue1==null || aValue2==null)
771 return (aValue1==null) && (aValue2==null);
773 return aValue1.equals(aValue2);
776 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
777 if (isINOperator(aToken)) {
778 if (!(aValue2 instanceof Set)) {
779 throw new RuntimeException("Internal error: set expected");
782 Iterator i = ((Set) aValue2).iterator();
784 while (i.hasNext()) {
785 if (areEqual(aValue1, i.next()))
789 return Boolean.FALSE;
792 if (isANDOperator(aToken))
793 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
794 if (isOROperator(aToken))
795 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
796 if (aToken instanceof EqualsToken) {
797 return new Boolean(areEqual(aValue1, aValue2));
799 if (aToken instanceof EqualsNotToken)
800 return new Boolean(!areEqual(aValue1, aValue2));
801 if (aToken instanceof PlusToken)
802 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
803 if (aToken instanceof TimesToken)
804 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
805 if (aToken instanceof DivideToken)
806 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
807 if (aToken instanceof MinusToken)
808 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
810 if (aToken instanceof ConcatenateToken)
811 return interpretAsString(aValue1) + interpretAsString(aValue2);
813 if (aToken instanceof LessThanOrEqualsToken)
814 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
815 if (aToken instanceof LessThanToken)
816 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
817 if (aToken instanceof GreaterThanOrEqualsToken)
818 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
819 if (aToken instanceof GreaterThanToken)
820 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
822 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
826 public static class ParameterExpanderExc extends Exc {
827 public ParameterExpanderExc(String msg, Object[] objects) {