package edu.stanford.nlp.ling.tokensregex.types; import edu.stanford.nlp.util.logging.Redwood; import edu.stanford.nlp.ling.tokensregex.Env; import edu.stanford.nlp.ling.tokensregex.EnvLookup; import edu.stanford.nlp.ling.tokensregex.SequenceMatchResult; import edu.stanford.nlp.util.CoreMap; import edu.stanford.nlp.util.MetaClass; import edu.stanford.nlp.util.Pair; import edu.stanford.nlp.util.StringUtils; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.regex.MatchResult; import java.util.regex.Pattern; /** * Various implementations of the Expression interface, which is * used for specifying an "action" or "result" in TokensRegex extraction rules. * Expressions are made up of identifiers, literals (numbers, strings "I'm a string", TRUE, FALSE), * function calls ( FUNC(args) ). * </p> * * After a pattern has been matched, we can access the capture groups using one of the following methods: * <p> * <table> * <tr><th>Field</th><th>Description</th></tr> * <tr><th colspan="2">Accessing captured groups as list of tokens</th></tr> * <tr><td>$n</td><td>Capture group (as list of tokens) corresponding to the variable <code>$n</code>. * If <code>n</code> is a integer, then the n-th captured group. Capture group 0 is the entire matched expression. * Otherwise, if <code>n</code> is a string, then the captured group with name <code>n</code>.</td></tr> * <tr><td>$n[i]</td><td>The i-th token of the captured group <code>$n</code>. * Use negative indices to count from the end of the list (e.g. -1 is the last token).</td></tr> * <tr><td>$n[i].key</td><td>The value of annotation <code>key</code> of the i-th token of the captured group <code>$n</code>.</td></tr> * <tr><th colspan="2">Accessing captured groups as MatchedGroupInfo</th></tr> * <tr><td>$$n</td><td>Capture group (as MatchedGroupInfo) corresponding to the variable <code>$n</code>. * Use to get the associated value of the group and any embedded capture groups. * If <code>n</code> is a integer, then the n-th captured group. Capture group 0 is the entire matched expression. * Otherwise, if <code>n</code> is a string, then the captured group with name <code>n</code>.</td></tr> * <tr><td>$$n.text</td><td>Text of the capture group <code>n</code>.</td></tr> * <tr><td>$$n.nodes</td><td>Tokens of the capture group <code>n</code> (this is equivalent to <code>$n</code>).</td></tr> * <tr><td>$$n.value</td><td>Value associated with capture group <code>n</code>.</td></tr> * <tr><td>$$n.matchResults</td><td>Additional match results associated with capture group <code>n</code>. * Use to get embedded capture groups. For instance, when the TokensRegex <code>/(\d\d)-(\d\d)/</code> is matched * against the sentence "the score was 10-12", <code>$$0.text</code> will be "10-12" and * <code>$$0.matchResults[0].word.group(1)</code> will be "10".</td></tr> * </table> * </p> * * <p> * The following functions are supported: * <table> * <tr><th>Function</th><th>Description</th></tr> * <tr><td><code>Annotate(CoreMap, field, value)</td><td>Annotates the CoreMap with specified field=value</td></tr> * <tr><td><code>Aggregate(function, initialValue,...)</td><td>Aggregates values using function (like fold)</td></tr> * <tr><td><code>Split(CoreMap, delimRegex, includeMatched)</td><td>Split one CoreMap into smaller coremaps using the specified delimRegex on the text of the CoreMap. * If includeMatched is true, pieces that matches the delimRegex are included in the final list of CoreMaps</td></tr> * <tr><th colspan="2">Tagging functions</th></tr> * <tr><td><code>Tag(CoreMap or List<CoreMap>, tag, value)<br/>VTag(Value,tag,value)</code></td><td>Sets a temporary tag on the CoreMap(s) or Value</td></tr> * <tr><td><code>GetTag(CoreMap or List<CoreMap>, tag)<br/>GetVTag(Value,tag)</code></td><td>Returns the temporary tag on the CoreMap(s) or Value</td></tr> * <tr><td><code>RemoveTag(CoreMap or List<CoreMap>, tag)<br/>RemoveVTag(Value,tag)</code></td><td>Removes the temporary tag on the CoreMap(s) or Value</td></tr> * <tr><th colspan="2">Regex functions</th></tr> * <tr><td><code>Match(List<CoreMap>, tokensregex)<br/>Match(String,regex)</code></td><td>Returns whether the tokens or text matched</td></tr> * <tr><td><code>Replace(List<CoreMap>, tokensregex, replacement)<br/>Match(String,regex,replacement)</code></td><td>Replaces the matched tokens or text</td></tr> * <tr><td><code>CreateRegex(List<String>)</code></td><td>Creates one big string regular expression that matches any of the strings in the list</td></tr> * <tr><th colspan="2">Accessor functions</th></tr> * <tr><td><code>Map(list,function)</code></td><td>Returns a new list that is the result of applying the function on every element of the List</td></tr> * <tr><td><code>Keys(map)</code></td><td>Returns list of keys for the given map</td></tr> * <tr><td><code>Set(object or map, fieldname, value)<br/>Set(list,index,value)</code></td><td>Set the field to the specified value</td></tr> * <tr><td><code>Get(object or map, fieldname) or object.fieldname <br/>Get(list,index) or list[index]</code></td><td>Returns the value of the specified field</td></tr> * <tr><th colspan="2">String functions</th></tr> * <tr><td><code>Format(format,arg1,arg2,...)</code></td><td>Returns formatted string</td></tr> * <tr><td><code>Concat(str1,str2,...)</code></td><td>Returns strings concatenated together</td></tr> * <tr><td><code>Join(glue,str1,str2,...)</code></td><td>Returns strings concatenated together with glue in the middle</td></tr> * <tr><td><code>Lowercase(str)</code></td><td>Returns the lowercase form of the string</td></tr> * <tr><td><code>Uppercase(str)</code></td><td>Returns the uppercase form of the string</td></tr> * <tr><th colspan="2">Numeric functions</th></tr> * <tr><td><code>Subtract(X,Y)</code></td><td>Returns <code>X-Y</code></td></tr> * <tr><td><code>Add(X,Y)</code></td><td>Returns <code>X+Y</code></td></tr> * <tr><td><code>Subtract(X,Y)</code></td><td>Returns <code>X-Y</code></td></tr> * <tr><td><code>Multiply(X,Y)</code></td><td>Returns <code>X*Y</code></td></tr> * <tr><td><code>Divide(X,Y)</code></td><td>Returns <code>X/Y</code></td></tr> * <tr><td><code>Mod(X,Y)</code></td><td>Returns <code>X%Y</code></td></tr> * <tr><td><code>Negate(X)</code></td><td>Returns <code>-X</code></td></tr> * <tr><th colspan="2">Boolean functions</th></tr> * <tr><td><code>And(X,Y)</code></td><td>Returns <code>X&&Y</code></td></tr> * <tr><td><code>Or(X,Y)</code></td><td>Returns <code>X||Y</code></td></tr> * <tr><td><code>Not(X)</code></td><td>Returns <code>!X</code></td></tr> * <tr><td><code>GE(X,Y) or X >= Y</code></td><td>Returns <code>X >= Y</code></td></tr> * <tr><td><code>GT(X,Y) or X > Y</code></td><td>Returns <code>X > Y</code></td></tr> * <tr><td><code>LE(X,Y) or X <= Y</code></td><td>Returns <code>X <= Y</code></td></tr> * <tr><td><code>LT(X,Y) or X < Y</code></td><td>Returns <code>X < Y</code></td></tr> * <tr><td><code>EQ(X,Y) or X == Y</code></td><td>Returns <code>X == Y</code></td></tr> * <tr><td><code>NE(X,Y) or X != Y</code></td><td>Returns <code>X != Y</code></td></tr> * </table> * </p> * * @author Angel Chang */ public class Expressions { /** A logger for this class */ private static Redwood.RedwoodChannels log = Redwood.channels(Expressions.class); /** VAR - Variable */ public static final String TYPE_VAR = "VAR"; /** FUNCTION - (input) => (output) where input is a list of Values, and output is a single Value */ public static final String TYPE_FUNCTION = "FUNCTION"; /** REGEX - Regular expression pattern (for tokens or string) */ public static final String TYPE_REGEX = "REGEX"; public static final String TYPE_STRING_REGEX = "STRING_REGEX"; public static final String TYPE_TOKEN_REGEX = "TOKEN_REGEX"; /** REGEXMATCHVAR - Variable that refers to variable resulting from a regex match or used in a regex match (starts with $) */ public static final String TYPE_REGEXMATCHVAR = "REGEXMATCHVAR"; /** STRING - String */ public static final String TYPE_STRING = "STRING"; /** NUMBER - Numeric value (can be integer or real) */ public static final String TYPE_NUMBER = "NUMBER"; /** COMPOSITE - Composite value with field names and field values */ public static final String TYPE_COMPOSITE = "COMPOSITE"; /** LIST - List */ public static final String TYPE_LIST = "LIST"; public static final String TYPE_SET = "SET"; public static final String TYPE_ANNOTATION_KEY = "ANNOKEY"; /** CLASS - Maps to a Java class */ public static final String TYPE_CLASS = "CLASS"; public static final String TYPE_TOKENS = "TOKENS"; public static final String TYPE_BOOLEAN = "BOOLEAN"; public static final String VAR_SELF = "_"; public static final Value<Boolean> TRUE = new PrimitiveValue<>(Expressions.TYPE_BOOLEAN, true); public static final Value<Boolean> FALSE = new PrimitiveValue<>(Expressions.TYPE_BOOLEAN, false); public static final Value NIL = new PrimitiveValue("NIL", null); private Expressions() { } // static methods and classes public static Boolean convertValueToBoolean(Value v, boolean keepNull) { Boolean res = null; if (v != null) { Object obj = v.get(); if (obj != null) { if (obj instanceof Boolean) { res = ((Boolean) obj).booleanValue(); } else if (obj instanceof Integer) { res = (((Integer) obj).intValue() != 0); } else { res = true; } return res; } } return (keepNull)? res:false; } public static Value<Boolean> convertValueToBooleanValue(Value v, boolean keepNull) { if (v != null) { Object obj = v.get(); if (obj instanceof Boolean) { return (Value<Boolean>) v; } else { return new PrimitiveValue<>(Expressions.TYPE_BOOLEAN, convertValueToBoolean(v, keepNull)); } } else { return keepNull? null:FALSE; } } public static <C> C asObject(Env env, Object v) { if (v instanceof Expression) { return (C) ((Expression) v).evaluate(env).get(); } else { return (C) v; } } public static Expression asExpression(Env env, Object v) { if (v instanceof Expression) { return (Expression) v; } else { return createValue(null, v); } } public static Value asValue(Env env, Object v) { if (v instanceof Value) { return (Value) v; } else { return createValue(null, v); } } public static <T> Value createValue(String typename, T value, String... tags) { if (value instanceof Value) { return (Value) value; } else { if (typename == null && value != null) { // TODO: Check for simpler typename provided by value typename = value.getClass().getName(); } return new PrimitiveValue<>(typename, value, tags); } } /** * An expression that is a wrapper around another expression. */ public abstract static class WrappedExpression implements Expression { protected Expression expr; @Override public Tags getTags() { return expr.getTags(); } @Override public void setTags(Tags tags) { expr.setTags(tags); } @Override public String getType() { return expr.getType(); } @Override public Expression simplify(Env env) { return expr.simplify(env); } @Override public boolean hasValue() { return expr.hasValue(); } @Override public Value evaluate(Env env, Object... args) { return expr.evaluate(env, args); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof WrappedExpression)) return false; WrappedExpression that = (WrappedExpression) o; if (expr != null ? !expr.equals(that.expr) : that.expr != null) return false; return true; } @Override public int hashCode() { return expr != null ? expr.hashCode() : 0; } } /** * An expression with a typename and tags. */ public abstract static class TypedExpression implements Expression, Serializable { String typename; Tags tags; public TypedExpression(String typename, String... tags) { this.typename = typename; if (tags != null) { this.tags = new Tags(tags); } } public Tags getTags() { return tags; } public void setTags(Tags tags) { this.tags = tags; } public String getType() { return typename; } public Expression simplify(Env env) { return this; } public boolean hasValue() { return false; } private static final long serialVersionUID = 2; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof TypedExpression)) return false; TypedExpression that = (TypedExpression) o; if (tags != null ? !tags.equals(that.tags) : that.tags != null) return false; if (typename != null ? !typename.equals(that.typename) : that.typename != null) return false; return true; } @Override public int hashCode() { int result = typename != null ? typename.hashCode() : 0; result = 31 * result + (tags != null ? tags.hashCode() : 0); return result; } } /** * A simple implementation of an expression that is represented by a java object of type T * @param <T> type of the expression object */ public abstract static class SimpleExpression<T> extends Expressions.TypedExpression { T value; protected SimpleExpression(String typename, T value, String... tags) { super(typename, tags); this.value = value; } public T get() { return value; } public String toString() { return getType() + "(" + value + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SimpleExpression)) return false; if (!super.equals(o)) return false; SimpleExpression that = (SimpleExpression) o; if (value != null ? !value.equals(that.value) : that.value != null) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (value != null ? value.hashCode() : 0); return result; } } /** * A simple implementation of an expression that is represented by a java object of type T * and which also has a cached Value stored with it * @param <T> type of the expression object */ public static class SimpleCachedExpression<T> extends SimpleExpression<T> { Value evaluated; boolean disableCaching = false; protected SimpleCachedExpression(String typename, T value, String... tags) { super(typename, value, tags); } protected Value doEvaluation(Env env, Object... args) { throw new UnsupportedOperationException("Cannot evaluate type: " + typename); } public Value evaluate(Env env, Object... args) { if (args != null) { return doEvaluation(env, args); } if (evaluated == null || disableCaching) { evaluated = doEvaluation(env, args); } return evaluated; } public boolean hasValue() { return (evaluated != null); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SimpleCachedExpression)) return false; SimpleCachedExpression that = (SimpleCachedExpression) o; if (disableCaching != that.disableCaching) return false; if (evaluated != null ? !evaluated.equals(that.evaluated) : that.evaluated != null) return false; return true; } @Override public int hashCode() { int result = evaluated != null ? evaluated.hashCode() : 0; result = 31 * result + (disableCaching ? 1 : 0); return result; } } /** * Simple implementation of Value backed by a java object of type T * @param <T> */ public static class SimpleValue<T> extends Expressions.TypedExpression implements Value<T> { T value; protected SimpleValue(String typename, T value, String... tags) { super(typename, tags); this.value = value; } public T get() { return value; } public Value evaluate(Env env, Object... args) { return this; } public String toString() { return getType() + "(" + value + ")"; } public boolean hasValue() { return true; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SimpleValue)) return false; if (!super.equals(o)) return false; SimpleValue that = (SimpleValue) o; if (value != null ? !value.equals(that.value) : that.value != null) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (value != null ? value.hashCode() : 0); return result; } } /** * A string that represents a regular expression */ public static class RegexValue extends SimpleValue<String> { public RegexValue(String regex, String... tags) { super(TYPE_REGEX, regex, tags); } } /** * A variable assignment with the name of the variable, and the expression to assign to that variable */ public static class VarAssignmentExpression extends Expressions.TypedExpression { final String varName; final Expression valueExpr; final boolean bindAsValue; public VarAssignmentExpression(String varName, Expression valueExpr, boolean bindAsValue) { super("VAR_ASSIGNMENT"); this.varName = varName; this.valueExpr = valueExpr; this.bindAsValue = bindAsValue; } public Value evaluate(Env env, Object... args) { Value value = valueExpr.evaluate(env, args); if (args != null) { if (args.length == 1 && args[0] instanceof CoreMap) { CoreMap cm = (CoreMap) args[0]; Class annotationKey = EnvLookup.lookupAnnotationKey(env, varName); if (annotationKey != null) { cm.set(annotationKey, (value != null)? value.get():null); return value; } } } if (bindAsValue) { env.bind(varName, value); } else { env.bind(varName, (value != null)? value.get():null); if (TYPE_REGEX == value.getType()) { try { Object vobj = value.get(); if (vobj instanceof String) { env.bindStringRegex(varName, (String) vobj); } else if (vobj instanceof Pattern) { env.bindStringRegex(varName, ((Pattern) vobj).pattern()); } } catch (Exception ex) {} } } return value; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof VarAssignmentExpression)) return false; if (!super.equals(o)) return false; VarAssignmentExpression that = (VarAssignmentExpression) o; if (bindAsValue != that.bindAsValue) return false; if (valueExpr != null ? !valueExpr.equals(that.valueExpr) : that.valueExpr != null) return false; if (varName != null ? !varName.equals(that.varName) : that.varName != null) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (varName != null ? varName.hashCode() : 0); result = 31 * result + (valueExpr != null ? valueExpr.hashCode() : 0); result = 31 * result + (bindAsValue ? 1 : 0); return result; } } // end class VarAssignmentExpression /** * A variable, which can be assigned any expression. * When evaluated, the value of the variable is retrieved from the * environment, evaluated, and returned. */ public static class VarExpression extends SimpleExpression<String> implements AssignableExpression { public VarExpression(String varname, String... tags) { super(TYPE_VAR, varname, tags); } @Override public Value evaluate(Env env, Object... args) { Expression exp = null; String varName = value; if (args != null) { if (args.length == 1 && args[0] instanceof CoreMap) { CoreMap cm = (CoreMap) args[0]; if (VAR_SELF.equals(varName)) { return createValue(varName, cm); } Class annotationKey = EnvLookup.lookupAnnotationKey(env, varName); if (annotationKey != null) { return createValue(varName, cm.get(annotationKey)); } } } if (VAR_SELF.equals(varName)) { return createValue(varName, env.peek(varName)); } Object obj = env.get(varName); if (obj != null) { exp = asExpression(env, obj); } Value v = exp != null? exp.evaluate(env, args): null; if (v == null) { log.info("Unknown variable: " + varName); } return v; } public Expression assign(Expression expr) { return new VarAssignmentExpression(value, expr, true); } } /** * A variable that represents a regular expression match result. * The match result is identified either by the group id (Integer) or * the group name (String). * When evaluated, one argument (the MatchResult or SequenceMatchResult) must be supplied. * Depending on the match result supplied, the returned value * is either a String (for MatchResult) or a list of tokens (for SequenceMatchResult). */ private static final Pattern DIGITS_PATTERN = Pattern.compile("\\d+"); public static class RegexMatchVarExpression extends SimpleExpression implements AssignableExpression { public RegexMatchVarExpression(String groupname, String... tags) { super(TYPE_REGEXMATCHVAR, groupname, tags); } public RegexMatchVarExpression(Integer groupid, String... tags) { super(TYPE_REGEXMATCHVAR, groupid, tags); } public static RegexMatchVarExpression valueOf(String group) { if (DIGITS_PATTERN.matcher(group).matches()) { Integer n = Integer.valueOf(group); return new RegexMatchVarExpression(n); } else { return new RegexMatchVarExpression(group); } } public Value evaluate(Env env, Object... args) { if (args != null && args.length > 0) { if (args[0] instanceof SequenceMatchResult) { SequenceMatchResult mr = (SequenceMatchResult) args[0]; Object v = get(); if (v instanceof String) { // TODO: depending if TYPE_STRING, use string version... return new PrimitiveValue<>(TYPE_TOKENS, mr.groupNodes((String) v)); } else if (v instanceof Integer) { return new PrimitiveValue<>(TYPE_TOKENS, mr.groupNodes((Integer) v)); } else { throw new UnsupportedOperationException("String match result must be referred to by group id"); } } else if (args[0] instanceof MatchResult) { MatchResult mr = (MatchResult) args[0]; Object v = get(); if (v instanceof Integer) { String str = mr.group((Integer) get()); return new PrimitiveValue<>(TYPE_STRING, str); } else { throw new UnsupportedOperationException("String match result must be referred to by group id"); } } } return null; } public Expression assign(Expression expr) { return new VarAssignmentExpression(value.toString(), expr, false); } } public static class RegexMatchResultVarExpression extends SimpleExpression { public RegexMatchResultVarExpression(String groupname, String... tags) { super(TYPE_REGEXMATCHVAR, groupname, tags); } public RegexMatchResultVarExpression(Integer groupid, String... tags) { super(TYPE_REGEXMATCHVAR, groupid, tags); } public static RegexMatchResultVarExpression valueOf(String group) { if (DIGITS_PATTERN.matcher(group).matches()) { Integer n = Integer.valueOf(group); return new RegexMatchResultVarExpression(n); } else { return new RegexMatchResultVarExpression(group); } } public Value evaluate(Env env, Object... args) { if (args != null && args.length > 0) { if (args[0] instanceof SequenceMatchResult) { SequenceMatchResult mr = (SequenceMatchResult) args[0]; Object v = get(); if (v instanceof String) { return new PrimitiveValue("MATCHED_GROUP_INFO", mr.groupInfo((String) v)); } else if (v instanceof Integer) { return new PrimitiveValue("MATCHED_GROUP_INFO", mr.groupInfo((Integer) v)); } else { throw new UnsupportedOperationException("String match result must be referred to by group id"); } } } return null; } } /** * A function call that can be assigned a value. */ public static class AssignableFunctionCallExpression extends FunctionCallExpression implements AssignableExpression { public AssignableFunctionCallExpression(String function, List<Expression> params, String... tags) { super(function, params, tags); } public Expression assign(Expression expr) { List<Expression> newParams = new ArrayList<>(params); newParams.add(expr); Expression res = new FunctionCallExpression(function, newParams); res.setTags(tags); return res; } } public static class IndexedExpression extends AssignableFunctionCallExpression { public IndexedExpression(Expression expr, int index) { super("ListSelect", Arrays.asList(expr, new PrimitiveValue("Integer", index))); } } public static class FieldExpression extends AssignableFunctionCallExpression { public FieldExpression(Expression expr, String field) { super("Select", Arrays.asList(expr, new PrimitiveValue(TYPE_STRING, field))); } public FieldExpression(Expression expr, Expression field) { super("Select", Arrays.asList(expr, field)); } } public static class OrExpression extends FunctionCallExpression { public OrExpression(List<Expression> children) { super("Or", children); } } public static class AndExpression extends FunctionCallExpression { public AndExpression(List<Expression> children) { super("And", children); } } public static class NotExpression extends FunctionCallExpression { public NotExpression(Expression expr) { super("Not", Arrays.asList(expr)); } } public static class IfExpression extends Expressions.TypedExpression { Expression condExpr; Expression trueExpr; Expression falseExpr; public IfExpression(Expression cond, Expression vt, Expression vf) { super("If"); this.condExpr = cond; this.trueExpr = vt; this.falseExpr = vf; } public Value evaluate(Env env, Object... args) { Value condValue = condExpr.evaluate(env, args); Boolean cond = (Boolean) condValue.get(); if (cond) { return trueExpr.evaluate(env, args); } else { return falseExpr.evaluate(env, args); } } } public static class CaseExpression extends Expressions.WrappedExpression { public CaseExpression(List<Pair<Expression,Expression>> conds, Expression elseExpr) { if (conds.size() == 0) { throw new IllegalArgumentException("No conditions!"); } else { expr = elseExpr; for (int i = conds.size()-1; i>=0; i--) { Pair<Expression,Expression> p = conds.get(i); expr = new IfExpression(p.first(), p.second(), expr); } } } } public static class ConditionalExpression extends Expressions.WrappedExpression { public ConditionalExpression(Expression expr) { this.expr = expr; } public ConditionalExpression(String op, Expression expr1, Expression expr2) { switch (op) { case ">=": expr = new FunctionCallExpression("GE", Arrays.asList(expr1, expr2)); break; case "<=": expr = new FunctionCallExpression("LE", Arrays.asList(expr1, expr2)); break; case ">": expr = new FunctionCallExpression("GT", Arrays.asList(expr1, expr2)); break; case "<": expr = new FunctionCallExpression("LT", Arrays.asList(expr1, expr2)); break; case "==": expr = new FunctionCallExpression("EQ", Arrays.asList(expr1, expr2)); break; case "!=": expr = new FunctionCallExpression("NE", Arrays.asList(expr1, expr2)); break; case "=~": expr = new FunctionCallExpression("Match", Arrays.asList(expr1, expr2)); break; case "!~": expr = new NotExpression(new FunctionCallExpression("Match", Arrays.asList(expr1, expr2))); break; } } @Override public String getType() { return Expressions.TYPE_BOOLEAN; } @Override public Expression simplify(Env env) { return this; } @Override public Value evaluate(Env env, Object... args) { Value v = expr.evaluate(env, args); return convertValueToBooleanValue(v, false); } } public static class ListExpression extends TypedExpression { List<Expression> exprs; public ListExpression(String typename, String... tags) { super(typename, tags); this.exprs = new ArrayList<>(); } public ListExpression(String typename, List<Expression> exprs, String... tags) { super(typename, tags); this.exprs = new ArrayList<>(exprs); } public void addAll(List<Expression> exprs) { if (exprs != null) { this.exprs.addAll(exprs); } } public void add(Expression expr) { this.exprs.add(expr); } public Value evaluate(Env env, Object... args) { List<Value> values = new ArrayList<>(exprs.size()); for (Expression s:exprs) { values.add(s.evaluate(env, args)); }; return new PrimitiveValue<>(typename, values); } } private static final boolean isArgTypesCompatible(Class[] paramTypes, Class[] targetParamTypes) { boolean compatible = true; if (targetParamTypes.length == paramTypes.length) { for (int i = 0; i < targetParamTypes.length; i++) { if (targetParamTypes[i].isPrimitive()) { compatible = false; if (paramTypes[i] != null) { try { Class<?> type = (Class<?>) paramTypes[i].getField("TYPE").get(null); if (type.equals(targetParamTypes[i])) { compatible = true; } } catch (NoSuchFieldException ex2) { } catch (IllegalAccessException ex2) { } } if (!compatible) break; } else { if (paramTypes[i] != null && !targetParamTypes[i].isAssignableFrom(paramTypes[i])) { compatible = false; break; } } } } else { compatible = false; } return compatible; } protected static final String NEWLINE = System.getProperty("line.separator"); public static class FunctionCallExpression extends Expressions.TypedExpression { final String function; final List<? extends Expression> params; public FunctionCallExpression(String function, List<? extends Expression> params, String... tags) { super(TYPE_FUNCTION, tags); this.function = function; this.params = params; } public String toString() { return function + '(' + StringUtils.join(params, ", ") + ')'; } public Expression simplify(Env env) { boolean paramsAllHasValue = true; List<Expression> simplifiedParams = new ArrayList<>(params.size()); for (Expression param:params) { Expression simplified = param.simplify(env); simplifiedParams.add(simplified); if (!(simplified.hasValue())) { paramsAllHasValue = false; } } Expression res = new FunctionCallExpression(function, simplifiedParams); if (paramsAllHasValue) { return res.evaluate(env); } else { return res; } } public Value evaluate(Env env, Object... args) { Object funcValue = ValueFunctions.lookupFunctionObject(env, function); if (funcValue == null) { throw new RuntimeException("Unknown function " + function); } if (funcValue instanceof Value) { funcValue = ((Value) funcValue).evaluate(env, args).get(); } if (funcValue instanceof ValueFunction) { ValueFunction f = (ValueFunction) funcValue; List<Value> evaled = new ArrayList<>(); for (Expression param:params) { evaled.add(param.evaluate(env, args)); } return f.apply(env, evaled); } else if (funcValue instanceof Collection) { List<Value> evaled = new ArrayList<>(); for (Expression param:params) { evaled.add(param.evaluate(env, args)); } Collection<ValueFunction> fs = (Collection<ValueFunction>) funcValue; for (ValueFunction f:fs) { if (f.checkArgs(evaled)) { return f.apply(env, evaled); } } StringBuilder sb = new StringBuilder(); sb.append("Cannot find function matching args: " + function + NEWLINE); sb.append("Args are: " + StringUtils.join(evaled, ",") + NEWLINE); if (fs.size() > 0) { sb.append("Options are:\n" + StringUtils.join(fs, NEWLINE)); } else { sb.append("No options"); } throw new RuntimeException(sb.toString()); } else if (funcValue instanceof Class) { Class c = (Class) funcValue; List<Value> evaled = new ArrayList<>(); for (Expression param:params) { evaled.add(param.evaluate(env, args)); } Class[] paramTypes = new Class[params.size()]; Object[] objs = new Object[params.size()]; boolean paramsNotNull = true; for (int i = 0; i < params.size(); i++) { Value v = evaled.get(i); if (v != null) { objs[i] = v.get(); if (objs[i] != null) { paramTypes[i] = objs[i].getClass(); } else { paramTypes[i] = null; paramsNotNull = false; } } else { objs[i] = null; paramTypes[i] = null; paramsNotNull = false; //throw new RuntimeException("Missing evaluated value for " + params.get(i)); } } if (paramsNotNull) { Object obj = MetaClass.create(c).createInstance(objs); if (obj != null) { return new PrimitiveValue<>(function, obj); } } try { Constructor constructor = null; try { constructor = c.getConstructor(paramTypes); } catch (NoSuchMethodException ex) { Constructor[] constructors = c.getConstructors(); for (Constructor cons:constructors) { Class[] consParamTypes = cons.getParameterTypes(); boolean compatible = isArgTypesCompatible(paramTypes, consParamTypes); if (compatible) { constructor = cons; break; } } if (constructor == null) { throw new RuntimeException("Cannot instantiate " + c, ex); } } Object obj = constructor.newInstance(objs); return new PrimitiveValue<>(function, obj); } catch (InvocationTargetException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } catch (InstantiationException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } } else { throw new UnsupportedOperationException("Unsupported function value " + funcValue); } } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof FunctionCallExpression)) return false; FunctionCallExpression that = (FunctionCallExpression) o; if (function != null ? !function.equals(that.function) : that.function != null) return false; if (params != null ? !params.equals(that.params) : that.params != null) return false; return true; } @Override public int hashCode() { int result = function != null ? function.hashCode() : 0; result = 31 * result + (params != null ? params.hashCode() : 0); return result; } } public static class MethodCallExpression extends Expressions.TypedExpression { String function; private final Expression object; List<Expression> params; public MethodCallExpression(String function, Expression object, List<Expression> params, String... tags) { super(TYPE_FUNCTION, tags); this.function = function; this.object = object; this.params = params; } public String toString() { return object + "." + function + '(' + StringUtils.join(params, ", ") + ')'; } public Expression simplify(Env env) { boolean paramsAllHasValue = true; List<Expression> simplifiedParams = new ArrayList<>(params.size()); for (Expression param:params) { Expression simplified = param.simplify(env); simplifiedParams.add(simplified); if (!(simplified.hasValue())) { paramsAllHasValue = false; } } Expression simplifiedObject = object.simplify(env); Expression res = new MethodCallExpression(function, simplifiedObject, simplifiedParams); if (paramsAllHasValue && object.hasValue()) { return res.evaluate(env); } else { return res; } } public Value evaluate(Env env, Object... args) { Value evaledObj = object.evaluate(env, args); if (evaledObj == null || evaledObj.get() == null) return null; Object mainObj = evaledObj.get(); Class c = mainObj.getClass(); List<Value> evaled = new ArrayList<>(); for (Expression param:params) { evaled.add(param.evaluate(env, args)); } Class[] paramTypes = new Class[params.size()]; Object[] objs = new Object[params.size()]; for (int i = 0; i < params.size(); i++) { Value v = evaled.get(i); if (v != null) { objs[i] = v.get(); if (objs[i] != null) { paramTypes[i] = objs[i].getClass(); } else { paramTypes[i] = null; } } else { objs[i] = null; paramTypes[i] = null; //throw new RuntimeException("Missing evaluated value for " + params.get(i)); } } Method method = null; try { method = c.getMethod(function, paramTypes); } catch (NoSuchMethodException ex) { Method[] methods = c.getMethods(); for (Method m:methods) { if (m.getName().equals(function)) { Class[] mParamTypes = m.getParameterTypes(); if (mParamTypes.length == paramTypes.length) { boolean compatible = isArgTypesCompatible(paramTypes, mParamTypes); if (compatible) { method = m; break; } } } } if (method == null) { throw new RuntimeException("Cannot find method " + function + " on object of class " + c, ex); } } try { Object res = method.invoke(mainObj, objs); return new PrimitiveValue<>(function, res); } catch (InvocationTargetException ex) { throw new RuntimeException("Cannot evaluate method " + function + " on object " + mainObj, ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Cannot evaluate method " + function + " on object " + mainObj, ex); } } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof MethodCallExpression)) return false; if (!super.equals(o)) return false; MethodCallExpression that = (MethodCallExpression) o; if (function != null ? !function.equals(that.function) : that.function != null) return false; if (object != null ? !object.equals(that.object) : that.object != null) return false; if (params != null ? !params.equals(that.params) : that.params != null) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (function != null ? function.hashCode() : 0); result = 31 * result + (object != null ? object.hashCode() : 0); result = 31 * result + (params != null ? params.hashCode() : 0); return result; } } /** * Primitive value that is directly represented by a Java object of type T */ public static class PrimitiveValue<T> extends SimpleValue<T> { public PrimitiveValue(String typename, T value, String... tags) { super(typename, value, tags); } } /** * A composite value with field names and values for each field */ public static class CompositeValue extends SimpleCachedExpression<Map<String,Expression>> implements Value<Map<String,Expression>>{ public CompositeValue(String... tags) { super(TYPE_COMPOSITE, new HashMap<>(), tags);//Generics.<String,Expression>newHashMap() } public CompositeValue(Map<String, Expression> m, boolean isEvaluated, String... tags) { super(TYPE_COMPOSITE, m, tags); if (isEvaluated) { evaluated = this; disableCaching = !checkValue(); } } private boolean checkValue() { boolean ok = true; for (String key:value.keySet()) { Expression expr = value.get(key); if (expr != null && !expr.hasValue()) { ok = false; } } return ok; } public Set<String> getAttributes() { return value.keySet(); } public Expression getExpression(String attr) { return value.get(attr); } public Value getValue(String attr) { Expression expr = value.get(attr); if (expr == null) return null; if (expr instanceof Value) { return (Value) expr; } throw new UnsupportedOperationException("Expression was not evaluated...."); } public <T> T get(String attr) { Expression expr = value.get(attr); if (expr == null) return null; if (expr instanceof Value) { return ((Value<T>) expr).get(); } throw new UnsupportedOperationException("Expression was not evaluated...."); } public void set(String attr, Object obj) { if (obj instanceof Expression) { value.put(attr, (Expression) obj); } else { value.put(attr, createValue(null, obj)); } evaluated = null; } private static Object toCompatibleObject(Field f, Object value) { if (value == null) return value; if (!f.getDeclaringClass().isAssignableFrom(value.getClass())) { if (Number.class.isAssignableFrom(value.getClass())) { Number number = (Number) value; if (f.getType().isAssignableFrom(Double.class)) { return number.doubleValue(); } else if (f.getType().isAssignableFrom(Float.class)) { return number.floatValue(); } else if (f.getType().isAssignableFrom(Long.class)) { return number.longValue(); } else if (f.getType().isAssignableFrom(Integer.class)) { return number.intValue(); } } } return value; } private static Value attemptTypeConversion(CompositeValue cv, Env env, Object... args) { Expression typeFieldExpr = cv.value.get("type"); if (typeFieldExpr != null) { // Automatically convert types .... Value typeValue = typeFieldExpr.evaluate(env, args); if (typeFieldExpr instanceof VarExpression) { VarExpression varExpr = (VarExpression) typeFieldExpr; // The name of the variable is used to indicate the "type" of object String typeName = varExpr.get(); if (typeValue != null) { // Check if variable points to a class // If so, then try to instantiate a new instance of the class if (TYPE_CLASS.equals(typeValue.getType())) { // Variable maps to a java class Class c = (Class) typeValue.get(); try { Object obj = c.newInstance(); // for any field other than the "type", set the value of the field // of the created object to the specified value for (String s:cv.value.keySet()) { if (!"type".equals(s)) { Value v = cv.value.get(s).evaluate(env, args); try { Field f = c.getField(s); Object objVal = toCompatibleObject(f, v.get()); f.set(obj, objVal); } catch (NoSuchFieldException ex){ throw new RuntimeException("Unknown field " + s + " for type " + typeName + ", trying to set to " + v, ex); } catch (IllegalArgumentException ex){ throw new RuntimeException("Incompatible type " + s + " for type " + typeName + ", trying to set to " + v, ex); } } } return new PrimitiveValue<>(typeName, obj); } catch (InstantiationException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } } else if (typeValue.get() != null){ // When evaluated, variable does not explicitly map to "CLASS" // See if we can convert this CompositeValue into appropriate object // by calling "create(CompositeValue cv)" Class c = typeValue.get().getClass(); try { Method m = c.getMethod("create", CompositeValue.class); CompositeValue evaluatedCv = cv.evaluateNoTypeConversion(env, args); try { return new PrimitiveValue<>(typeName, m.invoke(typeValue.get(), evaluatedCv)); } catch (InvocationTargetException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } catch (IllegalAccessException ex) { throw new RuntimeException("Cannot instantiate " + c, ex); } } catch (NoSuchMethodException ex) {} } } } else if (typeValue != null && typeValue.get() instanceof String) { String typeName = (String) typeValue.get(); // Predefined types: Expression valueField = cv.value.get("value"); Value value = valueField.evaluate(env, args); switch (typeName) { case TYPE_ANNOTATION_KEY: { String className = (String) value.get(); try { return new PrimitiveValue<Class>(TYPE_ANNOTATION_KEY, Class.forName(className)); } catch (ClassNotFoundException ex) { throw new RuntimeException("Unknown class " + className, ex); } } case TYPE_CLASS: { String className = (String) value.get(); try { return new PrimitiveValue<Class>(TYPE_CLASS, Class.forName(className)); } catch (ClassNotFoundException ex) { throw new RuntimeException("Unknown class " + className, ex); } } case TYPE_STRING: return new PrimitiveValue<>(TYPE_STRING, (String) value.get()); case TYPE_REGEX: return new RegexValue((String) value.get()); /* } else if (TYPE_TOKEN_REGEX.equals(type)) { return new PrimitiveValue<TokenSequencePattern>(TYPE_TOKEN_REGEX, (TokenSequencePattern) value.get()); */ case TYPE_NUMBER: if (value.get() instanceof Number) { return new PrimitiveValue<>(TYPE_NUMBER, (Number) value.get()); } else if (value.get() instanceof String) { String str = (String) value.get(); if (str.contains(".")) { return new PrimitiveValue<Number>(TYPE_NUMBER, Double.valueOf(str)); } else { return new PrimitiveValue<Number>(TYPE_NUMBER, Long.valueOf(str)); } } else { throw new IllegalArgumentException("Invalid value " + value + " for type " + typeName); } default: // TODO: support other types return new PrimitiveValue(typeName, value.get()); //throw new UnsupportedOperationException("Cannot convert type " + typeName); } } } return null; } public CompositeValue simplifyNoTypeConversion(Env env, Object... args) { Map<String, Expression> m = value; Map<String, Expression> res = new HashMap<>(m.size());//Generics.newHashMap (m.size()); for (Map.Entry<String, Expression> stringExpressionEntry : m.entrySet()) { res.put(stringExpressionEntry.getKey(), stringExpressionEntry.getValue().simplify(env)); } return new CompositeValue(res, true); } private CompositeValue evaluateNoTypeConversion(Env env, Object... args) { Map<String, Expression> m = value; Map<String, Expression> res = new HashMap<>(m.size());//Generics.newHashMap (m.size()); for (Map.Entry<String, Expression> stringExpressionEntry : m.entrySet()) { res.put(stringExpressionEntry.getKey(), stringExpressionEntry.getValue().evaluate(env, args)); } return new CompositeValue(res, true); } public Value doEvaluation(Env env, Object... args) { Value v = attemptTypeConversion(this, env, args); if (v != null) return v; Map<String, Expression> m = value; Map<String, Expression> res = new HashMap<>(m.size());//Generics.newHashMap (m.size()); for (Map.Entry<String, Expression> stringExpressionEntry : m.entrySet()) { res.put(stringExpressionEntry.getKey(), stringExpressionEntry.getValue().evaluate(env, args)); } disableCaching = !checkValue(); return new CompositeValue(res, true); } } // end static class CompositeValue }