/* * This used to have a license header that it was licensed by Gatespace in the year 2000. However, this was licensed * to the OSGi Alliance. A member donated this as ASL 2.0 licensed matching this project's default license. */ package aQute.lib.filter; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; import java.util.Dictionary; import java.util.Hashtable; import java.util.Map; public class Filter { final char WILDCARD = 65535; final static int EQ = 0; final static int LE = 1; final static int GE = 2; // Extended operators final static int NEQ = 100; final static int LT = 101; final static int GT = 102; final static int APPROX = 3; final String filter; final boolean extended; abstract class Query { static final String GARBAGE = "Trailing garbage"; static final String MALFORMED = "Malformed query"; static final String EMPTY = "Empty list"; static final String SUBEXPR = "No subexpression"; static final String OPERATOR = "Undefined operator"; static final String TRUNCATED = "Truncated expression"; static final String EQUALITY = "Only equality supported"; private String tail; boolean match() throws Exception { tail = filter; boolean val = doQuery(); if (tail.length() > 0) error(GARBAGE); return val; } private boolean doQuery() throws Exception { if (tail.length() < 3 || !prefix("(")) error(MALFORMED); boolean val; switch (tail.charAt(0)) { case '&' : val = doAnd(); break; case '|' : val = doOr(); break; case '!' : val = doNot(); break; default : val = doSimple(); break; } if (!prefix(")")) error(MALFORMED); return val; } private boolean doAnd() throws Exception { tail = skip(); boolean val = true; if (!tail.startsWith("(")) error(EMPTY); do { if (!doQuery()) val = false; } while (tail.startsWith("(")); return val; } String skip() { String a = tail; do { a = a.substring(1); } while (a.length() > 0 && Character.isWhitespace(a.charAt(0))); return a; } private boolean doOr() throws Exception { tail = skip(); boolean val = false; if (!tail.startsWith("(")) error(EMPTY); do { if (doQuery()) val = true; } while (tail.startsWith("(")); return val; } private boolean doNot() throws Exception { tail = skip(); if (!tail.startsWith("(")) error(SUBEXPR); return !doQuery(); } boolean doSimple() throws Exception { int op = 0; Object attr = getAttr(); if (prefix("=")) op = EQ; else if (prefix("<=")) op = LE; else if (prefix(">=")) op = GE; else if (prefix("~=")) op = APPROX; else if (extended && prefix("!=")) op = NEQ; else if (extended && prefix(">")) op = GT; else if (extended && prefix("<")) op = LT; else error(OPERATOR); return compare(attr, op, getValue()); } boolean prefix(String pre) { if (!tail.startsWith(pre)) return false; tail = tail.substring(pre.length()); return true; } Object getAttr() throws Exception { int len = tail.length(); int ix = 0; label: for (; ix < len; ix++) { switch (tail.charAt(ix)) { case '(' : case ')' : case '<' : case '>' : case '=' : case '~' : case '*' : case '\\' : break label; } } String attr = tail.substring(0, ix); tail = tail.substring(ix); return getProp(attr); } abstract Object getProp(String key) throws Exception; private String getValue() { StringBuilder sb = new StringBuilder(); int len = tail.length(); int ix = 0; label: for (; ix < len; ix++) { char c = tail.charAt(ix); switch (c) { case '(' : case ')' : break label; case '*' : sb.append(WILDCARD); break; case '\\' : if (ix == len - 1) break label; sb.append(tail.charAt(++ix)); break; default : sb.append(c); break; } } tail = tail.substring(ix); return sb.toString(); } void error(String m) throws IllegalArgumentException { throw new IllegalArgumentException(m + " " + tail); } @SuppressWarnings("unchecked") <T> boolean compare(T obj, int op, String s) { if (obj == null) return false; if ((op == EQ) && (s.length() == 1) && (s.charAt(0) == WILDCARD)) return true; try { Class<T> numClass = (Class<T>) obj.getClass(); if (numClass == String.class) { return compareString((String) obj, op, s); } else if (numClass == Character.class) { return compareString(obj.toString(), op, s); } else if (numClass == Long.class) { return compareSign(op, Long.valueOf(s).compareTo((Long) obj)); } else if (numClass == Integer.class) { return compareSign(op, Integer.valueOf(s).compareTo((Integer) obj)); } else if (numClass == Short.class) { return compareSign(op, Short.valueOf(s).compareTo((Short) obj)); } else if (numClass == Byte.class) { return compareSign(op, Byte.valueOf(s).compareTo((Byte) obj)); } else if (numClass == Double.class) { return compareSign(op, Double.valueOf(s).compareTo((Double) obj)); } else if (numClass == Float.class) { return compareSign(op, Float.valueOf(s).compareTo((Float) obj)); } else if (numClass == Boolean.class) { if (op != EQ) return false; int a = Boolean.valueOf(s).booleanValue() ? 1 : 0; int b = ((Boolean) obj).booleanValue() ? 1 : 0; return compareSign(op, a - b); } else if (numClass == BigInteger.class) { return compareSign(op, new BigInteger(s).compareTo((BigInteger) obj)); } else if (numClass == BigDecimal.class) { return compareSign(op, new BigDecimal(s).compareTo((BigDecimal) obj)); } else if (obj instanceof Collection< ? >) { for (Object x : (Collection< ? >) obj) if (compare(x, op, s)) return true; } else if (numClass.isArray()) { int len = Array.getLength(obj); for (int i = 0; i < len; i++) if (compare(Array.get(obj, i), op, s)) return true; } else { Constructor<T> constructor = numClass.getConstructor(String.class); T source = constructor.newInstance(s); if (op == EQ) return source.equals(obj); Comparable<T> a = Comparable.class.cast(source); Comparable<T> b = Comparable.class.cast(obj); return compareSign(op, a.compareTo((T) b)); } } catch (Exception e) {} return false; } } class DictQuery extends Query { private Dictionary< ? , ? > dict; DictQuery(Dictionary< ? , ? > dict) { this.dict = dict; } @Override Object getProp(String key) { return dict.get(key); } } class MapQuery extends Query { private Map< ? , ? > map; MapQuery(Map< ? , ? > dict) { this.map = dict; } @Override Object getProp(String key) { return map.get(key); } } class GetQuery extends Query { private Get get; GetQuery(Get get) { this.get = get; } @Override Object getProp(String key) throws Exception { return get.get(key); } } public Filter(String filter, boolean extended) throws IllegalArgumentException { this.filter = filter; this.extended = extended; if (filter == null || filter.length() == 0) throw new IllegalArgumentException("Null query"); } public Filter(String filter) throws IllegalArgumentException { this(filter, false); } public boolean match(Dictionary< ? , ? > dict) throws Exception { try { return new DictQuery(dict).match(); } catch (IllegalArgumentException e) { return false; } } public boolean matchMap(Map< ? , ? > dict) throws Exception { try { return new MapQuery(dict).match(); } catch (IllegalArgumentException e) { return false; } } public boolean match(Get get) throws Exception { try { return new GetQuery(get).match(); } catch (IllegalArgumentException e) { return false; } } public String verify() throws Exception { try { new DictQuery(new Hashtable<Object,Object>()).match(); } catch (IllegalArgumentException e) { return e.getMessage(); } return null; } @Override public String toString() { return filter; } @Override public boolean equals(Object obj) { return obj instanceof Filter && filter.equals(((Filter) obj).filter); } @Override public int hashCode() { return filter.hashCode(); } boolean compareString(String s1, int op, String s2) { switch (op) { case EQ : return patSubstr(s1, s2); case APPROX : return fixupString(s2).equals(fixupString(s1)); default : return compareSign(op, s2.compareTo(s1)); } } boolean compareSign(int op, int cmp) { switch (op) { case LE : return cmp >= 0; case GE : return cmp <= 0; case EQ : return cmp == 0; case NEQ : return cmp != 0; case LT : return cmp > 0; case GT : return cmp < 0; default : /* APPROX */ return cmp == 0; } } String fixupString(String s) { StringBuilder sb = new StringBuilder(); int len = s.length(); boolean isStart = true; boolean isWhite = false; for (int i = 0; i < len; i++) { char c = s.charAt(i); if (Character.isWhitespace(c)) { isWhite = true; } else { if (!isStart && isWhite) sb.append(' '); if (Character.isUpperCase(c)) c = Character.toLowerCase(c); sb.append(c); isStart = false; isWhite = false; } } return sb.toString(); } boolean patSubstr(String s, String pat) { if (s == null) return false; if (pat.length() == 0) return s.length() == 0; if (pat.charAt(0) == WILDCARD) { pat = pat.substring(1); for (;;) { if (patSubstr(s, pat)) return true; if (s.length() == 0) return false; s = s.substring(1); } } if (s.length() == 0 || s.charAt(0) != pat.charAt(0)) return false; return patSubstr(s.substring(1), pat.substring(1)); } }