You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2014/03/31 18:18:18 UTC

svn commit: r1583367 [3/4] - in /felix/trunk/pojosr: ./ src/ src/main/ src/main/java/ src/main/java/de/ src/main/java/de/kalpatec/ src/main/java/de/kalpatec/pojosr/ src/main/java/de/kalpatec/pojosr/framework/ src/main/java/de/kalpatec/pojosr/framework/...

Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,578 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import de.kalpatec.pojosr.framework.felix.framework.util.StringComparator;
+import org.osgi.framework.wiring.BundleCapability;
+
+public class CapabilitySet
+{
+    private final Map<String, Map<Object, Set<BundleCapability>>> m_indices;
+    private final Set<BundleCapability> m_capSet = new HashSet<BundleCapability>();
+
+    public CapabilitySet(List<String> indexProps, boolean caseSensitive)
+    {
+        m_indices = (caseSensitive)
+            ? new TreeMap<String, Map<Object, Set<BundleCapability>>>()
+            : new TreeMap<String, Map<Object, Set<BundleCapability>>>(
+                        new StringComparator(false));
+        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++)
+        {
+            m_indices.put(
+                indexProps.get(i), new HashMap<Object, Set<BundleCapability>>());
+        }
+    }
+
+    public void addCapability(BundleCapability cap)
+    {
+        m_capSet.add(cap);
+
+        // Index capability.
+        for (Entry<String, Map<Object, Set<BundleCapability>>> entry : m_indices.entrySet())
+        {
+            Object value = cap.getAttributes().get(entry.getKey());
+            if (value != null)
+            {
+                if (value.getClass().isArray())
+                {
+                    value = convertArrayToList(value);
+                }
+
+                Map<Object, Set<BundleCapability>> index = entry.getValue();
+
+                if (value instanceof Collection)
+                {
+                    Collection c = (Collection) value;
+                    for (Object o : c)
+                    {
+                        indexCapability(index, cap, o);
+                    }
+                }
+                else
+                {
+                    indexCapability(index, cap, value);
+                }
+            }
+        }
+    }
+
+    private void indexCapability(
+        Map<Object, Set<BundleCapability>> index, BundleCapability cap, Object capValue)
+    {
+        Set<BundleCapability> caps = index.get(capValue);
+        if (caps == null)
+        {
+            caps = new HashSet<BundleCapability>();
+            index.put(capValue, caps);
+        }
+        caps.add(cap);
+    }
+
+    public void removeCapability(BundleCapability cap)
+    {
+        if (m_capSet.remove(cap))
+        {
+            for (Entry<String, Map<Object, Set<BundleCapability>>> entry : m_indices.entrySet())
+            {
+                Object value = cap.getAttributes().get(entry.getKey());
+                if (value != null)
+                {
+                    if (value.getClass().isArray())
+                    {
+                        value = convertArrayToList(value);
+                    }
+
+                    Map<Object, Set<BundleCapability>> index = entry.getValue();
+
+                    if (value instanceof Collection)
+                    {
+                        Collection c = (Collection) value;
+                        for (Object o : c)
+                        {
+                            deindexCapability(index, cap, o);
+                        }
+                    }
+                    else
+                    {
+                        deindexCapability(index, cap, value);
+                    }
+                }
+            }
+        }
+    }
+
+    private void deindexCapability(
+        Map<Object, Set<BundleCapability>> index, BundleCapability cap, Object value)
+    {
+        Set<BundleCapability> caps = index.get(value);
+        if (caps != null)
+        {
+            caps.remove(cap);
+            if (caps.isEmpty())
+            {
+                index.remove(value);
+            }
+        }
+    }
+
+   public Set<BundleCapability> match(SimpleFilter sf, boolean obeyMandatory)
+    {
+        Set<BundleCapability> matches = match(m_capSet, sf);
+        return matches;
+       /* return (obeyMandatory)
+            ? matchMandatory(matches, sf)
+            : matches;*/
+    }
+
+    private Set<BundleCapability> match(Set<BundleCapability> caps, SimpleFilter sf)
+    {
+        Set<BundleCapability> matches = new HashSet<BundleCapability>();
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matches.addAll(caps);
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++)
+            {
+                matches = match(caps, sfs.get(i));
+                caps = matches;
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.OR)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.addAll(match(caps, sfs.get(i)));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.NOT)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matches.addAll(caps);
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.removeAll(match(caps, sfs.get(i)));
+            }
+        }
+        else
+        {
+            Map<Object, Set<BundleCapability>> index = m_indices.get(sf.getName());
+            if ((sf.getOperation() == SimpleFilter.EQ) && (index != null))
+            {
+                Set<BundleCapability> existingCaps = index.get(sf.getValue());
+                if (existingCaps != null)
+                {
+                    matches.addAll(existingCaps);
+                    matches.retainAll(caps);
+                }
+            }
+            else
+            {
+                for (Iterator<BundleCapability> it = caps.iterator(); it.hasNext(); )
+                {
+                    BundleCapability cap = it.next();
+                    Object lhs = cap.getAttributes().get(sf.getName());
+                    if (lhs != null)
+                    {
+                        if (compare(lhs, sf.getValue(), sf.getOperation()))
+                        {
+                            matches.add(cap);
+                        }
+                    }
+                }
+            }
+        }
+
+        return matches;
+    }
+
+  /*  public static boolean matches(BundleCapability cap, SimpleFilter sf)
+    {
+        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
+    }
+*/
+    private static boolean matchesInternal(BundleCapability cap, SimpleFilter sf)
+    {
+        boolean matched = true;
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matched = true;
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; matched && (i < sfs.size()); i++)
+            {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.OR)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matched = false;
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; !matched && (i < sfs.size()); i++)
+            {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.NOT)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matched = !(matchesInternal(cap, sfs.get(i)));
+            }
+        }
+        else
+        {
+            matched = false;
+            Object lhs = cap.getAttributes().get(sf.getName());
+            if (lhs != null)
+            {
+                matched = compare(lhs, sf.getValue(), sf.getOperation());
+            }
+        }
+
+        return matched;
+    }
+
+   /* private static Set<BundleCapability> matchMandatory(
+        Set<BundleCapability> caps, SimpleFilter sf)
+    {
+        for (Iterator<BundleCapability> it = caps.iterator(); it.hasNext(); )
+        {
+            BundleCapability cap = it.next();
+            if (!matchMandatory(cap, sf))
+            {
+                it.remove();
+            }
+        }
+        return caps;
+    }
+
+    private static boolean matchMandatory(BundleCapability cap, SimpleFilter sf)
+    {
+        Map<String, Object> attrs = cap.getAttributes();
+        for (Entry<String, Object> entry : attrs.entrySet())
+        {
+            if (((BundleCapability) cap).isAttributeMandatory(entry.getKey())
+                && !matchMandatoryAttrbute(entry.getKey(), sf))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean matchMandatoryAttrbute(String attrName, SimpleFilter sf)
+    {
+        if ((sf.getName() != null) && sf.getName().equals(attrName))
+        {
+            return true;
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            List list = (List) sf.getValue();
+            for (int i = 0; i < list.size(); i++)
+            {
+                SimpleFilter sf2 = (SimpleFilter) list.get(i);
+                if ((sf2.getName() != null)
+                    && sf2.getName().equals(attrName))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }*/
+
+    private static final Class<?>[] STRING_CLASS = new Class[] { String.class };
+
+    private static boolean compare(Object lhs, Object rhsUnknown, int op)
+    {
+        if (lhs == null)
+        {
+            return false;
+        }
+
+        // If this is a PRESENT operation, then just return true immediately
+        // since we wouldn't be here if the attribute wasn't present.
+        if (op == SimpleFilter.PRESENT)
+        {
+            return true;
+        }
+
+        // If the type is comparable, then we can just return the
+        // result immediately.
+        if (lhs instanceof Comparable)
+        {
+            // Spec says SUBSTRING is false for all types other than string.
+            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+            {
+                return false;
+            }
+
+            Object rhs;
+            if (op == SimpleFilter.SUBSTRING)
+            {
+                rhs = rhsUnknown;
+            }
+            else
+            {
+                try
+                {
+                    rhs = coerceType(lhs, (String) rhsUnknown);
+                }
+                catch (Exception ex)
+                {
+                    return false;
+                }
+            }
+
+            switch (op)
+            {
+            case SimpleFilter.EQ:
+                    try
+                    {
+                return (((Comparable) lhs).compareTo(rhs) == 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+            case SimpleFilter.GTE:
+                    try
+                    {
+                return (((Comparable) lhs).compareTo(rhs) >= 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+            case SimpleFilter.LTE:
+                    try
+                    {
+                return (((Comparable) lhs).compareTo(rhs) <= 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+            case SimpleFilter.APPROX:
+                return compareApproximate(((Comparable) lhs), rhs);
+            case SimpleFilter.SUBSTRING:
+                    return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+            default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+        // Booleans do not implement comparable, so special case them.
+        else if (lhs instanceof Boolean)
+        {
+            Object rhs;
+            try
+            {
+                rhs = coerceType(lhs, (String) rhsUnknown);
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+
+            switch (op)
+            {
+            case SimpleFilter.EQ:
+            case SimpleFilter.GTE:
+            case SimpleFilter.LTE:
+            case SimpleFilter.APPROX:
+                return (lhs.equals(rhs));
+            default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+
+        // If the LHS is not a comparable or boolean, check if it is an
+        // array. If so, convert it to a list so we can treat it as a
+        // collection.
+        if (lhs.getClass().isArray())
+        {
+            lhs = convertArrayToList(lhs);
+        }
+
+        // If LHS is a collection, then call compare() on each element
+        // of the collection until a match is found.
+        if (lhs instanceof Collection)
+        {
+            for (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext();)
+            {
+                if (compare(iter.next(), rhsUnknown, op))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Spec says SUBSTRING is false for all types other than string.
+        if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+        {
+            return false;
+        }
+
+        // Since we cannot identify the LHS type, then we can only perform
+        // equality comparison.
+        try
+        {
+            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
+        }
+        catch (Exception ex)
+        {
+            return false;
+        }
+    }
+
+    private static boolean compareApproximate(Object lhs, Object rhs)
+    {
+        if (rhs instanceof String)
+        {
+            return removeWhitespace((String) lhs)
+                .equalsIgnoreCase(removeWhitespace((String) rhs));
+        }
+        else if (rhs instanceof Character)
+        {
+            return Character.toLowerCase(((Character) lhs))
+                == Character.toLowerCase(((Character) rhs));
+        }
+        return lhs.equals(rhs);
+    }
+
+    private static String removeWhitespace(String s)
+    {
+        StringBuffer sb = new StringBuffer(s.length());
+        for (int i = 0; i < s.length(); i++)
+        {
+            if (!Character.isWhitespace(s.charAt(i)))
+            {
+                sb.append(s.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static Object coerceType(Object lhs, String rhsString) throws Exception
+    {
+        // If the LHS expects a string, then we can just return
+        // the RHS since it is a string.
+        if (lhs.getClass() == rhsString.getClass())
+        {
+            return rhsString;
+        }
+
+        // Try to convert the RHS type to the LHS type by using
+        // the string constructor of the LHS class, if it has one.
+        Object rhs = null;
+        try
+        {
+            // The Character class is a special case, since its constructor
+            // does not take a string, so handle it separately.
+            if (lhs instanceof Character)
+            {
+                rhs = new Character(rhsString.charAt(0));
+            }
+            else
+            {
+                // Spec says we should trim number types.
+                if ((lhs instanceof Number) || (lhs instanceof Boolean))
+                {
+                    rhsString = rhsString.trim();
+                }
+                Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
+                ctor.setAccessible(true);
+                rhs = ctor.newInstance(new Object[] { rhsString });
+            }
+        }
+        catch (Exception ex)
+        {
+            throw new Exception(
+                "Could not instantiate class "
+                    + lhs.getClass().getName()
+                    + " from string constructor with argument '"
+                    + rhsString + "' because " + ex);
+        }
+
+        return rhs;
+    }
+
+    /**
+     * This is an ugly utility method to convert an array of primitives to an
+     * array of primitive wrapper objects. This method simplifies processing
+     * LDAP filters since the special case of primitive arrays can be ignored.
+     *
+     * @param array
+     *            An array of primitive types.
+     * @return An corresponding array using pritive wrapper objects.
+     **/
+    private static List convertArrayToList(Object array)
+    {
+        int len = Array.getLength(array);
+        List list = new ArrayList(len);
+        for (int i = 0; i < len; i++)
+        {
+            list.add(Array.get(array, i));
+        }
+        return list;
+    }
+}
\ No newline at end of file

Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,46 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+public class Directive
+{
+    private final String m_name;
+    private final Object m_value;
+
+    public Directive(String name, Object value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public String toString()
+    {
+        return m_name + "=" + m_value;
+    }
+}
\ No newline at end of file

Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,34 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.List;
+
+public interface Requirement
+{
+    String getNamespace();
+
+    SimpleFilter getFilter();
+
+    boolean isOptional();
+
+    Directive getDirective(String name);
+
+    List<Directive> getDirectives();
+}
\ No newline at end of file

Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,556 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SimpleFilter
+{
+    public static final int MATCH_ALL = 0;
+    public static final int AND = 1;
+    public static final int OR = 2;
+    public static final int NOT = 3;
+    public static final int EQ = 4;
+    public static final int LTE = 5;
+    public static final int GTE = 6;
+    public static final int SUBSTRING = 7;
+    public static final int PRESENT = 8;
+    public static final int APPROX = 9;
+
+    private final String m_name;
+    private final Object m_value;
+    private final int m_op;
+
+    public SimpleFilter(String attr, Object value, int op)
+    {
+        m_name = attr;
+        m_value = value;
+        m_op = op;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public int getOperation()
+    {
+        return m_op;
+    }
+
+    public String toString()
+    {
+        String s = null;
+        switch (m_op)
+        {
+        case AND:
+            s = "(&" + toString((List) m_value) + ")";
+            break;
+        case OR:
+            s = "(|" + toString((List) m_value) + ")";
+            break;
+        case NOT:
+            s = "(!" + toString((List) m_value) + ")";
+            break;
+        case EQ:
+            s = "(" + m_name + "=" + toEncodedString(m_value) + ")";
+            break;
+        case LTE:
+            s = "(" + m_name + "<=" + toEncodedString(m_value) + ")";
+            break;
+        case GTE:
+            s = "(" + m_name + ">=" + toEncodedString(m_value) + ")";
+            break;
+        case SUBSTRING:
+            s = "(" + m_name + "=" + unparseSubstring((List<String>) m_value)
+                    + ")";
+            break;
+        case PRESENT:
+            s = "(" + m_name + "=*)";
+            break;
+        case APPROX:
+            s = "(" + m_name + "~=" + toEncodedString(m_value) + ")";
+            break;
+        }
+        return s;
+    }
+
+    private static String toString(List list)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < list.size(); i++)
+        {
+            sb.append(list.get(i).toString());
+        }
+        return sb.toString();
+    }
+
+    private static String toDecodedString(String s, int startIdx, int endIdx)
+    {
+        StringBuffer sb = new StringBuffer(endIdx - startIdx);
+        boolean escaped = false;
+        for (int i = 0; i < (endIdx - startIdx); i++)
+        {
+            char c = s.charAt(startIdx + i);
+            if (!escaped && (c == '\\'))
+            {
+                escaped = true;
+            }
+            else
+            {
+                escaped = false;
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    private static String toEncodedString(Object o)
+    {
+        if (o instanceof String)
+        {
+            String s = (String) o;
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < s.length(); i++)
+            {
+                char c = s.charAt(i);
+                if ((c == '\\') || (c == '(') || (c == ')') || (c == '*'))
+                {
+                    sb.append('\\');
+                }
+                sb.append(c);
+            }
+
+            o = sb.toString();
+        }
+
+        return o.toString();
+    }
+
+    public static SimpleFilter parse(String filter)
+    {
+        int idx = skipWhitespace(filter, 0);
+
+        if ((filter == null) || (filter.length() == 0)
+                || (idx >= filter.length()))
+        {
+            throw new IllegalArgumentException("Null or empty filter.");
+        }
+        else if (filter.charAt(idx) != '(')
+        {
+            throw new IllegalArgumentException("Missing opening parenthesis: "
+                    + filter);
+        }
+
+        SimpleFilter sf = null;
+        List stack = new ArrayList();
+        boolean isEscaped = false;
+        while (idx < filter.length())
+        {
+            if (sf != null)
+            {
+                throw new IllegalArgumentException(
+                        "Only one top-level operation allowed: " + filter);
+            }
+
+            if (!isEscaped && (filter.charAt(idx) == '('))
+            {
+                // Skip paren and following whitespace.
+                idx = skipWhitespace(filter, idx + 1);
+
+                if (filter.charAt(idx) == '&')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(),
+                                SimpleFilter.AND));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else if (filter.charAt(idx) == '|')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(),
+                                SimpleFilter.OR));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else if (filter.charAt(idx) == '!')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(),
+                                SimpleFilter.NOT));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else
+                {
+                    stack.add(0, new Integer(idx));
+                }
+            }
+            else if (!isEscaped && (filter.charAt(idx) == ')'))
+            {
+                Object top = stack.remove(0);
+                if (top instanceof SimpleFilter)
+                {
+                    if (!stack.isEmpty()
+                            && (stack.get(0) instanceof SimpleFilter))
+                    {
+                        ((List) ((SimpleFilter) stack.get(0)).m_value).add(top);
+                    }
+                    else
+                    {
+                        sf = (SimpleFilter) top;
+                    }
+                }
+                else if (!stack.isEmpty()
+                        && (stack.get(0) instanceof SimpleFilter))
+                {
+                    ((List) ((SimpleFilter) stack.get(0)).m_value)
+                            .add(SimpleFilter.subfilter(filter,
+                                    ((Integer) top).intValue(), idx));
+                }
+                else
+                {
+                    sf = SimpleFilter.subfilter(filter,
+                            ((Integer) top).intValue(), idx);
+                }
+            }
+            else if (!isEscaped && (filter.charAt(idx) == '\\'))
+            {
+                isEscaped = true;
+            }
+            else
+            {
+                isEscaped = false;
+            }
+
+            idx = skipWhitespace(filter, idx + 1);
+        }
+
+        if (sf == null)
+        {
+            throw new IllegalArgumentException("Missing closing parenthesis: "
+                    + filter);
+        }
+
+        return sf;
+    }
+
+    private static SimpleFilter subfilter(String filter, int startIdx,
+            int endIdx)
+    {
+        final String opChars = "=<>~";
+
+        // Determine the ending index of the attribute name.
+        int attrEndIdx = startIdx;
+        for (int i = 0; i < (endIdx - startIdx); i++)
+        {
+            char c = filter.charAt(startIdx + i);
+            if (opChars.indexOf(c) >= 0)
+            {
+                break;
+            }
+            else if (!Character.isWhitespace(c))
+            {
+                attrEndIdx = startIdx + i + 1;
+            }
+        }
+        if (attrEndIdx == startIdx)
+        {
+            throw new IllegalArgumentException("Missing attribute name: "
+                    + filter.substring(startIdx, endIdx));
+        }
+        String attr = filter.substring(startIdx, attrEndIdx);
+
+        // Skip the attribute name and any following whitespace.
+        startIdx = skipWhitespace(filter, attrEndIdx);
+
+        // Determine the operator type.
+        int op = -1;
+        switch (filter.charAt(startIdx))
+        {
+        case '=':
+            op = EQ;
+            startIdx++;
+            break;
+        case '<':
+            if (filter.charAt(startIdx + 1) != '=')
+            {
+                throw new IllegalArgumentException("Unknown operator: "
+                        + filter.substring(startIdx, endIdx));
+            }
+            op = LTE;
+            startIdx += 2;
+            break;
+        case '>':
+            if (filter.charAt(startIdx + 1) != '=')
+            {
+                throw new IllegalArgumentException("Unknown operator: "
+                        + filter.substring(startIdx, endIdx));
+            }
+            op = GTE;
+            startIdx += 2;
+            break;
+        case '~':
+            if (filter.charAt(startIdx + 1) != '=')
+            {
+                throw new IllegalArgumentException("Unknown operator: "
+                        + filter.substring(startIdx, endIdx));
+            }
+            op = APPROX;
+            startIdx += 2;
+            break;
+        default:
+            throw new IllegalArgumentException("Unknown operator: "
+                    + filter.substring(startIdx, endIdx));
+        }
+
+        // Parse value.
+        Object value = toDecodedString(filter, startIdx, endIdx);
+
+        // Check if the equality comparison is actually a substring
+        // or present operation.
+        if (op == EQ)
+        {
+            String valueStr = filter.substring(startIdx, endIdx);
+            List<String> values = parseSubstring(valueStr);
+            if ((values.size() == 2) && (values.get(0).length() == 0)
+                    && (values.get(1).length() == 0))
+            {
+                op = PRESENT;
+            }
+            else if (values.size() > 1)
+            {
+                op = SUBSTRING;
+                value = values;
+            }
+        }
+
+        return new SimpleFilter(attr, value, op);
+    }
+
+    public static List<String> parseSubstring(String value)
+    {
+        List<String> pieces = new ArrayList();
+        StringBuffer ss = new StringBuffer();
+        // int kind = SIMPLE; // assume until proven otherwise
+        boolean wasStar = false; // indicates last piece was a star
+        boolean leftstar = false; // track if the initial piece is a star
+        boolean rightstar = false; // track if the final piece is a star
+
+        int idx = 0;
+
+        // We assume (sub)strings can contain leading and trailing blanks
+        boolean escaped = false;
+        loop: for (;;)
+        {
+            if (idx >= value.length())
+            {
+                if (wasStar)
+                {
+                    // insert last piece as "" to handle trailing star
+                    rightstar = true;
+                }
+                else
+                {
+                    pieces.add(ss.toString());
+                    // accumulate the last piece
+                    // note that in the case of
+                    // (cn=); this might be
+                    // the string "" (!=null)
+                }
+                ss.setLength(0);
+                break loop;
+            }
+
+            // Read the next character and account for escapes.
+            char c = value.charAt(idx++);
+            if (!escaped && ((c == '(') || (c == ')')))
+            {
+                throw new IllegalArgumentException("Illegal value: " + value);
+            }
+            else if (!escaped && (c == '*'))
+            {
+                if (wasStar)
+                {
+                    // encountered two successive stars;
+                    // I assume this is illegal
+                    throw new IllegalArgumentException(
+                            "Invalid filter string: " + value);
+                }
+                if (ss.length() > 0)
+                {
+                    pieces.add(ss.toString()); // accumulate the pieces
+                    // between '*' occurrences
+                }
+                ss.setLength(0);
+                // if this is a leading star, then track it
+                if (pieces.size() == 0)
+                {
+                    leftstar = true;
+                }
+                wasStar = true;
+            }
+            else if (!escaped && (c == '\\'))
+            {
+                escaped = true;
+            }
+            else
+            {
+                escaped = false;
+                wasStar = false;
+                ss.append(c);
+            }
+        }
+        if (leftstar || rightstar || pieces.size() > 1)
+        {
+            // insert leading and/or trailing "" to anchor ends
+            if (rightstar)
+            {
+                pieces.add("");
+            }
+            if (leftstar)
+            {
+                pieces.add(0, "");
+            }
+        }
+        return pieces;
+    }
+
+    public static String unparseSubstring(List<String> pieces)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < pieces.size(); i++)
+        {
+            if (i > 0)
+            {
+                sb.append("*");
+            }
+            sb.append(toEncodedString(pieces.get(i)));
+        }
+        return sb.toString();
+    }
+
+    public static boolean compareSubstring(List<String> pieces, String s)
+    {
+        // Walk the pieces to match the string
+        // There are implicit stars between each piece,
+        // and the first and last pieces might be "" to anchor the match.
+        // assert (pieces.length > 1)
+        // minimal case is <string>*<string>
+
+        boolean result = true;
+        int len = pieces.size();
+
+        // Special case, if there is only one piece, then
+        // we must perform an equality test.
+        if (len == 1)
+        {
+            return s.equals(pieces.get(0));
+        }
+
+        // Otherwise, check whether the pieces match
+        // the specified string.
+
+        int index = 0;
+
+        loop: for (int i = 0; i < len; i++)
+        {
+            String piece = pieces.get(i);
+
+            // If this is the first piece, then make sure the
+            // string starts with it.
+            if (i == 0)
+            {
+                if (!s.startsWith(piece))
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // If this is the last piece, then make sure the
+            // string ends with it.
+            if (i == len - 1)
+            {
+                if (s.endsWith(piece))
+                {
+                    result = true;
+                }
+                else
+                {
+                    result = false;
+                }
+                break loop;
+            }
+
+            // If this is neither the first or last piece, then
+            // make sure the string contains it.
+            if ((i > 0) && (i < (len - 1)))
+            {
+                index = s.indexOf(piece, index);
+                if (index < 0)
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // Move string index beyond the matching piece.
+            index += piece.length();
+        }
+
+        return result;
+    }
+
+    private static int skipWhitespace(String s, int startIdx)
+    {
+        int len = s.length();
+        while ((startIdx < len) && Character.isWhitespace(s.charAt(startIdx)))
+        {
+            startIdx++;
+        }
+        return startIdx;
+    }
+}
\ No newline at end of file

Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,1044 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.osgi.framework.AllServiceListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServicePermission;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.launch.Framework;
+
+import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class EventDispatcher
+{
+    private final ServiceRegistry m_registry;
+    private Map<BundleContext, List<ListenerInfo>> m_fwkListeners = Collections.EMPTY_MAP;
+    private Map<BundleContext, List<ListenerInfo>> m_bndlListeners = Collections.EMPTY_MAP;
+    private Map<BundleContext, List<ListenerInfo>> m_syncBndlListeners = Collections.EMPTY_MAP;
+    private Map<BundleContext, List<ListenerInfo>> m_svcListeners = Collections.EMPTY_MAP;
+    // A single thread is used to deliver events for all dispatchers.
+    private static Thread m_thread = null;
+    private final static String m_threadLock = new String("thread lock");
+    private static int m_references = 0;
+    private static volatile boolean m_stopping = false;
+    // List of requests.
+    private static final List<Request> m_requestList = new ArrayList<Request>();
+    // Pooled requests to avoid memory allocation.
+    private static final List<Request> m_requestPool = new ArrayList<Request>();
+
+    private static final boolean m_sync = "true".equalsIgnoreCase(System
+            .getProperty("de.kalpatec.pojosr.framework.events.sync"));
+    
+    public EventDispatcher(ServiceRegistry registry)
+    {
+        m_registry = registry;
+    }
+
+    public void startDispatching()
+    {
+        synchronized (m_threadLock)
+        {
+            // Start event dispatching thread if necessary.
+            if (m_thread == null || !m_thread.isAlive())
+            {
+                m_stopping = false;
+
+                if (!m_sync)
+                {
+                    m_thread = new Thread(new Runnable()
+                    {
+                        public void run()
+                        {
+                            try
+                            {
+                                EventDispatcher.run();
+                            }
+                            finally
+                            {
+                                // Ensure we update state even if stopped by
+                                // external cause
+                                // e.g. an Applet VM forceably killing threads
+                                synchronized (m_threadLock)
+                                {
+                                    m_thread = null;
+                                    m_stopping = false;
+                                    m_references = 0;
+                                    m_threadLock.notifyAll();
+                                }
+                            }
+                        }
+                    }, "FelixDispatchQueue");
+                    m_thread.start();
+                }
+            }
+
+            // reference counting and flags
+            m_references++;
+        }
+    }
+
+    public void stopDispatching()
+    {
+        synchronized (m_threadLock)
+        {
+            // Return if already dead or stopping.
+            if (m_thread == null || m_stopping)
+            {
+                return;
+            }
+
+            // decrement use counter, don't continue if there are users
+            m_references--;
+            if (m_references > 0)
+            {
+                return;
+            }
+
+            m_stopping = true;
+        }
+
+        // Signal dispatch thread.
+        synchronized (m_requestList)
+        {
+            m_requestList.notify();
+        }
+
+        // Use separate lock for shutdown to prevent any chance of nested lock
+        // deadlock
+        synchronized (m_threadLock)
+        {
+            while (m_thread != null)
+            {
+                try
+                {
+                    m_threadLock.wait();
+                }
+                catch (InterruptedException ex)
+                {
+                }
+            }
+        }
+    }
+
+    public Filter addListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
+    {
+        // Verify the listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // See if we can simply update the listener, if so then
+        // return immediately.
+        Filter oldFilter = updateListener(bc, clazz, l, filter);
+        if (oldFilter != null)
+        {
+            return oldFilter;
+        }
+
+        // Lock the object to add the listener.
+        synchronized (this)
+        {
+            // Verify that the bundle context is still valid.
+            try
+            {
+                bc.getBundle();
+            }
+            catch (IllegalStateException ex)
+            {
+                // Bundle context is no longer valid, so just return.
+            }
+
+            Map<BundleContext, List<ListenerInfo>> listeners = null;
+            Object acc = null;
+
+            if (clazz == FrameworkListener.class)
+            {
+                listeners = m_fwkListeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    listeners = m_syncBndlListeners;
+                }
+                else
+                {
+                    listeners = m_bndlListeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                // Remember security context for filtering service events.
+               /* Object sm = System.getSecurityManager();
+                 if (sm != null)
+                 {
+                 acc = ((SecurityManager) sm).getSecurityContext();
+                 }*/
+                // We need to create a Set for keeping track of matching service
+                // registrations so we can fire ServiceEvent.MODIFIED_ENDMATCH
+                // events. We need a Set even if filter is null, since the
+                // listener can be updated and have a filter added later.
+                listeners = m_svcListeners;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unknown listener: " + l.getClass());
+            }
+
+            // Add listener.
+            ListenerInfo info =
+                new ListenerInfo(bc.getBundle(), bc, clazz, l, filter, acc, false);
+            listeners = addListenerInfo(listeners, info);
+
+            if (clazz == FrameworkListener.class)
+            {
+                m_fwkListeners = listeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    m_syncBndlListeners = listeners;
+                }
+                else
+                {
+                    m_bndlListeners = listeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                m_svcListeners = listeners;
+            }
+        }
+        return null;
+    }
+
+    public ListenerHook.ListenerInfo removeListener(
+        BundleContext bc, Class clazz, EventListener l)
+    {
+        ListenerHook.ListenerInfo returnInfo = null;
+
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // Lock the object to remove the listener.
+        synchronized (this)
+        {
+            Map<BundleContext, List<ListenerInfo>> listeners = null;
+
+            if (clazz == FrameworkListener.class)
+            {
+                listeners = m_fwkListeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    listeners = m_syncBndlListeners;
+                }
+                else
+                {
+                    listeners = m_bndlListeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                listeners = m_svcListeners;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unknown listener: " + l.getClass());
+            }
+
+            // Try to find the instance in our list.
+            int idx = -1;
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                List<ListenerInfo> infos = entry.getValue();
+                for (int i = 0; i < infos.size(); i++)
+                {
+                    ListenerInfo info = infos.get(i);
+                    if (info.getBundleContext().equals(bc)
+                        && (info.getListenerClass() == clazz)
+                        && (info.getListener() == l))
+                    {
+                        // For service listeners, we must return some info about
+                        // the listener for the ListenerHook callback.
+                        if (ServiceListener.class == clazz)
+                        {
+                            returnInfo = new ListenerInfo(infos.get(i), true);
+                        }
+                        idx = i;
+                        break;
+                    }
+                }
+            }
+
+            // If we have the instance, then remove it.
+            if (idx >= 0)
+            {
+                listeners = removeListenerInfo(listeners, bc, idx);
+            }
+
+            if (clazz == FrameworkListener.class)
+            {
+                m_fwkListeners = listeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    m_syncBndlListeners = listeners;
+                }
+                else
+                {
+                    m_bndlListeners = listeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                m_svcListeners = listeners;
+            }
+        }
+
+        // Return information about the listener; this is null
+        // for everything but service listeners.
+        return returnInfo;
+    }
+
+    public void removeListeners(BundleContext bc)
+    {
+        if (bc == null)
+        {
+            return;
+        }
+
+        synchronized (this)
+        {
+            // Remove all framework listeners associated with the specified bundle.
+            m_fwkListeners = removeListenerInfos(m_fwkListeners, bc);
+
+            // Remove all bundle listeners associated with the specified bundle.
+            m_bndlListeners = removeListenerInfos(m_bndlListeners, bc);
+
+            // Remove all synchronous bundle listeners associated with
+            // the specified bundle.
+            m_syncBndlListeners = removeListenerInfos(m_syncBndlListeners, bc);
+
+            // Remove all service listeners associated with the specified bundle.
+            m_svcListeners = removeListenerInfos(m_svcListeners, bc);
+        }
+    }
+
+    public Filter updateListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
+    {
+        if (clazz == ServiceListener.class)
+        {
+            synchronized (this)
+            {
+                // Verify that the bundle context is still valid.
+                try
+                {
+                    bc.getBundle();
+                }
+                catch (IllegalStateException ex)
+                {
+                    // Bundle context is no longer valid, so just return.
+                }
+
+                // See if the service listener is already registered; if so then
+                // update its filter per the spec.
+                List<ListenerInfo> infos = m_svcListeners.get(bc);
+                for (int i = 0; (infos != null) && (i < infos.size()); i++)
+                {
+                    ListenerInfo info = infos.get(i);
+                    if (info.getBundleContext().equals(bc)
+                        && (info.getListenerClass() == clazz)
+                        && (info.getListener() == l))
+                    {
+                        // The spec says to update the filter in this case.
+                        Filter oldFilter = info.getParsedFilter();
+                        ListenerInfo newInfo = new ListenerInfo(
+                            info.getBundle(),
+                            info.getBundleContext(),
+                            info.getListenerClass(),
+                            info.getListener(),
+                            filter,
+                            info.getSecurityContext(),
+                            info.isRemoved());
+                        m_svcListeners = updateListenerInfo(m_svcListeners, i, newInfo);
+                        return oldFilter;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns all existing service listener information into a collection of
+     * ListenerHook.ListenerInfo objects. This is used the first time a listener
+     * hook is registered to synchronize it with the existing set of listeners.
+     *
+     * @return Returns all existing service listener information into a
+     * collection of ListenerHook.ListenerInfo objects
+     *
+     */
+    public Collection<ListenerHook.ListenerInfo> getAllServiceListeners()
+    {
+        List<ListenerHook.ListenerInfo> listeners = new ArrayList<ListenerHook.ListenerInfo>();
+        synchronized (this)
+        {
+            for (Entry<BundleContext, List<ListenerInfo>> entry : m_svcListeners.entrySet())
+            {
+                listeners.addAll(entry.getValue());
+            }
+        }
+        return listeners;
+    }
+
+    public void fireFrameworkEvent(FrameworkEvent event)
+    {
+        // Take a snapshot of the listener array.
+        Map<BundleContext, List<ListenerInfo>> listeners = null;
+        synchronized (this)
+        {
+            listeners = m_fwkListeners;
+        }
+
+        // Fire all framework listeners on a separate thread.
+        fireEventAsynchronously(this, Request.FRAMEWORK_EVENT, listeners, event);
+    }
+
+    public void fireBundleEvent(BundleEvent event)
+    {
+        // Take a snapshot of the listener array.
+        Map<BundleContext, List<ListenerInfo>> listeners = null;
+        Map<BundleContext, List<ListenerInfo>> syncListeners = null;
+        synchronized (this)
+        {
+            listeners = m_bndlListeners;
+            syncListeners = m_syncBndlListeners;
+        }
+
+        // Create a whitelist of bundle context for bundle listeners,
+        // if we have hooks.
+        Set<BundleContext> whitelist = createWhitelistFromHooks(event, event.getBundle(),
+            listeners, syncListeners, org.osgi.framework.hooks.bundle.EventHook.class);
+
+        // If we have a whitelist, then create copies of only the whitelisted
+        // listeners.
+        if (whitelist != null)
+        {
+            Map<BundleContext, List<ListenerInfo>> copy =
+                new HashMap<BundleContext, List<ListenerInfo>>();
+            for (BundleContext bc : whitelist)
+            {
+                List<ListenerInfo> infos = listeners.get(bc);
+                if (infos != null)
+                {
+                    copy.put(bc, infos);
+                }
+            }
+            listeners = copy;
+            copy = new HashMap<BundleContext, List<ListenerInfo>>();
+            for (BundleContext bc : whitelist)
+            {
+                List<ListenerInfo> infos = syncListeners.get(bc);
+                if (infos != null)
+                {
+                    copy.put(bc, infos);
+                }
+            }
+            syncListeners = copy;
+        }
+
+        // Fire synchronous bundle listeners immediately on the calling thread.
+        fireEventImmediately(
+            this, Request.BUNDLE_EVENT, syncListeners, event, null);
+
+        // The spec says that asynchronous bundle listeners do not get events
+        // of types STARTING, STOPPING, or LAZY_ACTIVATION.
+        if ((event.getType() != BundleEvent.STARTING)
+            && (event.getType() != BundleEvent.STOPPING)
+            && (event.getType() != BundleEvent.LAZY_ACTIVATION))
+        {
+            // Fire asynchronous bundle listeners on a separate thread.
+            fireEventAsynchronously(
+                this, Request.BUNDLE_EVENT, listeners, event);
+        }
+    }
+
+    public void fireServiceEvent(
+        final ServiceEvent event, final Dictionary oldProps, final Framework felix)
+    {
+        // Take a snapshot of the listener array.
+        Map<BundleContext, List<ListenerInfo>> listeners = null;
+        synchronized (this)
+        {
+            listeners = m_svcListeners;
+        }
+
+        // Use service registry hooks to filter target listeners.
+        listeners = filterListenersUsingHooks(event, felix, listeners);
+
+        // Fire all service events immediately on the calling thread.
+        fireEventImmediately(
+            this, Request.SERVICE_EVENT, listeners, event, oldProps);
+    }
+
+// TODO: OSGi R4.3 - This is ugly and inefficient.
+    private Map<BundleContext, List<ListenerInfo>> filterListenersUsingHooks(
+        ServiceEvent event, Framework felix, Map<BundleContext, List<ListenerInfo>> listeners)
+    {
+        Set<ServiceReference<org.osgi.framework.hooks.service.EventHook>> ehs =
+            m_registry.getHooks(org.osgi.framework.hooks.service.EventHook.class);
+        if ((ehs != null) && !ehs.isEmpty())
+        {
+            // Create a whitelist of bundle context for bundle listeners,
+            // if we have hooks.
+            Set<BundleContext> whitelist = createWhitelistFromHooks(event, felix,
+                listeners, null, org.osgi.framework.hooks.service.EventHook.class);
+
+            // If we have a whitelist, then create copies of only the whitelisted
+            // listeners.
+            if (whitelist != null)
+            {
+                Map<BundleContext, List<ListenerInfo>> copy =
+                    new HashMap<BundleContext, List<ListenerInfo>>();
+                for (BundleContext bc : whitelist)
+                {
+                    copy.put(bc, listeners.get(bc));
+                }
+                listeners = copy;
+            }
+        }
+
+        Set<ServiceReference<org.osgi.framework.hooks.service.EventListenerHook>> elhs =
+            m_registry.getHooks(org.osgi.framework.hooks.service.EventListenerHook.class);
+        if ((elhs != null) && !elhs.isEmpty())
+        {
+            // Create shrinkable map with shrinkable collections.
+            Map<BundleContext, Collection<ListenerHook.ListenerInfo>> shrinkableMap =
+                new HashMap<BundleContext, Collection<ListenerHook.ListenerInfo>>();
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                Collection shrinkableCollection =
+                    new ShrinkableCollection(new ArrayList(entry.getValue()));
+                shrinkableMap.put(
+                    entry.getKey(),
+                    (Collection<ListenerHook.ListenerInfo>) shrinkableCollection);
+            }
+            shrinkableMap =
+                new ShrinkableMap<BundleContext, Collection<ListenerHook.ListenerInfo>>(shrinkableMap);
+            for (ServiceReference<org.osgi.framework.hooks.service.EventListenerHook> sr : elhs)
+            {
+                if (felix != null)
+                {
+                    org.osgi.framework.hooks.service.EventListenerHook elh = null;
+                    try
+                    {
+                        elh = m_registry.getService(felix, sr);
+                    }
+                    catch (Exception ex)
+                    {
+                        // If we can't get the hook, then ignore it.
+                    }
+                    if (elh != null)
+                    {
+                        try
+                        {
+                            elh.event(event, shrinkableMap);
+                        }
+                        catch (Throwable th)
+                        {
+                            System.out.println("Problem invoking event hook");
+                            th.printStackTrace();
+                        }
+                        finally
+                        {
+                            m_registry.ungetService(felix, sr);
+                        }
+                    }
+                }
+            }
+// TODO: OSGi R4.3 - Should check and only do this if there was a change.
+//       Also, it is inefficient to have to create new lists for the values.
+            Map<BundleContext, List<ListenerInfo>> newMap =
+                new HashMap<BundleContext, List<ListenerInfo>>();
+            for (Entry entry : shrinkableMap.entrySet())
+            {
+                if (!((Collection) entry.getValue()).isEmpty())
+                {
+                    newMap.put((BundleContext) entry.getKey(),
+                        new ArrayList<ListenerInfo>((Collection) entry.getValue()));
+                }
+            }
+            listeners = newMap;
+        }
+
+        return listeners;
+    }
+
+    private <T> Set<BundleContext> createWhitelistFromHooks(
+        EventObject event, Bundle bundle,
+        Map<BundleContext, List<ListenerInfo>> listeners1,
+        Map<BundleContext, List<ListenerInfo>> listeners2,
+        Class<T> hookClass)
+    {
+        // Create a whitelist of bundle context, if we have hooks.
+        Set<BundleContext> whitelist = null;
+        Set<ServiceReference<T>> hooks = m_registry.getHooks(hookClass);
+        if ((hooks != null) && !hooks.isEmpty())
+        {
+            whitelist = new HashSet<BundleContext>();
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners1.entrySet())
+            {
+                whitelist.add(entry.getKey());
+            }
+            if (listeners2 != null)
+            {
+                for (Entry<BundleContext, List<ListenerInfo>> entry : listeners2.entrySet())
+                {
+                    whitelist.add(entry.getKey());
+                }
+            }
+
+            int originalSize = whitelist.size();
+            ShrinkableCollection<BundleContext> shrinkable =
+                new ShrinkableCollection<BundleContext>(whitelist);
+            for (ServiceReference<T> sr : hooks)
+            {
+                if (bundle != null)
+                {
+                    T eh = null;
+                    try
+                    {
+                        eh = m_registry.getService(bundle, sr);
+                    }
+                    catch (Exception ex)
+                    {
+                        // If we can't get the hook, then ignore it.
+                    }
+                    if (eh != null)
+                    {
+                        try
+                        {
+                            if (eh instanceof org.osgi.framework.hooks.service.EventHook)
+                            {
+                                ((org.osgi.framework.hooks.service.EventHook) eh).event((ServiceEvent) event, shrinkable);
+                            }
+                            else if (eh instanceof org.osgi.framework.hooks.bundle.EventHook)
+                            {
+                                ((org.osgi.framework.hooks.bundle.EventHook) eh).event((BundleEvent) event, shrinkable);
+                            }
+                        }
+                        catch (Throwable th)
+                        {
+                            System.out.println("Problem invoking event hook");
+                            th.printStackTrace();
+                        }
+                        finally
+                        {
+                            m_registry.ungetService(bundle, sr);
+                        }
+                    }
+                }
+            }
+            // If the whitelist hasn't changed, then null it to avoid having
+            // to do whitelist lookups during event delivery.
+            if (originalSize == whitelist.size())
+            {
+                whitelist = null;
+            }
+        }
+        return whitelist;
+    }
+
+    private static void fireEventAsynchronously(
+        EventDispatcher dispatcher, int type,
+        Map<BundleContext, List<ListenerInfo>> listeners,
+        EventObject event)
+    {
+        if (!m_sync)
+        {
+            // TODO: should possibly check this within thread lock, seems to be
+            // ok
+            // though without
+            // If dispatch thread is stopped, then ignore dispatch request.
+            if (m_stopping || m_thread == null)
+            {
+                return;
+            }
+
+            // First get a request from the pool or create one if necessary.
+            Request req = null;
+            synchronized (m_requestPool)
+            {
+                if (m_requestPool.size() > 0)
+                {
+                    req = m_requestPool.remove(0);
+                }
+                else
+                {
+                    req = new Request();
+                }
+            }
+
+            // Initialize dispatch request.
+            req.m_dispatcher = dispatcher;
+            req.m_type = type;
+            req.m_listeners = listeners;
+            req.m_event = event;
+
+            // Lock the request list.
+            synchronized (m_requestList)
+            {
+                // Add our request to the list.
+                m_requestList.add(req);
+                // Notify the dispatch thread that there is work to do.
+                m_requestList.notify();
+            }
+        }
+        else
+        {
+            fireEventImmediately(dispatcher, type, listeners, event, null);
+        }
+    }
+
+    private static void fireEventImmediately(
+        EventDispatcher dispatcher, int type,
+        Map<BundleContext, List<ListenerInfo>> listeners,
+        EventObject event, Dictionary oldProps)
+    {
+        if (!listeners.isEmpty())
+        {
+            // Notify appropriate listeners.
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                for (ListenerInfo info : entry.getValue())
+                {
+                    Bundle bundle = info.getBundle();
+                    EventListener l = info.getListener();
+                    Filter filter = info.getParsedFilter();
+                    Object acc = info.getSecurityContext();
+
+                    try
+                    {
+                        if (type == Request.FRAMEWORK_EVENT)
+                        {
+                            invokeFrameworkListenerCallback(bundle, l, event);
+                        }
+                        else if (type == Request.BUNDLE_EVENT)
+                        {
+                            invokeBundleListenerCallback(bundle, l, event);
+                        }
+                        else if (type == Request.SERVICE_EVENT)
+                        {
+                            invokeServiceListenerCallback(
+                                bundle, l, filter, acc, event, oldProps);
+                        }
+                    }
+                    catch (Throwable th)
+                    {
+                        if ((type != Request.FRAMEWORK_EVENT)
+                            || (((FrameworkEvent) event).getType() != FrameworkEvent.ERROR))
+                        {
+                            System.out.println("EventDispatcher: Error during dispatch.");
+                            th.printStackTrace();
+                            dispatcher.fireFrameworkEvent(
+                                new FrameworkEvent(FrameworkEvent.ERROR, bundle, th));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static void invokeFrameworkListenerCallback(
+        Bundle bundle, final EventListener l, final EventObject event)
+    {
+        // The spec says only active bundles receive asynchronous events,
+        // but we will include starting bundles too otherwise
+        // it is impossible to see everything.
+        if ((bundle.getState() == Bundle.STARTING)
+            || (bundle.getState() == Bundle.ACTIVE))
+        {
+            ((FrameworkListener) l).frameworkEvent((FrameworkEvent) event);
+
+        }
+    }
+
+    private static void invokeBundleListenerCallback(
+        Bundle bundle, final EventListener l, final EventObject event)
+    {
+        // A bundle listener is either synchronous or asynchronous.
+        // If the bundle listener is synchronous, then deliver the
+        // event to bundles with a state of STARTING, STOPPING, or
+        // ACTIVE. If the listener is asynchronous, then deliver the
+        // event only to bundles that are STARTING or ACTIVE.
+        if (((SynchronousBundleListener.class.isAssignableFrom(l.getClass()))
+            && ((bundle.getState() == Bundle.STARTING)
+            || (bundle.getState() == Bundle.STOPPING)
+            || (bundle.getState() == Bundle.ACTIVE)))
+            || ((bundle.getState() == Bundle.STARTING)
+            || (bundle.getState() == Bundle.ACTIVE)))
+        {
+            ((BundleListener) l).bundleChanged((BundleEvent) event);
+        }
+    }
+
+    private static void invokeServiceListenerCallback(Bundle bundle,
+        final EventListener l, Filter filter, Object acc,
+        final EventObject event, final Dictionary oldProps)
+    {
+        // Service events should be delivered to STARTING,
+        // STOPPING, and ACTIVE bundles.
+        if ((bundle.getState() != Bundle.STARTING)
+            && (bundle.getState() != Bundle.STOPPING)
+            && (bundle.getState() != Bundle.ACTIVE))
+        {
+            return;
+        }
+
+        // Check that the bundle has permission to get at least
+        // one of the service interfaces; the objectClass property
+        // of the service stores its service interfaces.
+        ServiceReference ref = ((ServiceEvent) event).getServiceReference();
+
+        boolean hasPermission = true;
+        if (hasPermission)
+        {
+            // Dispatch according to the filter.
+            boolean matched = (filter == null)
+                || filter.match(((ServiceEvent) event).getServiceReference());
+
+            if (matched)
+            {
+                ((ServiceListener) l)
+                    .serviceChanged((ServiceEvent) event);
+            }
+            // We need to send an MODIFIED_ENDMATCH event if the listener
+            // matched previously.
+            else if (((ServiceEvent) event).getType() == ServiceEvent.MODIFIED)
+            {
+                if (filter.match(oldProps))
+                {
+                    final ServiceEvent se = new ServiceEvent(
+                        ServiceEvent.MODIFIED_ENDMATCH,
+                        ((ServiceEvent) event).getServiceReference());
+                    ((ServiceListener) l).serviceChanged(se);
+
+                }
+            }
+        }
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> addListenerInfo(
+        Map<BundleContext, List<ListenerInfo>> listeners, ListenerInfo info)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and make a copy so we can modify it.
+        List<ListenerInfo> infos = copy.remove(info.getBundleContext());
+        if (infos == null)
+        {
+            infos = new ArrayList<ListenerInfo>();
+        }
+        else
+        {
+            infos = new ArrayList<ListenerInfo>(infos);
+        }
+        // Add the new listener info.
+        infos.add(info);
+        // Put the listeners back into the copy of the map and return it.
+        copy.put(info.getBundleContext(), infos);
+        return copy;
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> updateListenerInfo(
+        Map<BundleContext, List<ListenerInfo>> listeners, int idx,
+        ListenerInfo info)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and make a copy so we can modify it.
+        List<ListenerInfo> infos = copy.remove(info.getBundleContext());
+        if (infos != null)
+        {
+            infos = new ArrayList<ListenerInfo>(infos);
+            // Update the new listener info.
+            infos.set(idx, info);
+            // Put the listeners back into the copy of the map and return it.
+            copy.put(info.getBundleContext(), infos);
+            return copy;
+        }
+        return listeners;
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> removeListenerInfo(
+        Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc, int idx)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and make a copy so we can modify it.
+        List<ListenerInfo> infos = copy.remove(bc);
+        if (infos != null)
+        {
+            infos = new ArrayList<ListenerInfo>(infos);
+            // Remove the listener info.
+            infos.remove(idx);
+            if (!infos.isEmpty())
+            {
+                // Put the listeners back into the copy of the map and return it.
+                copy.put(bc, infos);
+            }
+            return copy;
+        }
+        return listeners;
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> removeListenerInfos(
+        Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and return the copy.
+        copy.remove(bc);
+        return copy;
+    }
+
+    /**
+     * This is the dispatching thread's main loop.
+     *
+     */
+    private static void run()
+    {
+        Request req = null;
+        while (true)
+        {
+            // Lock the request list so we can try to get a
+            // dispatch request from it.
+            synchronized (m_requestList)
+            {
+                // Wait while there are no requests to dispatch. If the
+                // dispatcher thread is supposed to stop, then let the
+                // dispatcher thread exit the loop and stop.
+                while (m_requestList.isEmpty() && !m_stopping)
+                {
+                    // Wait until some signals us for work.
+                    try
+                    {
+                        m_requestList.wait();
+                    }
+                    catch (InterruptedException ex)
+                    {
+                        // Not much we can do here except for keep waiting.
+                    }
+                }
+
+                // If there are no events to dispatch and shutdown
+                // has been called then exit, otherwise dispatch event.
+                if (m_requestList.isEmpty() && m_stopping)
+                {
+                    return;
+                }
+
+                // Get the dispatch request.
+                req = m_requestList.remove(0);
+            }
+
+            // Deliver event outside of synchronized block
+            // so that we don't block other requests from being
+            // queued during event processing.
+            // NOTE: We don't catch any exceptions here, because
+            // the invoked method shields us from exceptions by
+            // catching Throwables when it invokes callbacks.
+            fireEventImmediately(
+                req.m_dispatcher, req.m_type, req.m_listeners,
+                req.m_event, null);
+
+            // Put dispatch request in cache.
+            synchronized (m_requestPool)
+            {
+                req.m_dispatcher = null;
+                req.m_type = -1;
+                req.m_listeners = null;
+                req.m_event = null;
+                m_requestPool.add(req);
+            }
+        }
+    }
+
+    private static class Request
+    {
+        public static final int FRAMEWORK_EVENT = 0;
+        public static final int BUNDLE_EVENT = 1;
+        public static final int SERVICE_EVENT = 2;
+        public EventDispatcher m_dispatcher = null;
+        public int m_type = -1;
+        public Map<BundleContext, List<ListenerInfo>> m_listeners = null;
+        public EventObject m_event = null;
+    }
+}

Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,85 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+public class ListenerHookInfoImpl implements ListenerHook.ListenerInfo
+{
+    private final BundleContext m_context;
+    private final ServiceListener m_listener;
+    private final String m_filter;
+    private boolean m_removed;
+
+    public ListenerHookInfoImpl(BundleContext context,
+            ServiceListener listener, String filter, boolean removed)
+    {
+        m_context = context;
+        m_listener = listener;
+        m_filter = filter;
+        m_removed = removed;
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return m_context;
+    }
+
+    public String getFilter()
+    {
+        return m_filter;
+    }
+
+    public boolean isRemoved()
+    {
+        return m_removed;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+
+        if (!(obj instanceof ListenerHookInfoImpl))
+        {
+            return false;
+        }
+
+        ListenerHookInfoImpl other = (ListenerHookInfoImpl) obj;
+        return other.m_listener == m_listener
+                && (m_filter == null ? other.m_filter == null : m_filter
+                        .equals(other.m_filter));
+    }
+
+    public int hashCode()
+    {
+        int rc = 17;
+
+        rc = 37 * rc + m_listener.hashCode();
+        if (m_filter != null)
+        {
+            rc = 37 * rc + m_filter.hashCode();
+        }
+        return rc;
+    }
+}
\ No newline at end of file