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 java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.List;
38 import java.util.Vector;
40 import mir.generator.Generator;
41 import mir.generator.GeneratorExc;
44 public class ParameterExpander {
45 final static String NODE_SEPARATOR = ".";
46 final static char STRING_ESCAPE_CHARACTER = '\\';
48 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
54 i = aParts.iterator();
57 String part = (String) i.next();
59 if (!(node instanceof Map)) {
60 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
63 if (location.length()>0) {
64 location=location + NODE_SEPARATOR;
66 location = location + part;
68 newNode = ((Map) node).get(part);
71 if (aMakeIfNotPresent) {
72 newNode = new HashMap();
73 ((Map) node).put(part, newNode);
76 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
84 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
86 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
88 node = findNode(aKey, aMap, parts, false);
93 public static String findStringForKey(Map aMap, String aKey) throws Exception {
94 Object expandedValue = findValueForKey(aMap, aKey);
96 if (!(expandedValue instanceof String))
97 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
99 return (String) expandedValue;
102 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
103 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
105 String key = (String) parts.get(parts.size()-1);
106 parts.remove(parts.size()-1);
108 Object node=findNode(aKey, aMap, parts, true);
110 if (node instanceof Map) {
111 ((Map) node).put(key, aValue);
114 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
117 public static String expandExpression(Map aMap, String anExpression) throws Exception {
118 int previousPosition = 0;
120 int endOfExpressionPosition;
121 StringBuffer result = new StringBuffer();
123 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
124 result.append(anExpression.substring(previousPosition, position));
126 if (position>=anExpression.length()-1) {
127 result.append(anExpression.substring(position, anExpression.length()));
128 previousPosition=anExpression.length();
132 if (anExpression.charAt(position+1) == '{') {
133 endOfExpressionPosition=position+2;
134 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
135 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
136 char boundary = anExpression.charAt(endOfExpressionPosition);
138 endOfExpressionPosition++;
139 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
140 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
141 endOfExpressionPosition++;
142 endOfExpressionPosition++;
144 if (endOfExpressionPosition>=anExpression.length()) {
145 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
148 endOfExpressionPosition++;
150 if (endOfExpressionPosition<anExpression.length()) {
151 result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
152 previousPosition=endOfExpressionPosition+1;
155 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
160 previousPosition=position+2;
161 result.append(anExpression.charAt(position+1));
165 result.append(anExpression.substring(previousPosition, anExpression.length()));
167 return result.toString();
170 public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
171 Parser parser = new Parser(anExpression, aMap);
173 return parser.parseBoolean();
176 public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
177 Parser parser = new Parser(anExpression, aMap);
179 return parser.parseString();
182 public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
183 if (anExpression == null || anExpression.trim().equals(""))
186 return evaluateIntegerExpression(aMap, anExpression);
189 public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
190 Parser parser = new Parser(anExpression, aMap);
192 return parser.parseInteger();
195 public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
196 Parser parser = new Parser(anExpression, aMap);
198 return parser.parseWhole();
201 private static class Reader {
203 private int position;
205 public Reader(String aData) {
210 public Character peek() {
211 if (position<data.length()) {
212 return (new Character(data.charAt(position)));
218 public boolean hasNext() {
222 public Character getNext() {
223 Character result = peek();
231 public String getPositionString() {
232 return data.substring(0, position) + "<__>" + data.substring(position) ;
236 private static abstract class Token {
239 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
240 private static class LeftSquareBraceToken extends PunctuationToken {};
241 private static class RightSquareBraceToken extends PunctuationToken {};
242 private static class EqualsToken extends PunctuationToken {};
243 private static class EqualsNotToken extends PunctuationToken {};
244 private static class NOTToken extends PunctuationToken {};
245 private static class LeftParenthesisToken extends PunctuationToken {};
246 private static class RightParenthesisToken extends PunctuationToken {};
247 private static class CommaToken extends PunctuationToken {};
248 private static class PeriodToken extends PunctuationToken {};
249 private static class PlusToken extends PunctuationToken {};
250 private static class TimesToken extends PunctuationToken {};
251 private static class DivideToken extends PunctuationToken {};
252 private static class MinusToken extends PunctuationToken {};
253 private static class ConcatenateToken extends PunctuationToken {};
254 private static class LessThanOrEqualsToken extends PunctuationToken {};
255 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
256 private static class LessThanToken extends PunctuationToken {};
257 private static class GreaterThanToken extends PunctuationToken {};
260 private static class IdentifierToken extends Token {
263 public IdentifierToken(String aName) {
267 public String getName() {
273 private static class LiteralToken extends Token {
274 private Object value;
276 public LiteralToken(Object aValue) {
280 public Object getValue() {
285 private static class Scanner {
286 private Reader reader;
287 private Token nextToken;
288 private String positionString;
290 public Scanner(Reader aReader) {
293 positionString = reader.getPositionString();
296 public Token scanStringLiteral() {
297 StringBuffer result = new StringBuffer();
300 delimiter = reader.getNext();
302 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
303 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
305 if (reader.hasNext())
306 result.append(reader.getNext());
309 result.append(reader.getNext());
313 if (!reader.hasNext())
314 throw new RuntimeException("unterminated string");
318 return new LiteralToken(result.toString());
321 public String getPositionString() {
322 return positionString;
325 private Token scanNumber() {
326 StringBuffer result = new StringBuffer();
327 result.append(reader.getNext());
329 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
330 result.append(reader.getNext());
334 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
336 catch (NumberFormatException e) {
337 throw new RuntimeException("Invalid number: " + e.getMessage());
341 private Token scanIdentifierKeyword() {
342 StringBuffer result = new StringBuffer();
343 result.append(reader.getNext());
345 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
346 result.append(reader.getNext());
349 return new IdentifierToken(result.toString());
352 private Token scanPunctuation() {
355 c = reader.getNext();
357 switch(c.charValue()) {
358 case '[': return new LeftSquareBraceToken();
359 case ']': return new RightSquareBraceToken();
361 if (reader.hasNext() && reader.peek().charValue() == '=') {
363 return new EqualsToken();
366 throw new RuntimeException("Unknown character: '='");
370 if (reader.hasNext() && reader.peek().charValue() == '=') {
372 return new EqualsNotToken();
375 return new NOTToken();
378 case '(': return new LeftParenthesisToken ();
380 case ')': return new RightParenthesisToken ();
381 case ',': return new CommaToken ();
382 case '.': return new PeriodToken ();
384 if (reader.hasNext() && reader.peek().charValue() == '+') {
386 return new ConcatenateToken();
389 return new PlusToken ();
391 case '*': return new TimesToken ();
392 case '/': return new DivideToken ();
393 case '-': return new MinusToken ();
395 if (reader.hasNext() && reader.peek().charValue() == '=') {
397 return new LessThanOrEqualsToken();
400 return new LessThanToken();
404 if (reader.hasNext() && reader.peek().charValue() == '=') {
406 return new GreaterThanOrEqualsToken();
409 return new GreaterThanToken();
412 throw new RuntimeException("Unexpected character: "+c);
416 public void skipWhitespace() {
417 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
421 private boolean isIdentifierStart(char c) {
422 return Character.isLetter(c) || (c == '_');
425 private boolean isIdentifierRest(char c) {
426 return Character.isLetterOrDigit(c) || (c == '_');
429 private boolean isNumberStart(char c) {
430 return Character.isDigit(c);
433 private boolean isNumberRest(char c) {
434 return Character.isDigit(c);
437 public Token scanNext() {
442 if (reader.hasNext()) {
443 Character c = reader.peek();
445 switch(c.charValue()) {
448 result = scanStringLiteral();
452 if (isIdentifierStart(c.charValue())) {
453 result = scanIdentifierKeyword();
455 else if (isNumberStart(c.charValue())) {
456 result = scanNumber();
459 result = scanPunctuation();
469 public Token scan() {
470 Token result = peek();
472 positionString = reader.getPositionString();
477 public Token peek() {
478 if (nextToken==null) {
479 nextToken = scanNext();
485 public boolean hasToken() {
490 private static class Parser {
491 private Scanner scanner;
492 private Map valueMap;
494 public Parser(String anExpression, Map aValueMap) {
495 scanner = new Scanner(new Reader(anExpression));
496 valueMap = aValueMap;
499 public boolean parseBoolean() {
501 return interpretAsBoolean(parseWhole());
503 catch (Throwable t) {
504 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
508 public int parseInteger() {
510 return interpretAsInteger(parseWhole());
512 catch (Throwable t) {
513 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
517 public String parseString() {
519 return interpretAsString(parseWhole());
521 catch (Throwable t) {
522 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
526 private String getLocation() {
527 return scanner.getPositionString();
530 private Object parseWhole() {
531 Object result = parse();
533 if (scanner.hasToken()) {
534 throw new RuntimeException("Operator expected");
540 private Object parse() {
541 return parseUntil(MAX_OPERATOR_LEVEL);
544 private List parseList() {
547 List result = new Vector();
549 token = scanner.scan();
550 if (!(token instanceof LeftParenthesisToken)) {
551 throw new RuntimeException("( expected");
554 if (scanner.peek() instanceof RightParenthesisToken) {
560 expression = parse();
562 if (expression==null) {
563 throw new RuntimeException("expression expected");
566 result.add(expression);
568 token = scanner.scan();
570 while (token instanceof CommaToken);
572 if (!(token instanceof RightParenthesisToken)) {
573 throw new RuntimeException(") or , expected");
579 private Object parseVariable() {
582 Object currentValue = valueMap;
587 token = scanner.peek();
589 if (token instanceof LeftSquareBraceToken) {
591 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
592 token = scanner.scan();
593 if (!(token instanceof RightSquareBraceToken))
594 throw new RuntimeException("] expected");
596 if (currentValue instanceof Map) {
597 currentValue = ((Map) currentValue).get(qualifier);
600 throw new RuntimeException("cannot reference into anything other than a map ('"+qualifier+"')");
603 else if (token instanceof IdentifierToken) {
605 qualifier = ((IdentifierToken) token).getName();
607 if (currentValue instanceof Map) {
608 currentValue = ((Map) currentValue).get(qualifier);
611 throw new RuntimeException("cannot reference into anything other than a map ('"+qualifier+"')");
614 else if (token instanceof LeftParenthesisToken) {
615 if (currentValue instanceof Generator.GeneratorFunction) {
616 parameters = parseList();
618 currentValue = ((Generator.GeneratorFunction) currentValue).perform(parameters);
620 catch (GeneratorExc t) {
621 throw new RuntimeException(t.getMessage());
625 throw new RuntimeException("not a function");
628 throw new RuntimeException("fieldname or [ expected");
630 if (scanner.peek() instanceof PeriodToken ||
631 scanner.peek() instanceof LeftSquareBraceToken ||
632 scanner.peek() instanceof LeftParenthesisToken) {
635 if (scanner.peek() instanceof PeriodToken)
646 private Object parseUntil(int aMaxOperatorLevel) {
647 Token token = scanner.peek();
650 if (token instanceof LeftParenthesisToken) {
653 token = scanner.peek();
654 if (!(token instanceof RightParenthesisToken))
655 throw new RuntimeException(") expected");
658 else if (isUnaryOperator(token)) {
660 value = parseUntil(unaryOperatorLevel(token));
661 value = expandOperatorExpression(token, value);
663 else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
664 value = parseVariable();
666 else if (token instanceof LiteralToken) {
668 value = ((LiteralToken) token).getValue();
671 throw new RuntimeException("Expression expected");
673 token = scanner.peek();
675 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
679 if (isINOperator(token)) {
680 value2 = parseList();
683 value2 = parseUntil(binaryOperatorLevel(token));
686 value = expandOperatorExpression(token, value, value2);
688 token = scanner.peek();
694 private static final int MAX_OPERATOR_LEVEL = 1000; //
695 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
696 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
697 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
698 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
700 private int unaryOperatorLevel(Token aToken) {
701 if (aToken instanceof NOTToken)
702 return LOGICAL_OPERATOR_LEVEL;
703 else if (aToken instanceof MinusToken)
704 return ADDITION_OPERATOR_LEVEL;
706 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
709 private boolean isUnaryOperator(Token aToken) {
711 ((aToken instanceof NOTToken) ||
712 (aToken instanceof MinusToken));
715 private int binaryOperatorLevel(Token aToken) {
716 if (isANDOperator(aToken) ||
717 isOROperator(aToken))
718 return LOGICAL_OPERATOR_LEVEL;
720 if ((aToken instanceof EqualsToken) ||
721 (aToken instanceof EqualsNotToken) ||
722 (aToken instanceof LessThanOrEqualsToken) ||
723 (aToken instanceof LessThanToken) ||
724 (aToken instanceof GreaterThanOrEqualsToken) ||
725 (aToken instanceof GreaterThanToken) ||
726 isINOperator(aToken))
727 return COMPARISON_OPERATOR_LEVEL;
729 if ((aToken instanceof PlusToken) ||
730 (aToken instanceof ConcatenateToken) ||
731 (aToken instanceof MinusToken))
732 return ADDITION_OPERATOR_LEVEL;
734 if ((aToken instanceof TimesToken) ||
735 (aToken instanceof DivideToken))
736 return MULTIPLICATION_OPERATOR_LEVEL;
738 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
741 private boolean isINOperator(Token aToken) {
742 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
745 private boolean isANDOperator(Token aToken) {
746 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
749 private boolean isOROperator(Token aToken) {
750 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
753 private boolean isBinaryOperator(Token aToken) {
755 (aToken instanceof EqualsToken) ||
756 (aToken instanceof EqualsNotToken) ||
757 (aToken instanceof PlusToken) ||
758 (aToken instanceof TimesToken) ||
759 (aToken instanceof DivideToken) ||
760 (aToken instanceof MinusToken) ||
761 (aToken instanceof ConcatenateToken) ||
762 (aToken instanceof LessThanOrEqualsToken) ||
763 (aToken instanceof LessThanToken) ||
764 (aToken instanceof GreaterThanOrEqualsToken) ||
765 (aToken instanceof GreaterThanToken) ||
766 isINOperator(aToken) ||
767 isOROperator(aToken) ||
768 isANDOperator(aToken);
771 private boolean interpretAsBoolean(Object aValue) {
772 if (aValue instanceof Boolean)
773 return ((Boolean) aValue).booleanValue();
778 private int interpretAsInteger(Object aValue) {
779 if (aValue instanceof Integer)
780 return ((Integer) aValue).intValue();
782 if (aValue instanceof String) {
784 return Integer.parseInt((String) aValue);
786 catch (Throwable t) {
790 throw new RuntimeException("Not an integer");
793 private String interpretAsString(Object aValue) {
794 if (aValue instanceof String)
795 return (String) aValue;
796 if (aValue instanceof Integer)
797 return ((Integer) aValue).toString();
799 throw new RuntimeException("Not a string");
802 private Object expandOperatorExpression(Token aToken, Object aValue) {
803 if (aToken instanceof NOTToken)
804 return new Boolean(!interpretAsBoolean(aValue));
805 else if (aToken instanceof MinusToken)
806 return new Integer(-interpretAsInteger(aValue));
808 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
811 private boolean areEqual(Object aValue1, Object aValue2) {
812 if (aValue1==null || aValue2==null)
813 return (aValue1==null) && (aValue2==null);
815 return aValue1.equals(aValue2);
818 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
819 if (isINOperator(aToken)) {
820 if (!(aValue2 instanceof List)) {
821 throw new RuntimeException("Internal error: List expected");
824 Iterator i = ((List) aValue2).iterator();
826 while (i.hasNext()) {
827 if (areEqual(aValue1, i.next()))
831 return Boolean.FALSE;
834 if (isANDOperator(aToken))
835 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
836 if (isOROperator(aToken))
837 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
838 if (aToken instanceof EqualsToken) {
839 return new Boolean(areEqual(aValue1, aValue2));
841 if (aToken instanceof EqualsNotToken)
842 return new Boolean(!areEqual(aValue1, aValue2));
843 if (aToken instanceof PlusToken)
844 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
845 if (aToken instanceof TimesToken)
846 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
847 if (aToken instanceof DivideToken)
848 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
849 if (aToken instanceof MinusToken)
850 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
852 if (aToken instanceof ConcatenateToken)
853 return interpretAsString(aValue1) + interpretAsString(aValue2);
855 if (aToken instanceof LessThanOrEqualsToken)
856 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
857 if (aToken instanceof LessThanToken)
858 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
859 if (aToken instanceof GreaterThanOrEqualsToken)
860 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
861 if (aToken instanceof GreaterThanToken)
862 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
864 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
868 public static class ParameterExpanderExc extends Exc {
869 public ParameterExpanderExc(String msg, Object[] objects) {