Stuart McCulloch | d00f971 | 2009-07-13 10:06:47 +0000 | [diff] [blame^] | 1 | /** |
| 2 | * Copyright (c) 2000 Gatespace AB. All Rights Reserved. |
| 3 | * |
| 4 | * Gatespace grants Open Services Gateway Initiative (OSGi) an irrevocable, |
| 5 | * perpetual, non-exclusive, worldwide, paid-up right and license to |
| 6 | * reproduce, display, perform, prepare and have prepared derivative works |
| 7 | * based upon and distribute and sublicense this material and derivative |
| 8 | * works thereof as set out in the OSGi MEMBER AGREEMENT as of January 24 |
| 9 | * 2000, for use in accordance with Section 2.2 of the BY-LAWS of the |
| 10 | * OSGi MEMBER AGREEMENT. |
| 11 | */ |
| 12 | |
| 13 | package aQute.lib.filter; |
| 14 | |
| 15 | import java.lang.reflect.*; |
| 16 | import java.math.*; |
| 17 | import java.util.*; |
| 18 | |
| 19 | public class Filter { |
| 20 | final char WILDCARD = 65535; |
| 21 | |
| 22 | final int EQ = 0; |
| 23 | final int LE = 1; |
| 24 | final int GE = 2; |
| 25 | final int APPROX = 3; |
| 26 | |
| 27 | private String filter; |
| 28 | |
| 29 | abstract class Query { |
| 30 | static final String GARBAGE = "Trailing garbage"; |
| 31 | static final String MALFORMED = "Malformed query"; |
| 32 | static final String EMPTY = "Empty list"; |
| 33 | static final String SUBEXPR = "No subexpression"; |
| 34 | static final String OPERATOR = "Undefined operator"; |
| 35 | static final String TRUNCATED = "Truncated expression"; |
| 36 | static final String EQUALITY = "Only equality supported"; |
| 37 | |
| 38 | private String tail; |
| 39 | |
| 40 | boolean match() throws IllegalArgumentException { |
| 41 | tail = filter; |
| 42 | boolean val = doQuery(); |
| 43 | if (tail.length() > 0) |
| 44 | error(GARBAGE); |
| 45 | return val; |
| 46 | } |
| 47 | |
| 48 | private boolean doQuery() throws IllegalArgumentException { |
| 49 | if (tail.length() < 3 || !prefix("(")) |
| 50 | error(MALFORMED); |
| 51 | boolean val; |
| 52 | |
| 53 | switch (tail.charAt(0)) { |
| 54 | case '&': |
| 55 | val = doAnd(); |
| 56 | break; |
| 57 | case '|': |
| 58 | val = doOr(); |
| 59 | break; |
| 60 | case '!': |
| 61 | val = doNot(); |
| 62 | break; |
| 63 | default: |
| 64 | val = doSimple(); |
| 65 | break; |
| 66 | } |
| 67 | |
| 68 | if (!prefix(")")) |
| 69 | error(MALFORMED); |
| 70 | return val; |
| 71 | } |
| 72 | |
| 73 | private boolean doAnd() throws IllegalArgumentException { |
| 74 | tail = tail.substring(1); |
| 75 | boolean val = true; |
| 76 | if (!tail.startsWith("(")) |
| 77 | error(EMPTY); |
| 78 | do { |
| 79 | if (!doQuery()) |
| 80 | val = false; |
| 81 | } while (tail.startsWith("(")); |
| 82 | return val; |
| 83 | } |
| 84 | |
| 85 | private boolean doOr() throws IllegalArgumentException { |
| 86 | tail = tail.substring(1); |
| 87 | boolean val = false; |
| 88 | if (!tail.startsWith("(")) |
| 89 | error(EMPTY); |
| 90 | do { |
| 91 | if (doQuery()) |
| 92 | val = true; |
| 93 | } while (tail.startsWith("(")); |
| 94 | return val; |
| 95 | } |
| 96 | |
| 97 | private boolean doNot() throws IllegalArgumentException { |
| 98 | tail = tail.substring(1); |
| 99 | if (!tail.startsWith("(")) |
| 100 | error(SUBEXPR); |
| 101 | return !doQuery(); |
| 102 | } |
| 103 | |
| 104 | private boolean doSimple() throws IllegalArgumentException { |
| 105 | int op = 0; |
| 106 | Object attr = getAttr(); |
| 107 | |
| 108 | if (prefix("=")) |
| 109 | op = EQ; |
| 110 | else if (prefix("<=")) |
| 111 | op = LE; |
| 112 | else if (prefix(">=")) |
| 113 | op = GE; |
| 114 | else if (prefix("~=")) |
| 115 | op = APPROX; |
| 116 | else |
| 117 | error(OPERATOR); |
| 118 | |
| 119 | return compare(attr, op, getValue()); |
| 120 | } |
| 121 | |
| 122 | private boolean prefix(String pre) { |
| 123 | if (!tail.startsWith(pre)) |
| 124 | return false; |
| 125 | tail = tail.substring(pre.length()); |
| 126 | return true; |
| 127 | } |
| 128 | |
| 129 | private Object getAttr() { |
| 130 | int len = tail.length(); |
| 131 | int ix = 0; |
| 132 | label: for (; ix < len; ix++) { |
| 133 | switch (tail.charAt(ix)) { |
| 134 | case '(': |
| 135 | case ')': |
| 136 | case '<': |
| 137 | case '>': |
| 138 | case '=': |
| 139 | case '~': |
| 140 | case '*': |
| 141 | case '\\': |
| 142 | break label; |
| 143 | } |
| 144 | } |
| 145 | String attr = tail.substring(0, ix).toLowerCase(); |
| 146 | tail = tail.substring(ix); |
| 147 | return getProp(attr); |
| 148 | } |
| 149 | |
| 150 | abstract Object getProp(String key); |
| 151 | |
| 152 | private String getValue() { |
| 153 | StringBuffer sb = new StringBuffer(); |
| 154 | int len = tail.length(); |
| 155 | int ix = 0; |
| 156 | label: for (; ix < len; ix++) { |
| 157 | char c = tail.charAt(ix); |
| 158 | switch (c) { |
| 159 | case '(': |
| 160 | case ')': |
| 161 | break label; |
| 162 | case '*': |
| 163 | sb.append(WILDCARD); |
| 164 | break; |
| 165 | case '\\': |
| 166 | if (ix == len - 1) |
| 167 | break label; |
| 168 | sb.append(tail.charAt(++ix)); |
| 169 | break; |
| 170 | default: |
| 171 | sb.append(c); |
| 172 | break; |
| 173 | } |
| 174 | } |
| 175 | tail = tail.substring(ix); |
| 176 | return sb.toString(); |
| 177 | } |
| 178 | |
| 179 | private void error(String m) throws IllegalArgumentException { |
| 180 | throw new IllegalArgumentException(m + " " + tail); |
| 181 | } |
| 182 | |
| 183 | private boolean compare(Object obj, int op, String s) { |
| 184 | if (obj == null) |
| 185 | return false; |
| 186 | try { |
| 187 | Class<?> numClass = obj.getClass(); |
| 188 | if (numClass == String.class) { |
| 189 | return compareString((String) obj, op, s); |
| 190 | } else if (numClass == Character.class) { |
| 191 | return compareString(obj.toString(), op, s); |
| 192 | } else if (numClass == Long.class) { |
| 193 | return compareSign(op, Long.valueOf(s) |
| 194 | .compareTo((Long) obj)); |
| 195 | } else if (numClass == Integer.class) { |
| 196 | return compareSign(op, Integer.valueOf(s).compareTo( |
| 197 | (Integer) obj)); |
| 198 | } else if (numClass == Short.class) { |
| 199 | return compareSign(op, Short.valueOf(s).compareTo( |
| 200 | (Short) obj)); |
| 201 | } else if (numClass == Byte.class) { |
| 202 | return compareSign(op, Byte.valueOf(s) |
| 203 | .compareTo((Byte) obj)); |
| 204 | } else if (numClass == Double.class) { |
| 205 | return compareSign(op, Double.valueOf(s).compareTo( |
| 206 | (Double) obj)); |
| 207 | } else if (numClass == Float.class) { |
| 208 | return compareSign(op, Float.valueOf(s).compareTo( |
| 209 | (Float) obj)); |
| 210 | } else if (numClass == Boolean.class) { |
| 211 | if (op != EQ) |
| 212 | return false; |
| 213 | int a = Boolean.valueOf(s).booleanValue() ? 1 : 0; |
| 214 | int b = ((Boolean) obj).booleanValue() ? 1 : 0; |
| 215 | return compareSign(op, a - b); |
| 216 | } else if (numClass == BigInteger.class) { |
| 217 | return compareSign(op, new BigInteger(s) |
| 218 | .compareTo((BigInteger) obj)); |
| 219 | } else if (numClass == BigDecimal.class) { |
| 220 | return compareSign(op, new BigDecimal(s) |
| 221 | .compareTo((BigDecimal) obj)); |
| 222 | } else if (obj instanceof Collection) { |
| 223 | for (Object x : (Collection<?>) obj) |
| 224 | if (compare(x, op, s)) |
| 225 | return true; |
| 226 | } else if (numClass.isArray()) { |
| 227 | int len = Array.getLength(obj); |
| 228 | for (int i = 0; i < len; i++) |
| 229 | if (compare(Array.get(obj, i), op, s)) |
| 230 | return true; |
| 231 | } |
| 232 | } catch (Exception e) { |
| 233 | } |
| 234 | return false; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | class DictQuery extends Query { |
| 239 | private Dictionary<?,?> dict; |
| 240 | |
| 241 | DictQuery(Dictionary<?,?> dict) { |
| 242 | this.dict = dict; |
| 243 | } |
| 244 | |
| 245 | Object getProp(String key) { |
| 246 | return dict.get(key); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | public Filter(String filter) throws IllegalArgumentException { |
| 251 | // NYI: Normalize the filter string? |
| 252 | this.filter = filter; |
| 253 | if (filter == null || filter.length() == 0) |
| 254 | throw new IllegalArgumentException("Null query"); |
| 255 | } |
| 256 | |
| 257 | public boolean match(Dictionary<?,?> dict) { |
| 258 | try { |
| 259 | return new DictQuery(dict).match(); |
| 260 | } catch (IllegalArgumentException e) { |
| 261 | return false; |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | public String verify() { |
| 266 | try { |
| 267 | new DictQuery(new Hashtable<Object,Object>()).match(); |
| 268 | } catch (IllegalArgumentException e) { |
| 269 | return e.getMessage(); |
| 270 | } |
| 271 | return null; |
| 272 | } |
| 273 | |
| 274 | public String toString() { |
| 275 | return filter; |
| 276 | } |
| 277 | |
| 278 | public boolean equals(Object obj) { |
| 279 | return obj != null && obj instanceof Filter |
| 280 | && filter.equals(((Filter) obj).filter); |
| 281 | } |
| 282 | |
| 283 | public int hashCode() { |
| 284 | return filter.hashCode(); |
| 285 | } |
| 286 | |
| 287 | boolean compareString(String s1, int op, String s2) { |
| 288 | switch (op) { |
| 289 | case EQ: |
| 290 | return patSubstr(s1, s2); |
| 291 | case APPROX: |
| 292 | return fixupString(s2).equals(fixupString(s1)); |
| 293 | default: |
| 294 | return compareSign(op, s2.compareTo(s1)); |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | boolean compareSign(int op, int cmp) { |
| 299 | switch (op) { |
| 300 | case LE: |
| 301 | return cmp >= 0; |
| 302 | case GE: |
| 303 | return cmp <= 0; |
| 304 | case EQ: |
| 305 | return cmp == 0; |
| 306 | default: /* APPROX */ |
| 307 | return cmp == 0; |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | String fixupString(String s) { |
| 312 | StringBuffer sb = new StringBuffer(); |
| 313 | int len = s.length(); |
| 314 | boolean isStart = true; |
| 315 | boolean isWhite = false; |
| 316 | for (int i = 0; i < len; i++) { |
| 317 | char c = s.charAt(i); |
| 318 | if (Character.isWhitespace(c)) { |
| 319 | isWhite = true; |
| 320 | } else { |
| 321 | if (!isStart && isWhite) |
| 322 | sb.append(' '); |
| 323 | if (Character.isUpperCase(c)) |
| 324 | c = Character.toLowerCase(c); |
| 325 | sb.append(c); |
| 326 | isStart = false; |
| 327 | isWhite = false; |
| 328 | } |
| 329 | } |
| 330 | return sb.toString(); |
| 331 | } |
| 332 | |
| 333 | boolean patSubstr(String s, String pat) { |
| 334 | if (s == null) |
| 335 | return false; |
| 336 | if (pat.length() == 0) |
| 337 | return s.length() == 0; |
| 338 | if (pat.charAt(0) == WILDCARD) { |
| 339 | pat = pat.substring(1); |
| 340 | for (;;) { |
| 341 | if (patSubstr(s, pat)) |
| 342 | return true; |
| 343 | if (s.length() == 0) |
| 344 | return false; |
| 345 | s = s.substring(1); |
| 346 | } |
| 347 | } else { |
| 348 | if (s.length() == 0 || s.charAt(0) != pat.charAt(0)) |
| 349 | return false; |
| 350 | return patSubstr(s.substring(1), pat.substring(1)); |
| 351 | } |
| 352 | } |
| 353 | } |