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;
38 import mir.generator.*;
40 public class ParameterExpander {
41 final static String NODE_SEPARATOR = ".";
42 final static char STRING_ESCAPE_CHARACTER = '\\';
44 private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
50 i = aParts.iterator();
53 String part = (String) i.next();
55 if (!(node instanceof Map)) {
56 throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
59 if (location.length()>0) {
60 location=location + NODE_SEPARATOR;
62 location = location + part;
64 newNode = ((Map) node).get(part);
67 if (aMakeIfNotPresent) {
68 newNode = new HashMap();
69 ((Map) node).put(part, newNode);
72 throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
80 public static Object findValueForKey(Map aMap, String aKey) throws Exception {
82 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
84 node = findNode(aKey, aMap, parts, false);
89 public static String findStringForKey(Map aMap, String aKey) throws Exception {
90 Object expandedValue = findValueForKey(aMap, aKey);
92 if (!(expandedValue instanceof String))
93 throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
95 return (String) expandedValue;
98 public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
99 List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
101 String key = (String) parts.get(parts.size()-1);
102 parts.remove(parts.size()-1);
104 Object node=findNode(aKey, aMap, parts, true);
106 if (node instanceof Map) {
107 ((Map) node).put(key, aValue);
110 throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
113 public static String expandExpression(Map aMap, String anExpression) throws Exception {
114 int previousPosition = 0;
116 int endOfExpressionPosition;
117 StringBuffer result = new StringBuffer();
119 while ((position=anExpression.indexOf("$", previousPosition))>=0) {
120 result.append(anExpression.substring(previousPosition, position));
122 if (position>=anExpression.length()-1) {
123 result.append(anExpression.substring(position, anExpression.length()));
124 previousPosition=anExpression.length();
128 if (anExpression.charAt(position+1) == '{') {
129 endOfExpressionPosition=position+2;
130 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
131 if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
132 char boundary = anExpression.charAt(endOfExpressionPosition);
134 endOfExpressionPosition++;
135 while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
136 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
137 endOfExpressionPosition++;
138 endOfExpressionPosition++;
140 if (endOfExpressionPosition<anExpression.length()) {
141 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
144 endOfExpressionPosition++;
146 if (endOfExpressionPosition<anExpression.length()) {
147 result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
148 previousPosition=endOfExpressionPosition+1;
151 throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
156 previousPosition=position+2;
157 result.append(anExpression.charAt(position+1));
161 result.append(anExpression.substring(previousPosition, anExpression.length()));
163 return result.toString();
166 public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
167 Parser parser = new Parser(anExpression, aMap);
169 return parser.parseBoolean();
172 public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
173 Parser parser = new Parser(anExpression, aMap);
175 return parser.parseString();
178 public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
179 if (anExpression == null || anExpression.trim().equals(""))
182 return evaluateIntegerExpression(aMap, anExpression);
185 public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
186 Parser parser = new Parser(anExpression, aMap);
188 return parser.parseInteger();
191 public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
192 Parser parser = new Parser(anExpression, aMap);
194 return parser.parseWhole();
197 private static class Reader {
199 private int position;
201 public Reader(String aData) {
206 public Character peek() {
207 if (position<data.length()) {
208 return (new Character(data.charAt(position)));
214 public boolean hasNext() {
218 public Character getNext() {
219 Character result = peek();
227 public String getPositionString() {
228 return data.substring(0, position) + "<__>" + data.substring(position) ;
232 private static abstract class Token {
235 public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
236 private static class LeftSquareBraceToken extends PunctuationToken {};
237 private static class RightSquareBraceToken extends PunctuationToken {};
238 private static class EqualsToken extends PunctuationToken {};
239 private static class EqualsNotToken extends PunctuationToken {};
240 private static class NOTToken extends PunctuationToken {};
241 private static class LeftParenthesisToken extends PunctuationToken {};
242 private static class RightParenthesisToken extends PunctuationToken {};
243 private static class CommaToken extends PunctuationToken {};
244 private static class PeriodToken extends PunctuationToken {};
245 private static class PlusToken extends PunctuationToken {};
246 private static class TimesToken extends PunctuationToken {};
247 private static class DivideToken extends PunctuationToken {};
248 private static class MinusToken extends PunctuationToken {};
249 private static class ConcatenateToken extends PunctuationToken {};
250 private static class LessThanOrEqualsToken extends PunctuationToken {};
251 private static class GreaterThanOrEqualsToken extends PunctuationToken {};
252 private static class LessThanToken extends PunctuationToken {};
253 private static class GreaterThanToken extends PunctuationToken {};
256 private static class IdentifierToken extends Token {
259 public IdentifierToken(String aName) {
263 public String getName() {
269 private static class LiteralToken extends Token {
270 private Object value;
272 public LiteralToken(Object aValue) {
276 public Object getValue() {
281 private static class Scanner {
282 private Reader reader;
283 private Token nextToken;
284 private String positionString;
286 public Scanner(Reader aReader) {
289 positionString = reader.getPositionString();
292 public Token scanStringLiteral() {
293 StringBuffer result = new StringBuffer();
296 delimiter = reader.getNext();
298 while (reader.hasNext() && !reader.peek().equals(delimiter)) {
299 if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
301 if (reader.hasNext())
302 result.append(reader.getNext());
305 result.append(reader.getNext());
309 if (!reader.hasNext())
310 throw new RuntimeException("unterminated string");
314 return new LiteralToken(result.toString());
317 public String getPositionString() {
318 return positionString;
321 private Token scanNumber() {
322 StringBuffer result = new StringBuffer();
323 result.append(reader.getNext());
325 while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
326 result.append(reader.getNext());
330 return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
332 catch (NumberFormatException e) {
333 throw new RuntimeException("Invalid number: " + e.getMessage());
337 private Token scanIdentifierKeyword() {
338 StringBuffer result = new StringBuffer();
339 result.append(reader.getNext());
341 while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
342 result.append(reader.getNext());
345 return new IdentifierToken(result.toString());
348 private Token scanPunctuation() {
351 c = reader.getNext();
353 switch(c.charValue()) {
354 case '[': return new LeftSquareBraceToken();
355 case ']': return new RightSquareBraceToken();
357 if (reader.hasNext() && reader.peek().charValue() == '=') {
359 return new EqualsToken();
362 throw new RuntimeException("Unknown character: '='");
366 if (reader.hasNext() && reader.peek().charValue() == '=') {
368 return new EqualsNotToken();
371 return new NOTToken();
374 case '(': return new LeftParenthesisToken ();
376 case ')': return new RightParenthesisToken ();
377 case ',': return new CommaToken ();
378 case '.': return new PeriodToken ();
380 if (reader.hasNext() && reader.peek().charValue() == '+') {
382 return new ConcatenateToken();
385 return new PlusToken ();
387 case '*': return new TimesToken ();
388 case '/': return new DivideToken ();
389 case '-': return new MinusToken ();
391 if (reader.hasNext() && reader.peek().charValue() == '=') {
393 return new LessThanOrEqualsToken();
396 return new LessThanToken();
400 if (reader.hasNext() && reader.peek().charValue() == '=') {
402 return new GreaterThanOrEqualsToken();
405 return new GreaterThanToken();
408 throw new RuntimeException("Unexpected character: "+c);
412 public void skipWhitespace() {
413 while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
417 private boolean isIdentifierStart(char c) {
418 return Character.isLetter(c) || (c == '_');
421 private boolean isIdentifierRest(char c) {
422 return Character.isLetterOrDigit(c) || (c == '_');
425 private boolean isNumberStart(char c) {
426 return Character.isDigit(c);
429 private boolean isNumberRest(char c) {
430 return Character.isDigit(c);
433 public Token scanNext() {
438 if (reader.hasNext()) {
439 Character c = reader.peek();
441 switch(c.charValue()) {
444 result = scanStringLiteral();
448 if (isIdentifierStart(c.charValue())) {
449 result = scanIdentifierKeyword();
451 else if (isNumberStart(c.charValue())) {
452 result = scanNumber();
455 result = scanPunctuation();
465 public Token scan() {
466 Token result = peek();
468 positionString = reader.getPositionString();
473 public Token peek() {
474 if (nextToken==null) {
475 nextToken = scanNext();
481 public boolean hasToken() {
486 private static class Parser {
487 private Scanner scanner;
488 private Map valueMap;
490 public Parser(String anExpression, Map aValueMap) {
491 scanner = new Scanner(new Reader(anExpression));
492 valueMap = aValueMap;
495 public boolean parseBoolean() {
497 return interpretAsBoolean(parseWhole());
499 catch (Throwable t) {
500 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
504 public int parseInteger() {
506 return interpretAsInteger(parseWhole());
508 catch (Throwable t) {
509 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
513 public String parseString() {
515 return interpretAsString(parseWhole());
517 catch (Throwable t) {
518 throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
522 private String getLocation() {
523 return scanner.getPositionString();
526 private Object parseWhole() {
527 Object result = parse();
529 if (scanner.hasToken()) {
530 throw new RuntimeException("Operator expected");
536 private Object parse() {
537 return parseUntil(MAX_OPERATOR_LEVEL);
540 private List parseList() {
543 List result = new Vector();
545 token = scanner.scan();
546 if (!(token instanceof LeftParenthesisToken)) {
547 throw new RuntimeException("( expected");
550 if (scanner.peek() instanceof RightParenthesisToken) {
556 expression = parse();
558 if (expression==null) {
559 throw new RuntimeException("expression expected");
562 result.add(expression);
564 token = scanner.scan();
566 while (token instanceof CommaToken);
568 if (!(token instanceof RightParenthesisToken)) {
569 throw new RuntimeException(") or , expected");
575 private Object parseVariable() {
578 Object currentValue = valueMap;
583 token = scanner.scan();
584 if (token instanceof LeftSquareBraceToken) {
585 qualifier = parseUntil(MAX_OPERATOR_LEVEL);
586 token = scanner.scan();
587 if (!(token instanceof RightSquareBraceToken))
588 throw new RuntimeException("] expected");
590 if (currentValue instanceof Map) {
591 currentValue = ((Map) currentValue).get(qualifier);
594 throw new RuntimeException("cannot reference into anything other than a map");
597 else if (token instanceof IdentifierToken) {
598 qualifier = ((IdentifierToken) token).getName();
600 if (currentValue instanceof Map) {
601 currentValue = ((Map) currentValue).get(qualifier);
604 throw new RuntimeException("cannot reference into anything other than a map");
607 else if (token instanceof LeftParenthesisToken) {
608 if (currentValue instanceof Generator.GeneratorFunction) {
609 parameters = parseList();
611 currentValue = ((Generator.GeneratorFunction) currentValue).perform(parameters);
613 catch (GeneratorExc t) {
614 throw new RuntimeException(t.getMessage());
618 throw new RuntimeException("not a function");
621 throw new RuntimeException("fieldname or [ expected");
623 if (scanner.peek() instanceof PeriodToken ||
624 scanner.peek() instanceof LeftSquareBraceToken ||
625 scanner.peek() instanceof LeftParenthesisToken) {
628 if (scanner.peek() instanceof PeriodToken)
639 private Object parseUntil(int aMaxOperatorLevel) {
640 Token token = scanner.peek();
643 if (token instanceof LeftParenthesisToken) {
646 token = scanner.peek();
647 if (!(token instanceof RightParenthesisToken))
648 throw new RuntimeException(") expected");
651 else if (isUnaryOperator(token)) {
653 value = parseUntil(unaryOperatorLevel(token));
654 value = expandOperatorExpression(token, value);
656 else if (token instanceof IdentifierToken) {
657 value = parseVariable();
659 else if (token instanceof LiteralToken) {
661 value = ((LiteralToken) token).getValue();
664 throw new RuntimeException("Expression expected");
666 token = scanner.peek();
668 while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
672 if (isINOperator(token)) {
673 value2 = parseList();
676 value2 = parseUntil(binaryOperatorLevel(token));
679 value = expandOperatorExpression(token, value, value2);
681 token = scanner.peek();
687 private static final int MAX_OPERATOR_LEVEL = 1000; // && || !
688 private static final int LOGICAL_OPERATOR_LEVEL = 5; // && || !
689 private static final int COMPARISON_OPERATOR_LEVEL = 4; // == <= >= in < >
690 private static final int ADDITION_OPERATOR_LEVEL = 3; // + - &
691 private static final int MULTIPLICATION_OPERATOR_LEVEL = 2; // * /
693 private int unaryOperatorLevel(Token aToken) {
694 if (aToken instanceof NOTToken)
695 return LOGICAL_OPERATOR_LEVEL;
696 else if (aToken instanceof MinusToken)
697 return ADDITION_OPERATOR_LEVEL;
699 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
702 private boolean isUnaryOperator(Token aToken) {
704 ((aToken instanceof NOTToken) ||
705 (aToken instanceof MinusToken));
708 private int binaryOperatorLevel(Token aToken) {
709 if (isANDOperator(aToken) ||
710 isOROperator(aToken))
711 return LOGICAL_OPERATOR_LEVEL;
713 if ((aToken instanceof EqualsToken) ||
714 (aToken instanceof EqualsNotToken) ||
715 (aToken instanceof LessThanOrEqualsToken) ||
716 (aToken instanceof LessThanToken) ||
717 (aToken instanceof GreaterThanOrEqualsToken) ||
718 (aToken instanceof GreaterThanToken) ||
719 isINOperator(aToken))
720 return COMPARISON_OPERATOR_LEVEL;
722 if ((aToken instanceof PlusToken) ||
723 (aToken instanceof ConcatenateToken) ||
724 (aToken instanceof MinusToken))
725 return ADDITION_OPERATOR_LEVEL;
727 if ((aToken instanceof TimesToken) ||
728 (aToken instanceof DivideToken))
729 return MULTIPLICATION_OPERATOR_LEVEL;
731 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
734 private boolean isINOperator(Token aToken) {
735 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
738 private boolean isANDOperator(Token aToken) {
739 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
742 private boolean isOROperator(Token aToken) {
743 return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
746 private boolean isBinaryOperator(Token aToken) {
748 (aToken instanceof EqualsToken) ||
749 (aToken instanceof EqualsNotToken) ||
750 (aToken instanceof PlusToken) ||
751 (aToken instanceof TimesToken) ||
752 (aToken instanceof DivideToken) ||
753 (aToken instanceof MinusToken) ||
754 (aToken instanceof ConcatenateToken) ||
755 (aToken instanceof LessThanOrEqualsToken) ||
756 (aToken instanceof LessThanToken) ||
757 (aToken instanceof GreaterThanOrEqualsToken) ||
758 (aToken instanceof GreaterThanToken) ||
759 isINOperator(aToken) ||
760 isOROperator(aToken) ||
761 isANDOperator(aToken);
764 private boolean interpretAsBoolean(Object aValue) {
765 if (aValue instanceof Boolean)
766 return ((Boolean) aValue).booleanValue();
771 private int interpretAsInteger(Object aValue) {
772 if (aValue instanceof Integer)
773 return ((Integer) aValue).intValue();
775 if (aValue instanceof String) {
777 return Integer.parseInt((String) aValue);
779 catch (Throwable t) {
783 throw new RuntimeException("Not an integer");
786 private String interpretAsString(Object aValue) {
787 if (aValue instanceof String)
788 return (String) aValue;
789 if (aValue instanceof Integer)
790 return ((Integer) aValue).toString();
792 throw new RuntimeException("Not a string");
795 private Object expandOperatorExpression(Token aToken, Object aValue) {
796 if (aToken instanceof NOTToken)
797 return new Boolean(!interpretAsBoolean(aValue));
798 else if (aToken instanceof MinusToken)
799 return new Integer(-interpretAsInteger(aValue));
801 throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
804 private boolean areEqual(Object aValue1, Object aValue2) {
805 if (aValue1==null || aValue2==null)
806 return (aValue1==null) && (aValue2==null);
808 return aValue1.equals(aValue2);
811 private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
812 if (isINOperator(aToken)) {
813 if (!(aValue2 instanceof List)) {
814 throw new RuntimeException("Internal error: List expected");
817 Iterator i = ((List) aValue2).iterator();
819 while (i.hasNext()) {
820 if (areEqual(aValue1, i.next()))
824 return Boolean.FALSE;
827 if (isANDOperator(aToken))
828 return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
829 if (isOROperator(aToken))
830 return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
831 if (aToken instanceof EqualsToken) {
832 return new Boolean(areEqual(aValue1, aValue2));
834 if (aToken instanceof EqualsNotToken)
835 return new Boolean(!areEqual(aValue1, aValue2));
836 if (aToken instanceof PlusToken)
837 return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
838 if (aToken instanceof TimesToken)
839 return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
840 if (aToken instanceof DivideToken)
841 return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
842 if (aToken instanceof MinusToken)
843 return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
845 if (aToken instanceof ConcatenateToken)
846 return interpretAsString(aValue1) + interpretAsString(aValue2);
848 if (aToken instanceof LessThanOrEqualsToken)
849 return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
850 if (aToken instanceof LessThanToken)
851 return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
852 if (aToken instanceof GreaterThanOrEqualsToken)
853 return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
854 if (aToken instanceof GreaterThanToken)
855 return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
857 throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
861 public static class ParameterExpanderExc extends Exc {
862 public ParameterExpanderExc(String msg, Object[] objects) {