/* | |
* $Id: FilterImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $ | |
* | |
* Copyright (c) 2000 Gatespace AB. All Rights Reserved. | |
* Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.osgi.impl.bundle.obr.resource; | |
import java.lang.reflect.*; | |
import java.math.BigInteger; | |
import java.util.*; | |
public class FilterImpl { | |
final char WILDCARD = 65535; | |
final int EQ = 0; | |
final int LE = 1; | |
final int GE = 2; | |
final int APPROX = 3; | |
final int LESS = 4; | |
final int GREATER = 5; | |
final int SUBSET = 6; | |
final int SUPERSET = 7; | |
private String filter; | |
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 IllegalArgumentException { | |
tail = filter; | |
boolean val = doQuery(); | |
if (tail.length() > 0) | |
error(GARBAGE); | |
return val; | |
} | |
private boolean doQuery() throws IllegalArgumentException { | |
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 IllegalArgumentException { | |
tail = tail.substring(1); | |
boolean val = true; | |
if (!tail.startsWith("(")) | |
error(EMPTY); | |
do { | |
if (!doQuery()) | |
val = false; | |
} while (tail.startsWith("(")); | |
return val; | |
} | |
private boolean doOr() throws IllegalArgumentException { | |
tail = tail.substring(1); | |
boolean val = false; | |
if (!tail.startsWith("(")) | |
error(EMPTY); | |
do { | |
if (doQuery()) | |
val = true; | |
} while (tail.startsWith("(")); | |
return val; | |
} | |
private boolean doNot() throws IllegalArgumentException { | |
tail = tail.substring(1); | |
if (!tail.startsWith("(")) | |
error(SUBEXPR); | |
return !doQuery(); | |
} | |
private boolean doSimple() throws IllegalArgumentException { | |
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 (prefix("*>")) | |
op = SUPERSET; | |
else if (prefix("<*")) | |
op = SUBSET; | |
else if (prefix("<")) | |
op = LESS; | |
else if (prefix(">")) | |
op = GREATER; | |
else | |
error(OPERATOR); | |
return compare(attr, op, getValue()); | |
} | |
private boolean prefix(String pre) { | |
if (!tail.startsWith(pre)) | |
return false; | |
tail = tail.substring(pre.length()); | |
return true; | |
} | |
private Object getAttr() { | |
int len = tail.length(); | |
int ix = 0; | |
label: for (; ix < len; ix++) { | |
switch (tail.charAt(ix)) { | |
case '(' : | |
case ')' : | |
case '<' : | |
case '>' : | |
case '=' : | |
case '~' : | |
case '*' : | |
case '}' : | |
case '{' : | |
case '\\' : | |
break label; | |
} | |
} | |
String attr = tail.substring(0, ix).toLowerCase(); | |
tail = tail.substring(ix); | |
return getProp(attr); | |
} | |
abstract Object getProp(String key); | |
private String getValue() { | |
StringBuffer sb = new StringBuffer(); | |
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(); | |
} | |
private void error(String m) throws IllegalArgumentException { | |
throw new IllegalArgumentException(m + " " + tail); | |
} | |
private boolean compare(Object obj, int op, String s) { | |
if (obj == null) { | |
// No value is ok for a subset | |
if (op == SUBSET) | |
return true; | |
// No value is ok for a superset when the value is | |
// empty | |
if (op == SUPERSET) { | |
return s.trim().length() == 0; | |
} | |
return false; | |
} | |
try { | |
Class numClass = 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 (obj instanceof Collection) { | |
if (op == SUBSET || op == SUPERSET) { | |
StringSet set = new StringSet(s); | |
if (op == SUBSET) | |
return set.containsAll((Collection) obj); | |
else | |
return ((Collection) obj).containsAll(set); | |
} | |
for (Iterator i = ((Collection) obj).iterator(); i | |
.hasNext();) { | |
Object element = i.next(); | |
if (compare(element, 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 { | |
try { | |
if (op == SUPERSET || op == SUBSET) { | |
StringSet set = new StringSet(s); | |
if (op == SUPERSET) | |
return set.contains(obj); | |
else | |
return set.size() == 0 | |
|| (set.size() == 1 && set.iterator() | |
.next().equals(obj)); | |
} | |
else { | |
Constructor constructor = numClass | |
.getConstructor(new Class[] {String.class}); | |
Object instance = constructor | |
.newInstance(new Object[] {s}); | |
switch (op) { | |
case EQ : | |
return obj.equals(instance); | |
case LESS : | |
return ((Comparable) obj) | |
.compareTo(instance) < 0; | |
case GREATER : | |
return ((Comparable) obj) | |
.compareTo(instance) > 0; | |
case LE : | |
return ((Comparable) obj) | |
.compareTo(instance) <= 0; | |
case GE : | |
return ((Comparable) obj) | |
.compareTo(instance) >= 0; | |
} | |
} | |
} | |
catch (Exception e) { | |
e.printStackTrace(); | |
// Ignore | |
} | |
} | |
} | |
catch (Exception e) { | |
} | |
return false; | |
} | |
} | |
class DictQuery extends Query { | |
private Map dict; | |
DictQuery(Map dict) { | |
this.dict = dict; | |
} | |
Object getProp(String key) { | |
return dict.get(key); | |
} | |
} | |
public FilterImpl(String filter) throws IllegalArgumentException { | |
// NYI: Normalize the filter string? | |
this.filter = filter; | |
if (filter == null || filter.length() == 0) | |
throw new IllegalArgumentException("Null query"); | |
} | |
public boolean match(Map dict) { | |
try { | |
return new DictQuery(dict).match(); | |
} | |
catch (IllegalArgumentException e) { | |
return false; | |
} | |
} | |
public String toString() { | |
return filter; | |
} | |
public boolean equals(Object obj) { | |
return obj != null && obj instanceof FilterImpl | |
&& filter.equals(((FilterImpl) obj).filter); | |
} | |
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 patSubstr(fixupString(s1), fixupString(s2)); | |
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; | |
default : /* APPROX */ | |
return cmp == 0; | |
} | |
} | |
String fixupString(String s) { | |
StringBuffer sb = new StringBuffer(); | |
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); | |
} | |
} | |
else { | |
if (s.length() == 0 || s.charAt(0) != pat.charAt(0)) | |
return false; | |
return patSubstr(s.substring(1), pat.substring(1)); | |
} | |
} | |
} |