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));
+        
+    }
+    
+}