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 public class ParameterExpander {
43 final static String NODE_SEPARATOR = ".";
44 final static char STRING_ESCAPE_CHARACTER = '\\';
46 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
52 i = aParts.iterator();
55 String part = (String) i.next();
57 if (!(node instanceof Map)) {
58 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
61 if (location.length()>0) {
62 location=location + NODE_SEPARATOR;
64 location = location + part;
66 newNode = ((Map) node).get(part);
69 if (aMakeIfNotPresent) {
70 newNode = new HashMap();
71 ((Map) node).put(part, newNode);
74 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
82 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
84 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
86 node = findNode(aKey, aMap, parts, false);
91 public static String findStringForKey(Map aMap, String aKey) throws Exception {
92 Object expandedValue = findValueForKey(aMap, aKey);
94 if (!(expandedValue instanceof String))
95 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
97 return (String) expandedValue;
100 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
101 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
103 String key = (String) parts.get(parts.size()-1);
104 parts.remove(parts.size()-1);
106 Object node=findNode(aKey, aMap, parts, true);
108 if (node instanceof Map) {
109 ((Map) node).put(key, aValue);
112 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
115 public static String expandExpression(Map aMap, String anExpression) throws Exception {
116 int previousPosition = 0;
118 int endOfExpressionPosition;
119 StringBuffer result = new StringBuffer();
121 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
122 result.append(anExpression.substring(previousPosition, position));
124 if (position>=anExpression.length()-1) {
125 result.append(anExpression.substring(position, anExpression.length()));
126 previousPosition=anExpression.length();
130 if (anExpression.charAt(position+1) == '{') {
131 endOfExpressionPosition=position+2;
132 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
133 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
134 char boundary = anExpression.charAt(endOfExpressionPosition);
136 endOfExpressionPosition++;
137 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
138 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
139 endOfExpressionPosition++;
140 endOfExpressionPosition++;
142 if (endOfExpressionPosition>=anExpression.length()) {
143 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
146 endOfExpressionPosition++;
148 if (endOfExpressionPosition<anExpression.length()) {
149 result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
150 previousPosition=endOfExpressionPosition+1;
153 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
158 previousPosition=position+2;
159 result.append(anExpression.charAt(position+1));
163 result.append(anExpression.substring(previousPosition, anExpression.length()));
165 return result.toString();
168 public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
169 Parser parser = new Parser(anExpression, aMap);
171 return parser.parseBoolean();
174 public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
175 Parser parser = new Parser(anExpression, aMap);
177 return parser.parseString();
180 public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
181 if (anExpression == null || anExpression.trim().equals(""))
184 return evaluateIntegerExpression(aMap, anExpression);
187 public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
188 Parser parser = new Parser(anExpression, aMap);
190 return parser.parseInteger();
193 public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
194 Parser parser = new Parser(anExpression, aMap);
196 return parser.parseWhole();
199 private static class Reader {
201 private int position;
203 public Reader(String aData) {
208 public Character peek() {
209 if (position<data.length()) {
210 return (new Character(data.charAt(position)));
216 public boolean hasNext() {
220 public Character getNext() {
221 Character result = peek();
229 public String getPositionString() {
230 return data.substring(0, position) + "<__>" + data.substring(position) ;
234 private static abstract class Token {
237 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
238 private static class LeftSquareBraceToken extends PunctuationToken {};
239 private static class RightSquareBraceToken extends PunctuationToken {};
240 private static class EqualsToken extends PunctuationToken {};
241 private static class EqualsNotToken extends PunctuationToken {};
242 private static class NOTToken extends PunctuationToken {};
243 private static class LeftParenthesisToken extends PunctuationToken {};
244 private static class RightParenthesisToken extends PunctuationToken {};
245 private static class CommaToken extends PunctuationToken {};
246 private static class PeriodToken extends PunctuationToken {};
247 private static class PlusToken extends PunctuationToken {};
248 private static class TimesToken extends PunctuationToken {};
249 private static class DivideToken extends PunctuationToken {};
250 private static class MinusToken extends PunctuationToken {};
251 private static class ConcatenateToken extends PunctuationToken {};
252 private static class LessThanOrEqualsToken extends PunctuationToken {};
253 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
254 private static class LessThanToken extends PunctuationToken {};
255 private static class GreaterThanToken extends PunctuationToken {};
258 private static class IdentifierToken extends Token {
261 public IdentifierToken(String aName) {
265 public String getName() {
271 private static class LiteralToken extends Token {
272 private Object value;
274 public LiteralToken(Object aValue) {
278 public Object getValue() {
283 private static class Scanner {
284 private Reader reader;
285 private Token nextToken;
286 private String positionString;
288 public Scanner(Reader aReader) {
291 positionString = reader.getPositionString();
294 public Token scanStringLiteral() {
295 StringBuffer result = new StringBuffer();
298 delimiter = reader.getNext();
300 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
301 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
303 if (reader.hasNext())
304 result.append(reader.getNext());
307 result.append(reader.getNext());
311 if (!reader.hasNext())
312 throw new RuntimeException("unterminated string");
316 return new LiteralToken(result.toString());
319 public String getPositionString() {
320 return positionString;
323 private Token scanNumber() {
324 StringBuffer result = new StringBuffer();
325 result.append(reader.getNext());
327 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
328 result.append(reader.getNext());
332 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
334 catch (NumberFormatException e) {
335 throw new RuntimeException("Invalid number: " + e.getMessage());
339 private Token scanIdentifierKeyword() {
340 StringBuffer result = new StringBuffer();
341 result.append(reader.getNext());
343 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
344 result.append(reader.getNext());
347 return new IdentifierToken(result.toString());
350 private Token scanPunctuation() {
353 c = reader.getNext();
355 switch(c.charValue()) {
356 case '[': return new LeftSquareBraceToken();
357 case ']': return new RightSquareBraceToken();
359 if (reader.hasNext() && reader.peek().charValue() == '=') {
361 return new EqualsToken();
364 throw new RuntimeException("Unknown character: '='");
368 if (reader.hasNext() && reader.peek().charValue() == '=') {
370 return new EqualsNotToken();
373 return new NOTToken();
376 case '(': return new LeftParenthesisToken ();
378 case ')': return new RightParenthesisToken ();
379 case ',': return new CommaToken ();
380 case '.': return new PeriodToken ();
382 if (reader.hasNext() && reader.peek().charValue() == '+') {
384 return new ConcatenateToken();
387 return new PlusToken ();
389 case '*': return new TimesToken ();
390 case '/': return new DivideToken ();
391 case '-': return new MinusToken ();
393 if (reader.hasNext() && reader.peek().charValue() == '=') {
395 return new LessThanOrEqualsToken();
398 return new LessThanToken();
402 if (reader.hasNext() && reader.peek().charValue() == '=') {
404 return new GreaterThanOrEqualsToken();
407 return new GreaterThanToken();
410 throw new RuntimeException("Unexpected character: "+c);
414 public void skipWhitespace() {
415 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
419 private boolean isIdentifierStart(char c) {
420 return Character.isLetter(c) || (c == '_');
423 private boolean isIdentifierRest(char c) {
424 return Character.isLetterOrDigit(c) || (c == '_');
427 private boolean isNumberStart(char c) {
428 return Character.isDigit(c);
431 private boolean isNumberRest(char c) {
432 return Character.isDigit(c);
435 public Token scanNext() {
440 if (reader.hasNext()) {
441 Character c = reader.peek();
443 switch(c.charValue()) {
446 result = scanStringLiteral();
450 if (isIdentifierStart(c.charValue())) {
451 result = scanIdentifierKeyword();
453 else if (isNumberStart(c.charValue())) {
454 result = scanNumber();
457 result = scanPunctuation();
467 public Token scan() {
468 Token result = peek();
470 positionString = reader.getPositionString();
475 public Token peek() {
476 if (nextToken==null) {
477 nextToken = scanNext();
483 public boolean hasToken() {
488 private static class Parser {
489 private Scanner scanner;
490 private Map valueMap;
492 public Parser(String anExpression, Map aValueMap) {
493 scanner = new Scanner(new Reader(anExpression));
494 valueMap = aValueMap;
497 public boolean parseBoolean() {
499 return interpretAsBoolean(parseWhole());
501 catch (Throwable t) {
502 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
506 public int parseInteger() {
508 return interpretAsInteger(parseWhole());
510 catch (Throwable t) {
511 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
515 public String parseString() {
517 return interpretAsString(parseWhole());
519 catch (Throwable t) {
520 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
524 private String getLocation() {
525 return scanner.getPositionString();
528 private Object parseWhole() {
529 Object result = parse();
531 if (scanner.hasToken()) {
532 throw new RuntimeException("Operator expected");
538 private Object parse() {
539 return parseUntil(MAX_OPERATOR_LEVEL);
542 private List parseList() {
545 List result = new Vector();
547 token = scanner.scan();
548 if (!(token instanceof LeftParenthesisToken)) {
549 throw new RuntimeException("( expected");
552 if (scanner.peek() instanceof RightParenthesisToken) {
558 expression = parse();
560 if (expression==null) {
561 throw new RuntimeException("expression expected");
564 result.add(expression);
566 token = scanner.scan();
568 while (token instanceof CommaToken);
570 if (!(token instanceof RightParenthesisToken)) {
571 throw new RuntimeException(") or , expected");
577 private Object parseVariable() {
580 Object currentValue = valueMap;
585 token = scanner.peek();
587 if (token instanceof LeftSquareBraceToken) {
589 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
590 token = scanner.scan();
591 if (!(token instanceof RightSquareBraceToken))
592 throw new RuntimeException("] expected");
594 if (currentValue instanceof Map) {
595 currentValue = ((Map) currentValue).get(qualifier);
598 throw new RuntimeException("cannot reference into anything other than a map ('"+qualifier+"')");
601 else if (token instanceof IdentifierToken) {
603 qualifier = ((IdentifierToken) token).getName();
605 if (currentValue instanceof Map) {
606 currentValue = ((Map) currentValue).get(qualifier);
609 throw new RuntimeException("cannot reference into anything other than a map ('"+qualifier+"')");
612 else if (token instanceof LeftParenthesisToken) {
613 if (currentValue instanceof Generator.GeneratorFunction) {
614 parameters = parseList();
616 currentValue = ((Generator.GeneratorFunction) currentValue).perform(parameters);
618 catch (GeneratorExc t) {
619 throw new RuntimeException(t.getMessage());
623 throw new RuntimeException("not a function");
626 throw new RuntimeException("fieldname or [ expected");
628 if (scanner.peek() instanceof PeriodToken ||
629 scanner.peek() instanceof LeftSquareBraceToken ||
630 scanner.peek() instanceof LeftParenthesisToken) {
633 if (scanner.peek() instanceof PeriodToken)
644 private Object parseUntil(int aMaxOperatorLevel) {
645 Token token = scanner.peek();
648 if (token instanceof LeftParenthesisToken) {
651 token = scanner.peek();
652 if (!(token instanceof RightParenthesisToken))
653 throw new RuntimeException(") expected");
656 else if (isUnaryOperator(token)) {
658 value = parseUntil(unaryOperatorLevel(token));
659 value = expandOperatorExpression(token, value);
661 else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
662 value = parseVariable();
664 else if (token instanceof LiteralToken) {
666 value = ((LiteralToken) token).getValue();
669 throw new RuntimeException("Expression expected");
671 token = scanner.peek();
673 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
677 if (isINOperator(token)) {
678 value2 = parseList();
681 value2 = parseUntil(binaryOperatorLevel(token));
684 value = expandOperatorExpression(token, value, value2);
686 token = scanner.peek();
692 private static final int MAX_OPERATOR_LEVEL = 1000; //
693 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
694 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
695 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
696 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
698 private int unaryOperatorLevel(Token aToken) {
699 if (aToken instanceof NOTToken)
700 return LOGICAL_OPERATOR_LEVEL;
701 else if (aToken instanceof MinusToken)
702 return ADDITION_OPERATOR_LEVEL;
704 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
707 private boolean isUnaryOperator(Token aToken) {
709 ((aToken instanceof NOTToken) ||
710 (aToken instanceof MinusToken));
713 private int binaryOperatorLevel(Token aToken) {
714 if (isANDOperator(aToken) ||
715 isOROperator(aToken))
716 return LOGICAL_OPERATOR_LEVEL;
718 if ((aToken instanceof EqualsToken) ||
719 (aToken instanceof EqualsNotToken) ||
720 (aToken instanceof LessThanOrEqualsToken) ||
721 (aToken instanceof LessThanToken) ||
722 (aToken instanceof GreaterThanOrEqualsToken) ||
723 (aToken instanceof GreaterThanToken) ||
724 isINOperator(aToken))
725 return COMPARISON_OPERATOR_LEVEL;
727 if ((aToken instanceof PlusToken) ||
728 (aToken instanceof ConcatenateToken) ||
729 (aToken instanceof MinusToken))
730 return ADDITION_OPERATOR_LEVEL;
732 if ((aToken instanceof TimesToken) ||
733 (aToken instanceof DivideToken))
734 return MULTIPLICATION_OPERATOR_LEVEL;
736 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
739 private boolean isINOperator(Token aToken) {
740 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
743 private boolean isANDOperator(Token aToken) {
744 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
747 private boolean isOROperator(Token aToken) {
748 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
751 private boolean isBinaryOperator(Token aToken) {
753 (aToken instanceof EqualsToken) ||
754 (aToken instanceof EqualsNotToken) ||
755 (aToken instanceof PlusToken) ||
756 (aToken instanceof TimesToken) ||
757 (aToken instanceof DivideToken) ||
758 (aToken instanceof MinusToken) ||
759 (aToken instanceof ConcatenateToken) ||
760 (aToken instanceof LessThanOrEqualsToken) ||
761 (aToken instanceof LessThanToken) ||
762 (aToken instanceof GreaterThanOrEqualsToken) ||
763 (aToken instanceof GreaterThanToken) ||
764 isINOperator(aToken) ||
765 isOROperator(aToken) ||
766 isANDOperator(aToken);
769 private boolean interpretAsBoolean(Object aValue) {
770 if (aValue instanceof Boolean)
771 return ((Boolean) aValue).booleanValue();
776 private int interpretAsInteger(Object aValue) {
777 if (aValue instanceof Integer)
778 return ((Integer) aValue).intValue();
780 if (aValue instanceof String) {
782 return Integer.parseInt((String) aValue);
784 catch (Throwable t) {
788 throw new RuntimeException("Not an integer");
791 private String interpretAsString(Object aValue) {
792 if (aValue instanceof String)
793 return (String) aValue;
794 if (aValue instanceof Integer)
795 return ((Integer) aValue).toString();
797 throw new RuntimeException("Not a string");
800 private Object expandOperatorExpression(Token aToken, Object aValue) {
801 if (aToken instanceof NOTToken)
802 return new Boolean(!interpretAsBoolean(aValue));
803 else if (aToken instanceof MinusToken)
804 return new Integer(-interpretAsInteger(aValue));
806 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
809 private boolean areEqual(Object aValue1, Object aValue2) {
810 if (aValue1==null || aValue2==null)
811 return (aValue1==null) && (aValue2==null);
813 return aValue1.equals(aValue2);
816 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
817 if (isINOperator(aToken)) {
818 if (!(aValue2 instanceof List)) {
819 throw new RuntimeException("Internal error: List expected");
822 Iterator i = ((List) aValue2).iterator();
824 while (i.hasNext()) {
825 if (areEqual(aValue1, i.next()))
829 return Boolean.FALSE;
832 if (isANDOperator(aToken))
833 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
834 if (isOROperator(aToken))
835 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
836 if (aToken instanceof EqualsToken) {
837 return new Boolean(areEqual(aValue1, aValue2));
839 if (aToken instanceof EqualsNotToken)
840 return new Boolean(!areEqual(aValue1, aValue2));
841 if (aToken instanceof PlusToken)
842 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
843 if (aToken instanceof TimesToken)
844 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
845 if (aToken instanceof DivideToken)
846 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
847 if (aToken instanceof MinusToken)
848 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
850 if (aToken instanceof ConcatenateToken)
851 return interpretAsString(aValue1) + interpretAsString(aValue2);
853 if (aToken instanceof LessThanOrEqualsToken)
854 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
855 if (aToken instanceof LessThanToken)
856 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
857 if (aToken instanceof GreaterThanOrEqualsToken)
858 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
859 if (aToken instanceof GreaterThanToken)
860 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
862 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
866 public static class ParameterExpanderExc extends Exc {
867 public ParameterExpanderExc(String msg, Object[] objects) {