Applied patch (FELIX-973) to add support for new operators in filter
implementation to work better with bindex generated repository files.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@760256 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/FilterImpl.java b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/FilterImpl.java
new file mode 100644
index 0000000..7609a30
--- /dev/null
+++ b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/FilterImpl.java
@@ -0,0 +1,663 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.bundlerepository;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+public class FilterImpl implements Filter
+{
+ private static final char WILDCARD = 65535;
+ private static final int EQ = 0;
+ private static final int LE = 1;
+ private static final int GE = 2;
+ private static final int APPROX = 3;
+ private static final int LESS = 4;
+ private static final int GREATER = 5;
+ private static final int SUBSET = 6;
+ private static final int SUPERSET = 7;
+ private String m_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 m_tail;
+ protected boolean m_caseSensitive = false;
+
+ boolean match() throws IllegalArgumentException
+ {
+ m_tail = m_filter;
+ boolean val = doQuery();
+ if (m_tail.length() > 0)
+ {
+ error(GARBAGE);
+ }
+ return val;
+ }
+
+ private boolean doQuery() throws IllegalArgumentException
+ {
+ if (m_tail.length() < 3 || !prefix("("))
+ {
+ error(MALFORMED);
+ }
+ boolean val;
+
+ switch (m_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
+ {
+ m_tail = m_tail.substring(1);
+ boolean val = true;
+ if (!m_tail.startsWith("("))
+ {
+ error(EMPTY);
+ }
+ do
+ {
+ if (!doQuery())
+ {
+ val = false;
+ }
+ }
+ while (m_tail.startsWith("("));
+ return val;
+ }
+
+ private boolean doOr() throws IllegalArgumentException
+ {
+ m_tail = m_tail.substring(1);
+ boolean val = false;
+ if (!m_tail.startsWith("("))
+ {
+ error(EMPTY);
+ }
+ do
+ {
+ if (doQuery())
+ {
+ val = true;
+ }
+ }
+ while (m_tail.startsWith("("));
+ return val;
+ }
+
+ private boolean doNot() throws IllegalArgumentException
+ {
+ m_tail = m_tail.substring(1);
+ if (!m_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 (!m_tail.startsWith(pre))
+ {
+ return false;
+ }
+ m_tail = m_tail.substring(pre.length());
+ return true;
+ }
+
+ private Object getAttr()
+ {
+ int len = m_tail.length();
+ int ix = 0;
+ label:
+ for (; ix < len; ix++)
+ {
+ switch (m_tail.charAt(ix))
+ {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '=':
+ case '~':
+ case '*':
+ case ':':
+ case '}':
+ case '{':
+ case '\\':
+ break label;
+ }
+ }
+ String attr = m_tail.substring(0, ix).toLowerCase();
+ m_tail = m_tail.substring(ix);
+ return getProp(attr);
+ }
+
+ abstract Object getProp(String key);
+
+ private String getValue()
+ {
+ StringBuffer sb = new StringBuffer();
+ int len = m_tail.length();
+ int ix = 0;
+ label:
+ for (; ix < len; ix++)
+ {
+ char c = m_tail.charAt(ix);
+ switch (c)
+ {
+ case '(':
+ case ')':
+ break label;
+ case '*':
+ sb.append(WILDCARD);
+ break;
+ case '\\':
+ if (ix == len - 1)
+ {
+ break label;
+ }
+ sb.append(m_tail.charAt(++ix));
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ m_tail = m_tail.substring(ix);
+ return sb.toString();
+ }
+
+ private void error(String m) throws IllegalArgumentException
+ {
+ throw new IllegalArgumentException(m + " " + m_tail);
+ }
+
+ private boolean compare(Object obj, int op, String s)
+ {
+ if (obj == null && (op != SUBSET && op != SUPERSET))
+ {
+ return false;
+ }
+ try
+ {
+ Class numClass = null;
+ if (obj != null)
+ {
+ numClass = obj.getClass();
+ }
+ if (numClass == String.class && (op != SUBSET && op != SUPERSET))
+ {
+ 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);
+ StringSet objSet = new StringSet((String) obj);
+
+ if (op == SUPERSET)
+ {
+
+ boolean found = true;
+ Iterator iterator = set.iterator();
+ while (iterator.hasNext() && found)
+ {
+ Object object = (Object) iterator.next();
+ if (!objSet.contains(object))
+ {
+ found = false;
+ }
+ }
+
+ return found;
+ }
+ else
+ {
+ return set.containsAll(objSet);
+ }
+ }
+ 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 Dictionary m_dict;
+
+ DictQuery(Dictionary dict)
+ {
+ m_dict = dict;
+ }
+
+ DictQuery(Dictionary dict, boolean caseSensitive)
+ {
+ m_dict = dict;
+ m_caseSensitive = caseSensitive;
+ }
+
+ Object getProp(String key)
+ {
+
+ if (m_caseSensitive)
+ {
+ return m_dict.get(key);
+ }
+ else
+ {
+ Enumeration keys = m_dict.keys();
+ while (keys.hasMoreElements())
+ {
+ String propertyKey = (String) keys.nextElement();
+ if (propertyKey.equalsIgnoreCase(key))
+ {
+ return m_dict.get(propertyKey);
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+
+ class ServiceReferenceQuery extends Query
+ {
+ private ServiceReference m_ref;
+
+ public ServiceReferenceQuery(ServiceReference ref)
+ {
+ m_ref = ref;
+ }
+
+ Object getProp(String key)
+ {
+
+ if (m_caseSensitive)
+ {
+ return m_ref.getProperty(key);
+ }
+ else
+ {
+
+ String[] propertyKeys = m_ref.getPropertyKeys();
+ for (int i = 0; i < propertyKeys.length; i++)
+ {
+ String propertyKey = propertyKeys[i];
+ if (propertyKey.equalsIgnoreCase(key))
+ {
+ return m_ref.getProperty(propertyKey);
+ }
+ }
+ }
+
+ return null;
+
+ }
+ }
+
+ public FilterImpl(String filter) throws IllegalArgumentException
+ {
+ // NYI: Normalize the filter string?
+ this.m_filter = filter;
+ if (filter == null || filter.length() == 0)
+ {
+ throw new IllegalArgumentException("Null query");
+ }
+ }
+
+ public String toString()
+ {
+ return m_filter;
+ }
+
+ public boolean equals(Object obj)
+ {
+ return obj != null && obj instanceof FilterImpl && m_filter.equals(((FilterImpl) obj).m_filter);
+ }
+
+ public int hashCode()
+ {
+ return m_filter.hashCode();
+ }
+
+ private static 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));
+ }
+ }
+
+ private static 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;
+ }
+ }
+
+ private static 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();
+ }
+
+ private static 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));
+ }
+ }
+
+ public boolean match(Dictionary dict)
+ {
+ try
+ {
+ return new DictQuery(dict).match();
+ }
+ catch (IllegalArgumentException e)
+ {
+ return false;
+ }
+ }
+
+ public boolean match(ServiceReference reference)
+ {
+ try
+ {
+ return new ServiceReferenceQuery(reference).match();
+ }
+ catch (IllegalArgumentException e)
+ {
+ return false;
+ }
+
+ }
+
+ public boolean matchCase(Dictionary dictionary)
+ {
+ try
+ {
+ return new DictQuery(dictionary, true).match();
+ }
+ catch (IllegalArgumentException e)
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java
index 8e61497..da60537 100644
--- a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java
+++ b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java
@@ -52,9 +52,9 @@
return m_filter.toString();
}
- public synchronized void setFilter(String filter) throws InvalidSyntaxException
+ public synchronized void setFilter(String filter)
{
- m_filter = RepositoryAdminImpl.m_context.createFilter(filter);
+ m_filter = new FilterImpl(filter);
}
public synchronized boolean isSatisfied(Capability capability)
diff --git a/bundlerepository/src/main/java/org/apache/felix/bundlerepository/StringSet.java b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/StringSet.java
new file mode 100644
index 0000000..1ce521b
--- /dev/null
+++ b/bundlerepository/src/main/java/org/apache/felix/bundlerepository/StringSet.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.bundlerepository;
+
+import java.util.*;
+
+public class StringSet extends HashSet
+{
+ private static final long serialVersionUID = 1L;
+
+ public StringSet(String set)
+ {
+ StringTokenizer st = new StringTokenizer(set, ",");
+ while (st.hasMoreTokens())
+ {
+ add(st.nextToken().trim());
+ }
+ }
+}
\ No newline at end of file
diff --git a/bundlerepository/src/test/java/org/apache/felix/bundlerepository/FilterImplTest.java b/bundlerepository/src/test/java/org/apache/felix/bundlerepository/FilterImplTest.java
new file mode 100644
index 0000000..2d93530
--- /dev/null
+++ b/bundlerepository/src/test/java/org/apache/felix/bundlerepository/FilterImplTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.bundlerepository;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import junit.framework.TestCase;
+
+public class FilterImplTest extends TestCase {
+
+ public void testStandardLDAP() throws Exception {
+
+ FilterImpl filterImpl = new FilterImpl("(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split))");
+
+ Dictionary dict = new Hashtable();
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split-wrong");
+
+ assertFalse(filterImpl.match(dict));
+ }
+
+ public void testNoneStandardLDAPOperators() throws Exception {
+
+ FilterImpl filterImpl = new FilterImpl("(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split)(mandatory:<*common,test))");
+
+ Dictionary dict = new Hashtable();
+ dict.put("somethindifferent", "sonstwas");
+ assertFalse(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory", "common");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory", "common,test");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertFalse(filterImpl.match(dict));
+
+ filterImpl = new FilterImpl("(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split)(mandatory:*>common))");
+ dict = new Hashtable();
+ dict.put("mandatory", "common");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory", "common,test");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ filterImpl = new FilterImpl("(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split)(mandatory:*>common,test))");
+ dict = new Hashtable();
+ dict.put("mandatory", "common");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertFalse(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory", "common,test");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+ }
+
+ public void testCaseSensitive() throws Exception {
+
+ FilterImpl filterImpl = new FilterImpl("(&(package=org.eclipse.core.runtime))");
+
+ Dictionary dict = new Hashtable();
+ dict.put("PACKAGE", "org.eclipse.core.runtime");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("PACKAGE", "org.eclipse.core.runtime");
+ assertFalse(filterImpl.matchCase(dict));
+
+ }
+
+}