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 the com.oreilly.servlet library, any library
22 * licensed under the Apache Software License, The Sun (tm) Java Advanced
23 * Imaging library (JAI), The Sun JIMI library (or with modified versions of
24 * the above that use the same license as the above), and distribute linked
25 * combinations including the two. You must obey the GNU General Public
26 * License in all respects for all of the code used other than the above
27 * mentioned libraries. If you modify this file, you may extend this exception
28 * to your version of the file, but you are not obligated to do so. If you do
29 * not wish to do so, delete this exception statement from your version.
34 import multex.Failure;
39 public class ParameterExpander {
40 final static String NODE_SEPARATOR = ".";
41 final static char STRING_ESCAPE_CHARACTER = '\\';
43 public static List splitString(String aString, String aSeparator) {
44 List result= new Vector();
45 int previousPosition = 0;
47 int endOfNamePosition;
49 while ((position = aString.indexOf(aSeparator, previousPosition))>=0) {
50 result.add(aString.substring(previousPosition, position));
51 previousPosition = position + aSeparator.length();
54 result.add(aString.substring(previousPosition, aString.length()));
59 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
65 i = aParts.iterator();
68 String part = (String) i.next();
70 if (!(node instanceof Map)) {
71 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
74 if (location.length()>0) {
75 location=location + NODE_SEPARATOR;
77 location = location + part;
79 newNode = ((Map) node).get(part);
82 if (aMakeIfNotPresent) {
83 newNode = new HashMap();
84 ((Map) node).put(part, newNode);
87 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
95 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
97 List parts = splitString(aKey, NODE_SEPARATOR);
99 node = findNode(aKey, aMap, parts, false);
104 public static String findStringForKey(Map aMap, String aKey) throws Exception {
105 Object expandedValue = findValueForKey(aMap, aKey);
107 if (!(expandedValue instanceof String))
108 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
110 return (String) expandedValue;
113 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
114 List parts = splitString(aKey, NODE_SEPARATOR);
116 String key = (String) parts.get(parts.size()-1);
117 parts.remove(parts.size()-1);
119 Object node=findNode(aKey, aMap, parts, true);
121 if (node instanceof Map) {
122 ((Map) node).put(key, aValue);
125 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
128 public static String expandExpression(Map aMap, String anExpression) throws Exception {
129 int previousPosition = 0;
131 int endOfExpressionPosition;
132 StringBuffer result = new StringBuffer();
134 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
135 result.append(anExpression.substring(previousPosition, position));
137 if (position>=anExpression.length()-1) {
138 result.append(anExpression.substring(position, anExpression.length()));
139 previousPosition=anExpression.length();
143 if (anExpression.charAt(position+1) == '{') {
144 endOfExpressionPosition=position+2;
145 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
146 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
147 char boundary = anExpression.charAt(endOfExpressionPosition);
149 endOfExpressionPosition++;
150 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
151 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
152 endOfExpressionPosition++;
153 endOfExpressionPosition++;
155 if (endOfExpressionPosition<anExpression.length()) {
156 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
159 endOfExpressionPosition++;
161 if (endOfExpressionPosition<anExpression.length()) {
162 result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
163 previousPosition=endOfExpressionPosition+1;
166 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
171 previousPosition=position+2;
172 result.append(anExpression.charAt(position+1));
176 result.append(anExpression.substring(previousPosition, anExpression.length()));
178 return result.toString();
181 public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
182 Parser parser = new Parser(anExpression, aMap);
184 return parser.parseBoolean();
187 public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
188 Parser parser = new Parser(anExpression, aMap);
190 return parser.parseString();
193 public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
194 if (anExpression == null || anExpression.trim().equals(""))
197 return evaluateIntegerExpression(aMap, anExpression);
200 public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
201 Parser parser = new Parser(anExpression, aMap);
203 return parser.parseInteger();
206 public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
207 Parser parser = new Parser(anExpression, aMap);
209 return parser.parseWhole();
212 private static class Reader {
214 private int position;
216 public Reader(String aData) {
221 public Character peek() {
222 if (position<data.length()) {
223 return (new Character(data.charAt(position)));
229 public boolean hasNext() {
233 public Character getNext() {
234 Character result = peek();
242 public String getPositionString() {
243 return data.substring(0, position) + "<__>" + data.substring(position) ;
247 private static abstract class Token {
250 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
251 private static class LeftSquareBraceToken extends PunctuationToken {};
252 private static class RightSquareBraceToken extends PunctuationToken {};
253 private static class EqualsToken extends PunctuationToken {};
254 private static class EqualsNotToken extends PunctuationToken {};
255 private static class NOTToken extends PunctuationToken {};
256 private static class LeftParenthesisToken extends PunctuationToken {};
257 private static class RightParenthesisToken extends PunctuationToken {};
258 private static class CommaToken extends PunctuationToken {};
259 private static class PeriodToken extends PunctuationToken {};
260 private static class PlusToken extends PunctuationToken {};
261 private static class TimesToken extends PunctuationToken {};
262 private static class DivideToken extends PunctuationToken {};
263 private static class MinusToken extends PunctuationToken {};
264 private static class ConcatenateToken extends PunctuationToken {};
265 private static class LessThanOrEqualsToken extends PunctuationToken {};
266 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
267 private static class LessThanToken extends PunctuationToken {};
268 private static class GreaterThanToken extends PunctuationToken {};
271 private static class IdentifierToken extends Token {
274 public IdentifierToken(String aName) {
278 public String getName() {
284 private static class LiteralToken extends Token {
285 private Object value;
287 public LiteralToken(Object aValue) {
291 public Object getValue() {
296 private static class Scanner {
297 private Reader reader;
298 private Token nextToken;
299 private String positionString;
301 public Scanner(Reader aReader) {
304 positionString = reader.getPositionString();
307 public Token scanStringLiteral() {
308 StringBuffer result = new StringBuffer();
311 delimiter = reader.getNext();
313 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
314 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
316 if (reader.hasNext())
317 result.append(reader.getNext());
320 result.append(reader.getNext());
324 if (!reader.hasNext())
325 throw new RuntimeException("unterminated string");
329 return new LiteralToken(result.toString());
332 public String getPositionString() {
333 return positionString;
336 private Token scanNumber() {
337 StringBuffer result = new StringBuffer();
338 result.append(reader.getNext());
340 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
341 result.append(reader.getNext());
345 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
347 catch (NumberFormatException e) {
348 throw new RuntimeException("Invalid number: " + e.getMessage());
352 private Token scanIdentifierKeyword() {
353 StringBuffer result = new StringBuffer();
354 result.append(reader.getNext());
356 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
357 result.append(reader.getNext());
360 return new IdentifierToken(result.toString());
363 private Token scanPunctuation() {
366 c = reader.getNext();
368 switch(c.charValue()) {
369 case '[': return new LeftSquareBraceToken();
370 case ']': return new RightSquareBraceToken();
372 if (reader.hasNext() && reader.peek().charValue() == '=') {
374 return new EqualsToken();
377 throw new RuntimeException("Unknown character: '='");
381 if (reader.hasNext() && reader.peek().charValue() == '=') {
383 return new EqualsNotToken();
386 return new NOTToken();
389 case '(': return new LeftParenthesisToken ();
391 case ')': return new RightParenthesisToken ();
392 case ',': return new CommaToken ();
393 case '.': return new PeriodToken ();
395 if (reader.hasNext() && reader.peek().charValue() == '+') {
397 return new ConcatenateToken();
400 return new PlusToken ();
402 case '*': return new TimesToken ();
403 case '/': return new DivideToken ();
404 case '-': return new MinusToken ();
406 if (reader.hasNext() && reader.peek().charValue() == '=') {
408 return new LessThanOrEqualsToken();
411 return new LessThanToken();
415 if (reader.hasNext() && reader.peek().charValue() == '=') {
417 return new GreaterThanOrEqualsToken();
420 return new GreaterThanToken();
423 throw new RuntimeException("Unexpected character: "+c);
427 public void skipWhitespace() {
428 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
432 private boolean isIdentifierStart(char c) {
433 return Character.isLetter(c) || (c == '_');
436 private boolean isIdentifierRest(char c) {
437 return Character.isLetterOrDigit(c) || (c == '_');
440 private boolean isNumberStart(char c) {
441 return Character.isDigit(c);
444 private boolean isNumberRest(char c) {
445 return Character.isDigit(c);
448 public Token scanNext() {
453 if (reader.hasNext()) {
454 Character c = reader.peek();
456 switch(c.charValue()) {
459 result = scanStringLiteral();
463 if (isIdentifierStart(c.charValue())) {
464 result = scanIdentifierKeyword();
466 else if (isNumberStart(c.charValue())) {
467 result = scanNumber();
470 result = scanPunctuation();
480 public Token scan() {
481 Token result = peek();
483 positionString = reader.getPositionString();
488 public Token peek() {
489 if (nextToken==null) {
490 nextToken = scanNext();
496 public boolean hasToken() {
501 private static class Parser {
502 private Scanner scanner;
503 private Map valueMap;
505 public Parser(String anExpression, Map aValueMap) {
506 scanner = new Scanner(new Reader(anExpression));
507 valueMap = aValueMap;
510 public boolean parseBoolean() {
512 return interpretAsBoolean(parseWhole());
514 catch (Throwable t) {
515 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
519 public int parseInteger() {
521 return interpretAsInteger(parseWhole());
523 catch (Throwable t) {
524 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
528 public String parseString() {
530 return interpretAsString(parseWhole());
532 catch (Throwable t) {
533 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
537 private String getLocation() {
538 return scanner.getPositionString();
541 private Object parseWhole() {
542 Object result = parse();
544 if (scanner.hasToken()) {
545 throw new RuntimeException("Operator expected");
551 private Object parse() {
552 return parseUntil(MAX_OPERATOR_LEVEL);
555 private Object parseSet() {
558 Set result = new HashSet();
560 token = scanner.scan();
561 if (!(token instanceof LeftParenthesisToken)) {
562 throw new RuntimeException("( expected after in keyword");
565 if (scanner.peek() instanceof RightParenthesisToken) {
571 expression = parse();
573 if (expression==null) {
574 throw new RuntimeException("expression expected");
577 result.add(expression);
579 token = scanner.scan();
581 while (token instanceof CommaToken);
583 if (!(token instanceof RightParenthesisToken)) {
584 throw new RuntimeException(") or , expected");
590 private Object parseVariable() {
593 Object currentValue = valueMap;
597 token = scanner.scan();
598 if (token instanceof LeftSquareBraceToken) {
599 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
600 token = scanner.scan();
601 if (!(token instanceof RightSquareBraceToken))
602 throw new RuntimeException("] expected");
604 else if (token instanceof IdentifierToken) {
605 qualifier = ((IdentifierToken) token).getName();
608 throw new RuntimeException("fieldname or [ expected");
610 if (currentValue!=null) {
611 if (currentValue instanceof Map) {
612 currentValue = ((Map) currentValue).get(qualifier);
615 throw new RuntimeException("cannot reference into anything other than a map");
619 // throw? or allow null.null?
622 if (scanner.peek() instanceof PeriodToken)
635 private Object parseUntil(int aMaxOperatorLevel) {
636 Token token = scanner.peek();
639 if (token instanceof LeftParenthesisToken) {
642 token = scanner.peek();
643 if (!(token instanceof RightParenthesisToken))
644 throw new RuntimeException(") expected");
647 else if (isUnaryOperator(token)) {
649 value = parseUntil(unaryOperatorLevel(token));
650 value = expandOperatorExpression(token, value);
652 else if (token instanceof IdentifierToken) {
653 value = parseVariable();
655 else if (token instanceof LiteralToken) {
657 value = ((LiteralToken) token).getValue();
660 throw new RuntimeException("Expression expected");
662 token = scanner.peek();
664 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
668 if (isINOperator(token)) {
672 value2 = parseUntil(binaryOperatorLevel(token));
675 value = expandOperatorExpression(token, value, value2);
677 token = scanner.peek();
683 private static final int MAX_OPERATOR_LEVEL = 1000; // && || !
684 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
685 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
686 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
687 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
689 private int unaryOperatorLevel(Token aToken) {
690 if (aToken instanceof NOTToken)
691 return LOGICAL_OPERATOR_LEVEL;
692 else if (aToken instanceof MinusToken)
693 return ADDITION_OPERATOR_LEVEL;
695 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
698 private boolean isUnaryOperator(Token aToken) {
700 ((aToken instanceof NOTToken) ||
701 (aToken instanceof MinusToken));
704 private int binaryOperatorLevel(Token aToken) {
705 if (isANDOperator(aToken) ||
706 isOROperator(aToken))
707 return LOGICAL_OPERATOR_LEVEL;
709 if ((aToken instanceof EqualsToken) ||
710 (aToken instanceof EqualsNotToken) ||
711 (aToken instanceof LessThanOrEqualsToken) ||
712 (aToken instanceof LessThanToken) ||
713 (aToken instanceof GreaterThanOrEqualsToken) ||
714 (aToken instanceof GreaterThanToken) ||
715 isINOperator(aToken))
716 return COMPARISON_OPERATOR_LEVEL;
718 if ((aToken instanceof PlusToken) ||
719 (aToken instanceof ConcatenateToken) ||
720 (aToken instanceof MinusToken))
721 return ADDITION_OPERATOR_LEVEL;
723 if ((aToken instanceof TimesToken) ||
724 (aToken instanceof DivideToken))
725 return MULTIPLICATION_OPERATOR_LEVEL;
727 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
730 private boolean isINOperator(Token aToken) {
731 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
734 private boolean isANDOperator(Token aToken) {
735 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
738 private boolean isOROperator(Token aToken) {
739 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
742 private boolean isBinaryOperator(Token aToken) {
744 (aToken instanceof EqualsToken) ||
745 (aToken instanceof EqualsNotToken) ||
746 (aToken instanceof PlusToken) ||
747 (aToken instanceof TimesToken) ||
748 (aToken instanceof DivideToken) ||
749 (aToken instanceof MinusToken) ||
750 (aToken instanceof ConcatenateToken) ||
751 (aToken instanceof LessThanOrEqualsToken) ||
752 (aToken instanceof LessThanToken) ||
753 (aToken instanceof GreaterThanOrEqualsToken) ||
754 (aToken instanceof GreaterThanToken) ||
755 isINOperator(aToken) ||
756 isOROperator(aToken) ||
757 isANDOperator(aToken);
760 private boolean interpretAsBoolean(Object aValue) {
761 if (aValue instanceof Boolean)
762 return ((Boolean) aValue).booleanValue();
767 private int interpretAsInteger(Object aValue) {
768 if (aValue instanceof Integer)
769 return ((Integer) aValue).intValue();
771 if (aValue instanceof String) {
773 return Integer.parseInt((String) aValue);
775 catch (Throwable t) {
779 throw new RuntimeException("Not an integer");
782 private String interpretAsString(Object aValue) {
783 if (aValue instanceof String)
784 return (String) aValue;
785 if (aValue instanceof Integer)
786 return ((Integer) aValue).toString();
788 throw new RuntimeException("Not a string");
791 private Object expandOperatorExpression(Token aToken, Object aValue) {
792 if (aToken instanceof NOTToken)
793 return new Boolean(!interpretAsBoolean(aValue));
794 else if (aToken instanceof MinusToken)
795 return new Integer(-interpretAsInteger(aValue));
797 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
800 private boolean areEqual(Object aValue1, Object aValue2) {
801 if (aValue1==null || aValue2==null)
802 return (aValue1==null) && (aValue2==null);
804 return aValue1.equals(aValue2);
807 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
808 if (isINOperator(aToken)) {
809 if (!(aValue2 instanceof Set)) {
810 throw new RuntimeException("Internal error: set expected");
813 Iterator i = ((Set) aValue2).iterator();
815 while (i.hasNext()) {
816 if (areEqual(aValue1, i.next()))
820 return Boolean.FALSE;
823 if (isANDOperator(aToken))
824 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
825 if (isOROperator(aToken))
826 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
827 if (aToken instanceof EqualsToken) {
828 return new Boolean(areEqual(aValue1, aValue2));
830 if (aToken instanceof EqualsNotToken)
831 return new Boolean(!areEqual(aValue1, aValue2));
832 if (aToken instanceof PlusToken)
833 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
834 if (aToken instanceof TimesToken)
835 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
836 if (aToken instanceof DivideToken)
837 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
838 if (aToken instanceof MinusToken)
839 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
841 if (aToken instanceof ConcatenateToken)
842 return interpretAsString(aValue1) + interpretAsString(aValue2);
844 if (aToken instanceof LessThanOrEqualsToken)
845 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
846 if (aToken instanceof LessThanToken)
847 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
848 if (aToken instanceof GreaterThanOrEqualsToken)
849 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
850 if (aToken instanceof GreaterThanToken)
851 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
853 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
857 public static class ParameterExpanderExc extends Exc {
858 public ParameterExpanderExc(String msg, Object[] objects) {