8 public class ParameterExpander {
9 final static String NODE_SEPARATOR = ".";
11 public static List splitString(String aString, String aSeparator) {
12 List result= new Vector();
13 int previousPosition = 0;
15 int endOfNamePosition;
17 while ((position = aString.indexOf(aSeparator, previousPosition))>=0) {
18 result.add(aString.substring(previousPosition, position));
19 previousPosition = position + aSeparator.length();
22 result.add(aString.substring(previousPosition, aString.length()));
27 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
33 i = aParts.iterator();
36 String part = (String) i.next();
38 if (!(node instanceof Map)) {
39 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
42 if (location.length()>0) {
43 location=location + NODE_SEPARATOR;
45 location = location + part;
47 newNode = ((Map) node).get(part);
50 if (aMakeIfNotPresent) {
51 newNode = new HashMap();
52 ((Map) node).put(part, newNode);
55 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
63 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
65 List parts = splitString(aKey, NODE_SEPARATOR);
67 node = findNode(aKey, aMap, parts, false);
72 public static String findStringForKey(Map aMap, String aKey) throws Exception {
73 Object expandedValue = findValueForKey(aMap, aKey);
75 if (!(expandedValue instanceof String))
76 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
78 return (String) expandedValue;
81 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
82 List parts = splitString(aKey, NODE_SEPARATOR);
84 String key = (String) parts.get(parts.size()-1);
85 parts.remove(parts.size()-1);
87 Object node=findNode(aKey, aMap, parts, true);
89 if (node instanceof Map) {
90 ((Map) node).put(key, aValue);
93 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
96 public static String expandExpression(Map aMap, String anExpression) throws Exception {
97 int previousPosition = 0;
99 int endOfNamePosition;
100 StringBuffer result = new StringBuffer();
102 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
103 result.append(anExpression.substring(previousPosition, position));
105 if (position>=anExpression.length()-1) {
106 result.append(anExpression.substring(position, anExpression.length()));
107 previousPosition=anExpression.length();
111 if (anExpression.charAt(position+1) == '{') {
112 endOfNamePosition=anExpression.indexOf('}', position);
113 if (endOfNamePosition>=0) {
114 result.append(findStringForKey(aMap, anExpression.substring(position+2, endOfNamePosition)));
115 previousPosition=endOfNamePosition+1;
118 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
123 previousPosition=position+2;
124 result.append(anExpression.charAt(position+1));
128 result.append(anExpression.substring(previousPosition, anExpression.length()));
130 return result.toString();
133 public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
134 Parser parser = new Parser(anExpression, aMap);
136 return parser.parseBoolean();
139 public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
140 Parser parser = new Parser(anExpression, aMap);
142 return parser.parseWhole();
145 private static class Reader {
147 private int position;
149 public Reader(String aData) {
154 public Character peek() {
155 if (position<data.length()) {
156 return (new Character(data.charAt(position)));
162 public boolean hasNext() {
166 public Character getNext() {
167 Character result = peek();
175 public int getPosition() {
180 private static abstract class Token {
183 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
184 private static class LeftSquareBraceToken extends PunctuationToken {};
185 private static class RightSquareBraceToken extends PunctuationToken {};
186 private static class ANDToken extends PunctuationToken {};
187 private static class ORToken extends PunctuationToken {};
188 private static class EqualsToken extends PunctuationToken {};
189 private static class EqualsNotToken extends PunctuationToken {};
190 private static class NOTToken extends PunctuationToken {};
191 private static class LeftParenthesisToken extends PunctuationToken {};
192 private static class RightParenthesisToken extends PunctuationToken {};
193 private static class CommaToken extends PunctuationToken {};
194 private static class PeriodToken extends PunctuationToken {};
195 private static class PlusToken extends PunctuationToken {};
196 private static class TimesToken extends PunctuationToken {};
197 private static class DivideToken extends PunctuationToken {};
198 private static class MinusToken extends PunctuationToken {};
199 private static class ConcatenateToken extends PunctuationToken {};
200 private static class LessThanOrEqualsToken extends PunctuationToken {};
201 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
202 private static class LessThanToken extends PunctuationToken {};
203 private static class GreaterThanToken extends PunctuationToken {};
206 private static class IdentifierToken extends Token {
209 public IdentifierToken(String aName) {
213 public String getName() {
219 private static class LiteralToken extends Token {
220 private Object value;
222 public LiteralToken(Object aValue) {
226 public Object getValue() {
231 private static class Scanner {
232 private Reader reader;
233 private Token nextToken;
235 public Scanner(Reader aReader) {
239 public Token scanStringLiteral() {
240 StringBuffer result = new StringBuffer();
243 delimiter = reader.getNext();
245 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
246 result.append(reader.getNext());
249 if (!reader.hasNext())
250 throw new RuntimeException("unterminated string");
254 return new LiteralToken(result.toString());
257 public int getPosition() {
258 return reader.getPosition();
261 private Token scanNumber() {
262 StringBuffer result = new StringBuffer();
263 result.append(reader.getNext());
265 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
266 result.append(reader.getNext());
270 return new LiteralToken(Integer.getInteger(result.toString()));
272 catch (NumberFormatException e) {
273 throw new RuntimeException("Invalid number: " + e.getMessage());
277 private Token scanIdentifierKeyword() {
278 StringBuffer result = new StringBuffer();
279 result.append(reader.getNext());
281 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
282 result.append(reader.getNext());
285 return new IdentifierToken(result.toString());
288 private Token scanPunctuation() {
291 c = reader.getNext();
293 switch(c.charValue()) {
294 case '[': return new LeftSquareBraceToken();
295 case ']': return new RightSquareBraceToken();
297 if (reader.hasNext() && reader.peek().charValue() == '&') {
299 return new ANDToken();
302 return new ConcatenateToken();
305 if (reader.hasNext() && reader.peek().charValue() == '|') {
307 return new ORToken();
310 throw new RuntimeException("Unknown character: '|'");
314 if (reader.hasNext() && reader.peek().charValue() == '=') {
316 return new EqualsToken();
319 throw new RuntimeException("Unknown character: '='");
323 if (reader.hasNext() && reader.peek().charValue() == '=') {
325 return new EqualsNotToken();
328 return new NOTToken();
331 case '(': return new LeftParenthesisToken ();
333 case ')': return new RightParenthesisToken ();
334 case ',': return new CommaToken ();
335 case '.': return new PeriodToken ();
336 case '+': return new PlusToken ();
337 case '*': return new TimesToken ();
338 case '/': return new DivideToken ();
339 case '-': return new MinusToken ();
341 if (reader.hasNext() && reader.peek().charValue() == '=') {
343 return new LessThanOrEqualsToken();
346 return new LessThanToken();
350 if (reader.hasNext() && reader.peek().charValue() == '=') {
352 return new GreaterThanOrEqualsToken();
355 return new GreaterThanToken();
358 throw new RuntimeException("Unexpected character: "+c);
362 public void skipWhitespace() {
363 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
367 private boolean isIdentifierStart(char c) {
368 return Character.isLetter(c) || (c == '_');
371 private boolean isIdentifierRest(char c) {
372 return Character.isLetterOrDigit(c) || (c == '_');
375 private boolean isNumberStart(char c) {
376 return Character.isDigit(c);
379 private boolean isNumberRest(char c) {
380 return Character.isDigit(c);
383 public Token scanNext() {
386 if (!reader.hasNext())
389 Character c = reader.peek();
391 switch(c.charValue()) {
393 case '"': return scanStringLiteral();
396 if (isIdentifierStart(c.charValue())) {
397 return scanIdentifierKeyword();
399 else if (isNumberStart(c.charValue())) {
403 return scanPunctuation();
408 public Token scan() {
411 if (nextToken!=null) {
423 public Token peek() {
429 public boolean hasToken() {
434 private static class Parser {
435 private Scanner scanner;
436 private Map valueMap;
438 public Parser(String anExpression, Map aValueMap) {
439 scanner = new Scanner(new Reader(anExpression));
440 valueMap = aValueMap;
443 public boolean parseBoolean() {
445 return interpretAsBoolean(parseWhole());
447 catch (Throwable t) {
448 t.printStackTrace(System.out);
449 throw new RuntimeException("Parser error at position "+getLocation()+":"+t.getMessage());
453 public String parseString() {
455 return interpretAsString(parseWhole());
457 catch (Throwable t) {
458 throw new RuntimeException("Parser error at position "+getLocation()+":"+t.getMessage());
462 private String getLocation() {
463 int position = scanner.getPosition();
465 return Integer.toString(position);
468 private Object parseWhole() {
469 Object result = parse();
471 if (scanner.hasToken()) {
472 throw new RuntimeException("Operator expected");
478 private Object parse() {
479 return parseUntil(MAX_OPERATOR_LEVEL);
482 private Object parseSet() {
485 Set result = new HashSet();
487 token = scanner.scan();
488 if (!(token instanceof LeftParenthesisToken)) {
489 throw new RuntimeException("( expected after in keyword");
492 if (scanner.peek() instanceof RightParenthesisToken) {
498 expression = parse();
500 if (expression==null) {
501 throw new RuntimeException("expression expected");
504 result.add(expression);
506 token = scanner.scan();
507 } while (token instanceof CommaToken);
509 if (!(token instanceof RightParenthesisToken)) {
510 throw new RuntimeException(") or , expected");
516 private Object parseVariable() {
519 Object currentValue = valueMap;
523 token = scanner.scan();
524 if (token instanceof LeftSquareBraceToken) {
525 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
526 token = scanner.scan();
527 if (!(token instanceof RightSquareBraceToken))
528 throw new RuntimeException("] expected");
530 else if (token instanceof IdentifierToken) {
531 qualifier = ((IdentifierToken) token).getName();
534 throw new RuntimeException("fieldname or [ expected");
536 if (currentValue!=null) {
537 if (currentValue instanceof Map) {
538 currentValue = ((Map) currentValue).get(qualifier);
541 throw new RuntimeException("cannot reference into anything other than a map");
545 // throw? or allow null.null?
548 if (scanner.peek() instanceof PeriodToken)
561 private Object parseUntil(int aMaxOperatorLevel) {
562 Token token = scanner.peek();
565 if (token instanceof LeftParenthesisToken) {
568 token = scanner.peek();
569 if (!(token instanceof RightParenthesisToken))
570 throw new RuntimeException(") expected");
573 else if (isUnaryOperator(token)) {
575 value = parseUntil(unaryOperatorLevel(token));
576 value = expandOperatorExpression(token, value);
578 else if (token instanceof IdentifierToken) {
579 value = parseVariable();
581 else if (token instanceof LiteralToken) {
583 value = ((LiteralToken) token).getValue();
586 throw new RuntimeException("Expression expected");
588 token = scanner.peek();
590 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
594 if (isINOperator(token)) {
598 value2 = parseUntil(binaryOperatorLevel(token));
601 value = expandOperatorExpression(token, value, value2);
603 token = scanner.peek();
609 private static final int MAX_OPERATOR_LEVEL = 1000; // && || !
610 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
611 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
612 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
613 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
615 private int unaryOperatorLevel(Token aToken) {
616 if (aToken instanceof NOTToken)
617 return LOGICAL_OPERATOR_LEVEL;
618 else if (aToken instanceof MinusToken)
619 return ADDITION_OPERATOR_LEVEL;
621 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
624 private boolean isUnaryOperator(Token aToken) {
626 ((aToken instanceof NOTToken) ||
627 (aToken instanceof MinusToken));
630 private int binaryOperatorLevel(Token aToken) {
631 if ((aToken instanceof ANDToken) ||
632 (aToken instanceof ORToken))
633 return LOGICAL_OPERATOR_LEVEL;
635 if ((aToken instanceof EqualsToken) ||
636 (aToken instanceof EqualsNotToken) ||
637 (aToken instanceof LessThanOrEqualsToken) ||
638 (aToken instanceof LessThanToken) ||
639 (aToken instanceof GreaterThanOrEqualsToken) ||
640 (aToken instanceof GreaterThanToken) ||
641 isINOperator(aToken))
642 return COMPARISON_OPERATOR_LEVEL;
644 if ((aToken instanceof PlusToken) ||
645 (aToken instanceof ConcatenateToken) ||
646 (aToken instanceof MinusToken))
647 return ADDITION_OPERATOR_LEVEL;
649 if ((aToken instanceof TimesToken) ||
650 (aToken instanceof DivideToken))
651 return MULTIPLICATION_OPERATOR_LEVEL;
653 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
656 private boolean isINOperator(Token aToken) {
657 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
660 private boolean isBinaryOperator(Token aToken) {
662 (aToken instanceof ANDToken) ||
663 (aToken instanceof ORToken) ||
664 (aToken instanceof EqualsToken) ||
665 (aToken instanceof EqualsNotToken) ||
666 (aToken instanceof PlusToken) ||
667 (aToken instanceof TimesToken) ||
668 (aToken instanceof DivideToken) ||
669 (aToken instanceof MinusToken) ||
670 (aToken instanceof ConcatenateToken) ||
671 (aToken instanceof LessThanOrEqualsToken) ||
672 (aToken instanceof LessThanToken) ||
673 (aToken instanceof GreaterThanOrEqualsToken) ||
674 (aToken instanceof GreaterThanToken) ||
675 isINOperator(aToken);
678 private boolean interpretAsBoolean(Object aValue) {
679 if (aValue instanceof Boolean)
680 return ((Boolean) aValue).booleanValue();
685 private int interpretAsInteger(Object aValue) {
686 if (aValue instanceof Integer)
687 return ((Integer) aValue).intValue();
689 throw new RuntimeException("Not an integer");
692 private String interpretAsString(Object aValue) {
693 if (aValue instanceof String)
694 return (String) aValue;
695 if (aValue instanceof Integer)
696 return ((Integer) aValue).toString();
698 throw new RuntimeException("Not a string");
701 private Object expandOperatorExpression(Token aToken, Object aValue) {
702 if (aToken instanceof NOTToken)
703 return new Boolean(!interpretAsBoolean(aValue));
704 else if (aToken instanceof MinusToken)
705 return new Integer(-interpretAsInteger(aValue));
707 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
710 private boolean areEqual(Object aValue1, Object aValue2) {
711 if (aValue1==null || aValue2==null)
712 return (aValue1==null) && (aValue2==null);
714 return aValue1.equals(aValue2);
717 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
718 if (isINOperator(aToken)) {
719 if (!(aValue2 instanceof Set)) {
720 throw new RuntimeException("Internal error: set expected");
723 Iterator i = ((Set) aValue2).iterator();
725 while (i.hasNext()) {
726 if (areEqual(aValue1, i.next()))
730 return Boolean.FALSE;
733 if (aToken instanceof ANDToken)
734 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
735 if (aToken instanceof ORToken)
736 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
737 if (aToken instanceof EqualsToken) {
738 return new Boolean(areEqual(aValue1, aValue2));
740 if (aToken instanceof EqualsNotToken)
741 return new Boolean(!areEqual(aValue1, aValue2));
742 if (aToken instanceof PlusToken)
743 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
744 if (aToken instanceof TimesToken)
745 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
746 if (aToken instanceof DivideToken)
747 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
748 if (aToken instanceof MinusToken)
749 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
751 if (aToken instanceof ConcatenateToken)
752 return interpretAsString(aValue1) + interpretAsString(aValue1);
754 if (aToken instanceof LessThanOrEqualsToken)
755 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
756 if (aToken instanceof LessThanToken)
757 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
758 if (aToken instanceof GreaterThanOrEqualsToken)
759 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
760 if (aToken instanceof GreaterThanToken)
761 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
763 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
767 public static class ParameterExpanderExc extends Exc {
768 public ParameterExpanderExc(String msg, Object[] objects) {