Applying patch for RFC-126 service hooks. (FELIX-905, FELIX-906)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@751101 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 1f1f210..5b23ae0 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -32,7 +32,7 @@
import org.apache.felix.framework.util.manifestparser.*;
import org.apache.felix.moduleloader.*;
import org.osgi.framework.*;
-import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.hooks.service.*;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.startlevel.StartLevel;
@@ -611,6 +611,8 @@
// Create service registry.
m_registry = new ServiceRegistry(m_logger);
+ m_dispatcher.setServiceRegistry(m_registry);
+
// Add a listener to the service registry; this is
// used to distribute service registry events to
// service listeners.
@@ -2412,11 +2414,11 @@
bundle, ServiceListener.class, l, (f == null) ? null : new FilterImpl(m_logger, f));
// Invoke the ListenerHook.added() on all hooks.
- ListenerHook[] hooks = m_registry.getListenerHooks();
+ List listenerHooks = m_registry.getListenerHooks();
Collection c = Collections.singleton(new ListenerHookInfoImpl(bundle.getBundleContext(), f));
- for (int i = 0; i < hooks.length; i++)
+ for (int i = 0; i < listenerHooks.size(); i++)
{
- hooks[i].added(c);
+ ((ListenerHook) listenerHooks.get(i)).added(c);
}
}
@@ -2435,11 +2437,11 @@
if (listener != null)
{
// Invoke the ListenerHook.removed() on all hooks.
- ListenerHook[] hooks = m_registry.getListenerHooks();
+ List listenerHooks = m_registry.getListenerHooks();
Collection c = Collections.singleton(listener);
- for (int i = 0; i < hooks.length; i++)
+ for (int i = 0; i < listenerHooks.size(); i++)
{
- hooks[i].removed(c);
+ ((ListenerHook) listenerHooks.get(i)).removed(c);
}
}
}
@@ -2591,6 +2593,18 @@
if (refList.size() > 0)
{
+ // activate findhooks
+ List findHooks = m_registry.getFindHooks();
+ for (int i = 0; i < findHooks.size(); i++)
+ {
+ ((FindHook) findHooks.get(i)).find(
+ bundle.getBundleContext(),
+ className,
+ expr,
+ !checkAssignable,
+ new ShrinkableCollection(refList));
+ }
+
return (ServiceReference[]) refList.toArray(new ServiceReference[refList.size()]);
}
diff --git a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
index 2d60732..60258aa 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
@@ -22,7 +22,7 @@
import org.apache.felix.framework.util.FelixConstants;
import org.osgi.framework.*;
-import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.hooks.service.*;
public class ServiceRegistry
{
@@ -39,7 +39,12 @@
private ServiceListener m_serviceListener = null;
- private final List m_listenerHooks = new ArrayList();
+ private final Object m_eventHookLock = new Object();
+ private Object[] m_eventHooks = new Object[0];
+ private final Object m_findHookLock = new Object();
+ private Object[] m_findHooks = new Object[0];
+ private final Object m_listenerHookLock = new Object();
+ private Object[] m_listenerHooks = new Object[0];
public ServiceRegistry(Logger logger)
{
@@ -728,13 +733,37 @@
private void addHooks(String[] classNames, Object svcObj)
{
- if (isHook(classNames, ListenerHook.class, svcObj))
+ if (isHook(classNames, EventHook.class, svcObj))
{
- synchronized (m_listenerHooks)
+ synchronized (m_eventHookLock)
{
- m_listenerHooks.add(svcObj);
+ m_eventHooks = addToArray(m_eventHooks, svcObj);
}
}
+
+ if (isHook(classNames, FindHook.class, svcObj))
+ {
+ synchronized (m_findHookLock)
+ {
+ m_findHooks = addToArray(m_findHooks, svcObj);
+ }
+ }
+
+ if (isHook(classNames, ListenerHook.class, svcObj))
+ {
+ synchronized (m_listenerHookLock)
+ {
+ m_listenerHooks = addToArray(m_listenerHooks, svcObj);
+ }
+ }
+ }
+
+ private static Object[] addToArray(Object[] src, Object svcObj)
+ {
+ Object[] dst = new Object[src.length + 1];
+ System.arraycopy(src, 0, dst, 0, src.length);
+ dst[src.length] = svcObj;
+ return dst;
}
boolean isHook(String[] classNames, Class hookClass, Object svcObj)
@@ -755,21 +784,87 @@
private void removeHook(Object svcObj)
{
+ if (svcObj instanceof EventHook)
+ {
+ synchronized (m_eventHookLock)
+ {
+ m_eventHooks = removeFromArray(m_eventHooks, svcObj);
+ }
+ }
+
+ if (svcObj instanceof FindHook)
+ {
+ synchronized (m_findHookLock)
+ {
+ m_findHooks = removeFromArray(m_findHooks, svcObj);
+ }
+ }
+
if (svcObj instanceof ListenerHook)
{
- synchronized (m_listenerHooks)
+ synchronized (m_listenerHookLock)
{
- m_listenerHooks.remove(svcObj);
+ m_listenerHooks = removeFromArray(m_listenerHooks, svcObj);
}
}
}
- ListenerHook[] getListenerHooks()
+ private static Object[] removeFromArray(Object[] src, Object svcObj)
+ {
+ Object[] dst = src;
+
+ int idx = -1;
+ for (int i = 0; i < src.length; i++)
+ {
+ if (src[i].equals(svcObj))
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx >= 0)
+ {
+ if (src.length == 1)
+ {
+ dst = new Object[0];
+ }
+ else
+ {
+ dst = new Object[src.length - 1];
+ System.arraycopy(dst, 0, src, 0, idx);
+ if (idx < dst.length)
+ {
+ System.arraycopy(
+ src, idx + 1, dst, idx, dst.length - idx);
+ }
+ }
+ }
+
+ return dst;
+ }
+
+ public List getEventHooks()
+ {
+ synchronized (m_eventHooks)
+ {
+ return Collections.unmodifiableList(Arrays.asList(m_eventHooks));
+ }
+ }
+
+ List getFindHooks()
+ {
+ synchronized (m_findHooks)
+ {
+ return Collections.unmodifiableList(Arrays.asList(m_findHooks));
+ }
+ }
+
+ List getListenerHooks()
{
synchronized (m_listenerHooks)
{
- return (ListenerHook[])
- m_listenerHooks.toArray(new ListenerHook[m_listenerHooks.size()]);
+ return Collections.unmodifiableList(Arrays.asList(m_listenerHooks));
}
}
diff --git a/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java b/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java
index 5213202..96b7e97 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/EventDispatcher.java
@@ -24,11 +24,15 @@
import java.util.Collection;
import java.util.EventListener;
import java.util.EventObject;
-
+import java.util.Iterator;
import java.util.List;
+import java.util.NoSuchElementException;
+
import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.ServiceRegistry;
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.Constants;
@@ -41,6 +45,7 @@
import org.osgi.framework.ServiceReference;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.hooks.service.EventHook;
public class EventDispatcher
{
@@ -51,8 +56,8 @@
private static final int LISTENER_SECURITY_OFFSET = 4;
private static final int LISTENER_ARRAY_INCREMENT = 5;
- // Framework instance for this dispatcher.
private Logger m_logger = null;
+ private volatile ServiceRegistry m_serviceRegistry = null;
// Representation of an empty listener list.
private static final Object[] m_emptyList = new Object[0];
@@ -120,6 +125,11 @@
return eventDispatcher;
}
+ public void setServiceRegistry(ServiceRegistry sr)
+ {
+ m_serviceRegistry = sr;
+ }
+
public static void shutdown()
{
synchronized (m_threadLock)
@@ -605,6 +615,22 @@
listeners = m_serviceListeners;
}
+ if (m_serviceRegistry != null)
+ {
+ List eventHooks = m_serviceRegistry.getEventHooks();
+ if ((eventHooks != null) && (eventHooks.size() > 0))
+ {
+ ListenerBundleContextCollectionWrapper wrapper =
+ new ListenerBundleContextCollectionWrapper(listeners);
+ for (int i = 0; i < eventHooks.size(); i++)
+ {
+ ((EventHook) eventHooks.get(i)).event(event, wrapper);
+ }
+
+ listeners = wrapper.getListeners();
+ }
+ }
+
// Fire all service events immediately on the calling thread.
fireEventImmediately(m_logger, Request.SERVICE_EVENT, listeners, event);
}
@@ -885,6 +911,201 @@
}
}
+ static class ListenerBundleContextCollectionWrapper implements Collection
+ {
+ private Object[] m_listeners;
+
+ ListenerBundleContextCollectionWrapper(Object [] listeners)
+ {
+ m_listeners = listeners;
+ }
+
+ Object [] getListeners()
+ {
+ return m_listeners;
+ }
+
+ public boolean add(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean addAll(Collection c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear()
+ {
+ m_listeners = new Object[0];
+ }
+
+ public boolean contains(Object o)
+ {
+ return indexOf(o) >= 0;
+ }
+
+ public boolean containsAll(Collection c)
+ {
+ for (Iterator it = c.iterator(); it.hasNext(); )
+ {
+ if (!contains(it.next()))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private int indexOf(Object o)
+ {
+ if (!(o instanceof BundleContext))
+ {
+ return -1;
+ }
+
+ for (int i = m_listeners.length - LISTENER_ARRAY_INCREMENT;
+ i >= 0;
+ i -= LISTENER_ARRAY_INCREMENT)
+ {
+ Bundle bundle = (Bundle) m_listeners[i + LISTENER_BUNDLE_OFFSET];
+ if (bundle != null)
+ {
+ if (bundle.getBundleContext().equals(o))
+ {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public boolean isEmpty()
+ {
+ return m_listeners.length == 0;
+ }
+
+ public Iterator iterator()
+ {
+ return new WrapperIterator();
+ }
+
+ public boolean remove(Object o)
+ {
+ return removeIndex(indexOf(o));
+ }
+
+ private boolean removeIndex(int idx)
+ {
+ if (idx < 0)
+ {
+ return false;
+ }
+
+ Object [] newListeners = new Object[m_listeners.length - LISTENER_ARRAY_INCREMENT];
+ System.arraycopy(m_listeners, 0, newListeners, 0, idx);
+ System.arraycopy(m_listeners, idx + LISTENER_ARRAY_INCREMENT,
+ newListeners, idx, newListeners.length - idx);
+ m_listeners = newListeners;
+
+ return true;
+ }
+
+ public boolean removeAll(Collection c)
+ {
+ boolean rv = false;
+
+ for (Iterator it = c.iterator(); it.hasNext(); )
+ {
+ if (remove(it.next()))
+ {
+ rv = true;
+ }
+ }
+
+ return rv;
+ }
+
+ public boolean retainAll(Collection c)
+ {
+ boolean rv = false;
+
+ for (Iterator it = iterator(); it.hasNext(); )
+ {
+ if (!(c.contains(it.next())))
+ {
+ it.remove();
+ rv = true;
+ }
+ }
+
+ return rv;
+ }
+
+ public int size()
+ {
+ return m_listeners.length / LISTENER_ARRAY_INCREMENT;
+ }
+
+ public Object[] toArray()
+ {
+ Object [] array = new Object[size()];
+ int idx = 0;
+ for (Iterator it = iterator(); it.hasNext(); )
+ {
+ array[idx++] = it.next();
+ }
+ return array;
+ }
+
+ public Object[] toArray(Object[] a)
+ {
+ if (!(a.getClass().equals(Object[].class)))
+ {
+ throw new ArrayStoreException();
+ }
+ return toArray();
+ }
+
+ private class WrapperIterator implements Iterator
+ {
+ int curIdx = 0;
+ int lastIdx = -1;
+
+ private WrapperIterator() {}
+
+ public boolean hasNext()
+ {
+ return curIdx < m_listeners.length;
+ }
+
+ public Object next()
+ {
+ if (!hasNext())
+ {
+ throw new NoSuchElementException();
+ }
+
+ Bundle b = (Bundle) m_listeners[curIdx + LISTENER_BUNDLE_OFFSET];
+ lastIdx = curIdx;
+ curIdx += LISTENER_ARRAY_INCREMENT;
+ return b.getBundleContext();
+ }
+
+ public void remove()
+ {
+ if (lastIdx < 0)
+ {
+ throw new IllegalStateException();
+ }
+ removeIndex(lastIdx);
+
+ curIdx = lastIdx;
+ lastIdx = -1;
+ }
+ }
+ }
+
private static class Request
{
public static final int FRAMEWORK_EVENT = 0;
diff --git a/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java b/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java
new file mode 100644
index 0000000..5719e17
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/util/ShrinkableCollection.java
@@ -0,0 +1,110 @@
+/*
+ * 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.framework.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/** This collection wraps any other collection but prohibits calls to add
+ * elements to the collection.
+ */
+public class ShrinkableCollection implements Collection
+{
+ private final Collection m_delegate;
+
+ public ShrinkableCollection(Collection delegate)
+ {
+ m_delegate = delegate;
+ }
+
+ public boolean add(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean addAll(Collection c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear()
+ {
+ m_delegate.clear();
+ }
+
+ public boolean contains(Object o)
+ {
+ return m_delegate.contains(o);
+ }
+
+ public boolean containsAll(Collection c)
+ {
+ return m_delegate.containsAll(c);
+ }
+
+ public boolean equals(Object o)
+ {
+ return m_delegate.equals(o);
+ }
+
+ public int hashCode()
+ {
+ return m_delegate.hashCode();
+ }
+
+ public boolean isEmpty()
+ {
+ return m_delegate.isEmpty();
+ }
+
+ public Iterator iterator()
+ {
+ return m_delegate.iterator();
+ }
+
+ public boolean remove(Object o)
+ {
+ return m_delegate.remove(o);
+ }
+
+ public boolean removeAll(Collection c)
+ {
+ return m_delegate.removeAll(c);
+ }
+
+ public boolean retainAll(Collection c)
+ {
+ return m_delegate.retainAll(c);
+ }
+
+ public int size()
+ {
+ return m_delegate.size();
+ }
+
+ public Object[] toArray()
+ {
+ return m_delegate.toArray();
+ }
+
+ public Object[] toArray(Object[] a)
+ {
+ return m_delegate.toArray(a);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/osgi/framework/hooks/service/EventHook.java b/framework/src/main/java/org/osgi/framework/hooks/service/EventHook.java
new file mode 100644
index 0000000..7db29b3
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/hooks/service/EventHook.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) OSGi Alliance (2000, 2009). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.framework.hooks.service;
+
+import java.util.Collection;
+
+import org.osgi.framework.ServiceEvent;
+
+public interface EventHook {
+ void event(ServiceEvent event, Collection/* <? extends BundleContext> */contexts);
+}
diff --git a/framework/src/main/java/org/osgi/framework/hooks/service/FindHook.java b/framework/src/main/java/org/osgi/framework/hooks/service/FindHook.java
new file mode 100644
index 0000000..2d3af3b
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/hooks/service/FindHook.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) OSGi Alliance (2000, 2009). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.framework.hooks.service;
+
+import java.util.Collection;
+
+import org.osgi.framework.BundleContext;
+
+public interface FindHook {
+ void find(BundleContext context,
+ String name,
+ String filter,
+ boolean allServices,
+ Collection /* <? extends ServiceReference> */ references);
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherListenerWrapperTest.java b/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherListenerWrapperTest.java
new file mode 100644
index 0000000..741b3cb
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherListenerWrapperTest.java
@@ -0,0 +1,396 @@
+/*
+ * 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.framework.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import junit.framework.TestCase;
+
+import org.easymock.MockControl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+
+public class EventDispatcherListenerWrapperTest extends TestCase {
+ public void testRemove() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Bundle b2 = getMockBundle();
+ BundleContext bc2 = b2.getBundleContext();
+ Bundle b3 = getMockBundle();
+ BundleContext bc3 = b3.getBundleContext();
+ Bundle b4 = getMockBundle();
+ BundleContext bc4 = b4.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+
+ b2,
+ Integer.class,
+ new Object(),
+ "(some.other=filter)",
+ new Integer(15),
+
+ b3,
+ BundleContext.class,
+ new Object(),
+ null,
+ Boolean.TRUE,
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ assertEquals(3, c.size());
+ assertFalse(c.isEmpty());
+ assertTrue(c.contains(bc1));
+ assertTrue(c.contains(bc2));
+ assertTrue(c.contains(bc3));
+ assertFalse(c.contains(bc4));
+
+ assertTrue(c.remove(bc2));
+ assertEquals(2, c.size());
+ assertTrue(c.contains(bc1));
+ assertFalse(c.contains(bc2));
+ assertTrue(c.contains(bc3));
+ assertFalse(c.contains(bc4));
+ assertFalse("Already removed", c.remove(bc2));
+
+ Object [] actualListeners =
+ ((EventDispatcher.ListenerBundleContextCollectionWrapper) c).getListeners();
+ Object [] expectedListeners = new Object[10];
+ System.arraycopy(listeners, 0, expectedListeners, 0, 5);
+ System.arraycopy(listeners, 10, expectedListeners, 5, 5);
+ assertTrue(Arrays.equals(expectedListeners, actualListeners));
+
+ assertTrue(c.remove(bc1));
+ assertEquals(1, c.size());
+ assertFalse(c.contains(bc1));
+ assertFalse(c.contains(bc2));
+ assertTrue(c.contains(bc3));
+ assertFalse(c.contains(bc4));
+ assertFalse(c.isEmpty());
+
+ Object [] actualListeners2 =
+ ((EventDispatcher.ListenerBundleContextCollectionWrapper) c).getListeners();
+ Object [] expectedListeners2 = new Object[5];
+ System.arraycopy(listeners, 10, expectedListeners2, 0, 5);
+ assertTrue(Arrays.equals(expectedListeners2, actualListeners2));
+
+ assertTrue(c.remove(bc3));
+ assertEquals(0, c.size());
+ assertFalse(c.contains(bc1));
+ assertFalse(c.contains(bc2));
+ assertFalse(c.contains(bc3));
+ assertFalse(c.contains(bc4));
+ assertTrue(c.isEmpty());
+
+ Object [] actualListeners3 =
+ ((EventDispatcher.ListenerBundleContextCollectionWrapper) c).getListeners();
+ assertEquals(0, actualListeners3.length);
+ }
+
+ public void testIterator() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Bundle b2 = getMockBundle();
+ BundleContext bc2 = b2.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+
+ b2,
+ Integer.class,
+ new Object(),
+ "(some.other=filter)",
+ new Integer(15)
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ Iterator it = c.iterator();
+
+ assertEquals(2, c.size());
+ assertTrue(it.hasNext());
+ try {
+ it.remove();
+ fail("Should throw an exception");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ assertSame(bc1, it.next());
+ it.remove();
+ assertEquals(1, c.size());
+
+ // Create another iterator and make sure it sees the removal of it
+ Iterator it2 = c.iterator();
+ assertTrue(it2.hasNext());
+ assertSame(bc2, it2.next());
+ assertFalse(it2.hasNext());
+
+ // back to the origial iterator
+
+ try {
+ it.remove();
+ fail("Should throw an exception");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ assertTrue(it.hasNext());
+ try {
+ it.remove();
+ fail("Should throw an exception");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ assertSame(bc2, it.next());
+ it.remove();
+ assertEquals(0, c.size());
+
+ assertFalse(it.hasNext());
+ try {
+ it.next();
+ fail("Should throw an exception");
+ } catch (NoSuchElementException nse) {
+ // good
+ }
+ try {
+ it.remove();
+ fail("Should throw an exception");
+ } catch (IllegalStateException ise) {
+ // good
+ }
+ }
+
+ public void testAdd() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ try {
+ c.add(new Object());
+ fail("Should not have been able to add to the collection");
+ } catch (UnsupportedOperationException uoe) {
+ // good
+ }
+ }
+
+ public void testAddAll() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Object [] listeners = {};
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ try {
+ c.addAll(Collections.singleton(bc1));
+ fail("Should not have been able to add to the collection");
+ } catch (UnsupportedOperationException uoe) {
+ // good
+ }
+ }
+
+ public void testContainsAll() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Bundle b2 = getMockBundle();
+ BundleContext bc2 = b2.getBundleContext();
+ Bundle b3 = getMockBundle();
+ BundleContext bc3 = b3.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+
+ b2,
+ Integer.class,
+ new Object(),
+ "(some.other=filter)",
+ new Integer(15),
+
+ b3,
+ BundleContext.class,
+ new Object(),
+ null,
+ Boolean.TRUE,
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+
+ assertTrue(c.containsAll(Collections.emptySet()));
+ assertTrue(c.containsAll(Collections.singleton(bc2)));
+ assertTrue(c.containsAll(Arrays.asList(new Object [] {bc2, bc1})));
+ assertTrue(c.containsAll(Arrays.asList(new Object [] {bc3, bc2, bc1})));
+ assertFalse(c.containsAll(Arrays.asList(new Object [] {bc3, bc2, bc1, new Object()})));
+
+ assertEquals(3, c.size());
+ c.clear();
+ assertEquals(0, c.size());
+ }
+
+ public void testRemoveAll() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Bundle b2 = getMockBundle();
+ BundleContext bc2 = b2.getBundleContext();
+ Bundle b3 = getMockBundle();
+ BundleContext bc3 = b3.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+
+ b2,
+ Integer.class,
+ new Object(),
+ "(some.other=filter)",
+ new Integer(15),
+
+ b3,
+ BundleContext.class,
+ new Object(),
+ null,
+ Boolean.TRUE,
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ assertFalse(c.removeAll(Collections.emptyList()));
+ assertFalse(c.removeAll(Collections.singleton(new Object())));
+ assertTrue(c.contains(bc2));
+ assertTrue(c.removeAll(Arrays.asList(new Object [] {new Object(), bc2})));
+ assertFalse(c.contains(bc2));
+
+ assertEquals(2, c.size());
+ assertTrue(c.removeAll(Arrays.asList(new Object [] {bc1, bc3})));
+ assertEquals(0, c.size());
+ }
+
+ public void testRetainAll() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Bundle b2 = getMockBundle();
+ BundleContext bc2 = b2.getBundleContext();
+ Bundle b3 = getMockBundle();
+ BundleContext bc3 = b3.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+
+ b2,
+ Integer.class,
+ new Object(),
+ "(some.other=filter)",
+ new Integer(15),
+
+ b3,
+ BundleContext.class,
+ new Object(),
+ null,
+ Boolean.TRUE,
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ assertFalse(c.retainAll(Arrays.asList(new Object [] {bc3, bc1, bc2})));
+ assertTrue(Arrays.equals(new Object [] {bc1, bc2, bc3}, c.toArray()));
+
+ assertTrue(c.retainAll(Arrays.asList(new Object [] {bc1, bc2, new Object()})));
+ assertTrue(Arrays.equals(new Object [] {bc1, bc2}, c.toArray()));
+
+ assertTrue(c.retainAll(Collections.emptyList()));
+ assertEquals(0, c.size());
+ }
+
+ public void testToArray() {
+ Bundle b1 = getMockBundle();
+ BundleContext bc1 = b1.getBundleContext();
+ Bundle b2 = getMockBundle();
+ BundleContext bc2 = b2.getBundleContext();
+
+ Object [] listeners = new Object[] {
+ b1,
+ String.class,
+ new Object(),
+ "(some=filter)",
+ null,
+
+ b2,
+ Integer.class,
+ new Object(),
+ "(some.other=filter)",
+ new Integer(15),
+ };
+
+ Collection c = new EventDispatcher.ListenerBundleContextCollectionWrapper(listeners);
+ assertTrue(Arrays.equals(new Object [] {bc1, bc2}, c.toArray()));
+ assertTrue(Arrays.equals(new Object [] {bc1, bc2}, c.toArray(new Object [] {})));
+
+ try {
+ c.toArray(new String [] {});
+ fail("Should not be allowed");
+ } catch (ArrayStoreException ase) {
+ // good
+ }
+ }
+
+ private Bundle getMockBundle() {
+ MockControl bcControl = MockControl.createNiceControl(BundleContext.class);
+ BundleContext bc = (BundleContext) bcControl.getMock();
+
+ MockControl bControl = MockControl.createNiceControl(Bundle.class);
+ Bundle b = (Bundle) bControl.getMock();
+ b.getBundleContext();
+ bControl.setReturnValue(bc, MockControl.ZERO_OR_MORE);
+ b.getState();
+ bControl.setReturnValue(Bundle.ACTIVE, MockControl.ZERO_OR_MORE);
+
+ bc.getBundle();
+ bcControl.setReturnValue(b, MockControl.ZERO_OR_MORE);
+
+ bcControl.replay();
+ bControl.replay();
+
+ return b;
+ }
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherTest.java b/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherTest.java
new file mode 100644
index 0000000..de1f4e5
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/util/EventDispatcherTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.framework.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.ServiceRegistry;
+import org.easymock.MockControl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.EventHook;
+
+public class EventDispatcherTest extends TestCase
+{
+ public void testFireServiceEvent()
+ {
+ final Bundle b1 = getMockBundle();
+ final Bundle b2 = getMockBundle();
+ final Bundle b3 = getMockBundle();
+
+ final Set calledHooks = new HashSet();
+ final EventHook ph1 = new EventHook()
+ {
+ public void event(ServiceEvent event, Collection contexts)
+ {
+ calledHooks.add(this);
+ }
+ };
+ final EventHook ph2 = new EventHook()
+ {
+ public void event(ServiceEvent event, Collection contexts)
+ {
+ calledHooks.add(this);
+ for (Iterator it = contexts.iterator(); it.hasNext();)
+ {
+ BundleContext bc = (BundleContext) it.next();
+ if (bc.getBundle() == b1)
+ {
+ it.remove();
+ }
+ if (bc.getBundle() == b2)
+ {
+ it.remove();
+ }
+ }
+ }
+ };
+
+ Logger logger = new Logger();
+ ServiceRegistry registry = new ServiceRegistry(logger)
+ {
+ public List getEventHooks()
+ {
+ return Collections.unmodifiableList(
+ Arrays.asList(new EventHook[]
+ {
+ ph1, ph2
+ }));
+ }
+ };
+
+ // -- Set up event dispatcher
+
+ EventDispatcher ed = EventDispatcher.start(logger);
+ EventDispatcher.shutdown(); // stop the thread - would be nicer if we could create one without a thread for testing
+ ed.setServiceRegistry(registry);
+
+ // -- Register some listeners
+ final List fired = Collections.synchronizedList(new ArrayList());
+ ServiceListener sl1 = new ServiceListener()
+ {
+ public void serviceChanged(ServiceEvent arg0)
+ {
+ fired.add(this);
+ System.out.println("*** sl1");
+ }
+ };
+ ed.addListener(b1, ServiceListener.class, sl1, null);
+
+ ServiceListener sl2 = new ServiceListener()
+ {
+ public void serviceChanged(ServiceEvent arg0)
+ {
+ fired.add(this);
+ System.out.println("*** sl2");
+ }
+ };
+ ed.addListener(b2, ServiceListener.class, sl2, null);
+
+ ServiceListener sl3 = new ServiceListener()
+ {
+ public void serviceChanged(ServiceEvent arg0)
+ {
+ fired.add(this);
+ System.out.println("*** sl3");
+ }
+ };
+ ed.addListener(b3, ServiceListener.class, sl3, null);
+
+ // --- make the invocation
+ MockControl control = MockControl.createNiceControl(ServiceReference.class);
+ ServiceReference sr = (ServiceReference) control.getMock();
+ sr.getProperty(Constants.OBJECTCLASS);
+ control.setReturnValue(new String[]
+ {
+ "java.lang.String"
+ }, MockControl.ZERO_OR_MORE);
+ sr.isAssignableTo(b1, String.class.getName());
+ control.setReturnValue(true, MockControl.ZERO_OR_MORE);
+ sr.isAssignableTo(b2, String.class.getName());
+ control.setReturnValue(true, MockControl.ZERO_OR_MORE);
+ sr.isAssignableTo(b3, String.class.getName());
+ control.setReturnValue(true, MockControl.ZERO_OR_MORE);
+ control.replay();
+
+ ServiceEvent event = new ServiceEvent(ServiceEvent.REGISTERED, sr);
+
+ assertEquals("Precondition failed", 0, fired.size());
+ ed.fireServiceEvent(event);
+ assertEquals(1, fired.size());
+ assertSame(sl3, fired.iterator().next());
+
+ assertEquals(2, calledHooks.size());
+ assertTrue(calledHooks.contains(ph1));
+ assertTrue(calledHooks.contains(ph2));
+ }
+
+ private Bundle getMockBundle()
+ {
+ MockControl bcControl = MockControl.createNiceControl(BundleContext.class);
+ BundleContext bc = (BundleContext) bcControl.getMock();
+
+ MockControl bControl = MockControl.createNiceControl(Bundle.class);
+ Bundle b = (Bundle) bControl.getMock();
+ b.getBundleContext();
+ bControl.setReturnValue(bc, MockControl.ZERO_OR_MORE);
+ b.getState();
+ bControl.setReturnValue(Bundle.ACTIVE, MockControl.ZERO_OR_MORE);
+
+ bc.getBundle();
+ bcControl.setReturnValue(b, MockControl.ZERO_OR_MORE);
+
+ bcControl.replay();
+ bControl.replay();
+
+ return b;
+ }
+}
\ No newline at end of file