/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.expression; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.el.ELException; import javax.el.ExpressionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; import de.odysseus.el.ExpressionFactoryImpl; import de.odysseus.el.util.SimpleContext; /** * Implementation of {@link UserExpressionParser} using the EL library. An augmented EL * grammar is available that supports a sequence of expressions and an "IF" construct. */ public class ELExpressionParser extends UserExpressionParser { private static final Logger s_logger = LoggerFactory.getLogger(ELExpressionParser.class); private static final Pattern s_if = Pattern.compile("^\\s*if\\s*\\("); private final ExpressionFactory _factory; private final SimpleContext _context; public ELExpressionParser() { _factory = new ExpressionFactoryImpl(); _context = new SimpleContext(); } protected ExpressionFactory getFactory() { return _factory; } protected SimpleContext getContext() { return _context; } @Override public void setConstant(final String name, final Object value) { getContext().getVariableMapper().setVariable(name, getFactory().createValueExpression(value, Object.class)); } @Override public void setFunction(final String object, final String name, final Method method) { getContext().setFunction(object, name, method); } /** * Parse the EL expression by wrapping it in "${...}". Note that a single equals sign (assignment) is converted * to the double equal comparison operation. I.e. "x=4" gets parsed as "${x==4}". * * @param fragment text expression * @return the parsed expression */ private UserExpression elParse(final String fragment) { // TODO: escape the string if it contains "${}" characters StringBuilder sb = new StringBuilder(fragment.length() + 10); sb.append("${"); int state = 0; for (int i = 0; i < fragment.length(); i++) { final char c = fragment.charAt(i); switch (state) { case 0: sb.append(c); switch (c) { case '=': state = 1; break; case '\"': state = 2; break; case '\'': state = 3; break; case '!': case '<': case '>': state = 4; break; } break; case 1: switch (c) { case '=': sb.append('='); state = 0; break; case '\"': sb.append("=\""); state = 2; break; case '\'': sb.append("='"); state = 3; break; default: sb.append('='); sb.append(c); state = 0; break; } break; case 2: sb.append(c); switch (c) { case '\"': state = 0; break; case '\\': i++; if (i < fragment.length()) { sb.append(fragment.charAt(i)); } break; } break; case 3: sb.append(c); switch (c) { case '\'': state = 0; break; case '\\': i++; if (i < fragment.length()) { sb.append(fragment.charAt(i)); } break; } break; case 4: sb.append(c); switch (c) { case '\"': state = 2; break; case '\'': state = 3; break; default: state = 0; break; } break; } } sb.append('}'); s_logger.debug("Evaluating {}", sb); try { return new ELExpression(this, getFactory().createValueExpression(getContext(), sb.toString(), Object.class)); } catch (ELException e) { s_logger.warn("EL exception = {}", e.getMessage()); throw new IllegalArgumentException(fragment); } } private static int findEndQuote(final String str, final int i) { for (int j = i; j < str.length(); j++) { switch (str.charAt(j)) { case '\"': return j; case '\\': j++; break; } } return -1; } private static int findEndApostrophe(final String str, final int i) { for (int j = i; j < str.length(); j++) { switch (str.charAt(j)) { case '\'': return j; case '\\': j++; break; } } return -1; } private static int findCloseSquareBracket(final String str, final int i) { for (int j = i; j < str.length(); j++) { switch (str.charAt(j)) { case ']': return j; case '(': j = findCloseBracket(str, j + 1); break; case '\"': j = findEndQuote(str, j + 1); break; case '\'': j = findEndApostrophe(str, j + 1); break; } if (j == -1) { break; } } return -1; } private static int findCloseBracket(final String str, final int i) { for (int j = i; j < str.length(); j++) { switch (str.charAt(j)) { case '[': j = findCloseSquareBracket(str, j + 1); break; case '(': j = findCloseBracket(str, j + 1); break; case ')': return j; case '\"': j = findEndQuote(str, j + 1); break; case '\'': j = findEndApostrophe(str, j + 1); break; } if (j == -1) { break; } } return -1; } private static int findSemiColon(final String str, final int i) { for (int j = i; j < str.length(); j++) { switch (str.charAt(j)) { case ';': return j; case '\"': j = findEndQuote(str, j + 1); break; case '\'': j = findEndApostrophe(str, j + 1); break; } if (j == -1) { break; } } return -1; } private Pair<UserExpression, String> ueParse(final String source) { final Matcher m = s_if.matcher(source); if (m.find()) { final int openBracket = m.end() - 1; final int closeBracket = findCloseBracket(source, openBracket + 1); if (closeBracket < 0) { throw new IllegalArgumentException("No closing bracket for IF construct in " + source); } final UserExpression condition = elParse(source.substring(openBracket + 1, closeBracket)); final int semiColon = findSemiColon(source, closeBracket + 1); final UserExpression operation; final String tail; if (semiColon != -1) { operation = elParse(source.substring(closeBracket + 1, semiColon)); tail = source.substring(semiColon + 1); } else { operation = elParse(source.substring(closeBracket + 1)); tail = ""; } UserExpression expr = new IfExpression(condition, operation); return Pairs.of(expr, tail); } else { final int semiColon = findSemiColon(source, 0); if (semiColon == -1) { return Pairs.of(elParse(source), ""); } else { return Pairs.of(elParse(source.substring(0, semiColon)), source.substring(semiColon + 1)); } } } @Override public UserExpression parse(final String source) { Pair<UserExpression, String> parsed = ueParse(source.trim()); if (parsed.getSecond().length() == 0) { return parsed.getFirst(); } List<UserExpression> exprs = new LinkedList<UserExpression>(); exprs.add(parsed.getFirst()); do { parsed = ueParse(parsed.getSecond()); exprs.add(parsed.getFirst()); } while (parsed.getSecond().length() > 0); return new FirstValidExpression(exprs); } }