Bug 305068 -  OSGiContextStrategy cleanup is too expensive
diff --git a/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java b/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java
index 333eeef..9ee77e1 100644
--- a/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java
+++ b/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009 IBM Corporation and others.
+ * Copyright (c) 2009, 2010 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -16,9 +16,14 @@
 import java.util.Map;
 import java.util.WeakHashMap;
 import org.eclipse.e4.core.services.IDisposable;
+import org.eclipse.e4.core.services.context.ContextChangeEvent;
 import org.eclipse.e4.core.services.context.IContextFunction;
 import org.eclipse.e4.core.services.context.IEclipseContext;
+import org.eclipse.e4.core.services.context.IRunAndTrack;
+import org.eclipse.e4.core.services.context.spi.IContextConstants;
 import org.eclipse.e4.core.services.context.spi.ILookupStrategy;
+import org.eclipse.e4.core.services.injector.IObjectProvider;
+import org.eclipse.e4.core.services.internal.context.ObjectProviderContext;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
@@ -31,14 +36,15 @@
  * <p>
  * OSGi services are looked up by service class name.
  */
-public class OSGiContextStrategy implements ILookupStrategy, IDisposable, ServiceTrackerCustomizer {
+public class OSGiContextStrategy implements ILookupStrategy, IDisposable, ServiceTrackerCustomizer,
+		IRunAndTrack {
 	class ServiceData {
 		// the service name
 		String name;
 
 		ServiceTracker tracker;
 		// the contexts using this service (IEclipseContext -> null)
-		final Map users = new WeakHashMap();
+		final Map<IEclipseContext, Object> users = new WeakHashMap<IEclipseContext, Object>();
 
 		ServiceData(String name) {
 			this.name = name;
@@ -46,6 +52,8 @@
 
 		public void addContext(IEclipseContext originatingContext) {
 			users.put(originatingContext, null);
+			// track this context so we can cleanup when the context is disposed
+			originatingContext.runAndTrack(OSGiContextStrategy.this, null);
 		}
 	}
 
@@ -53,7 +61,8 @@
 	/**
 	 * Map of String (service name) -> ServiceData
 	 */
-	private Map services = Collections.synchronizedMap(new HashMap());
+	private Map<String, ServiceData> services = Collections
+			.synchronizedMap(new HashMap<String, ServiceData>());
 
 	public OSGiContextStrategy(BundleContext bc) {
 		super();
@@ -66,33 +75,16 @@
 		if (newValue == null)
 			return null;
 		// for performance we store the concrete service object with each context that requested it
-		ServiceData data = (ServiceData) services.get(name);
+		ServiceData data = getServiceData(name);
 		// may have been cleaned up concurrently
 		if (data == null)
 			return null;
-		for (Iterator it = data.users.keySet().iterator(); it.hasNext();)
-			((IEclipseContext) it.next()).set(name, newValue);
+		for (Iterator<IEclipseContext> it = data.users.keySet().iterator(); it.hasNext();)
+			it.next().set(name, newValue);
 		return newValue;
 	}
 
-	/**
-	 * Discards any services that are no longer used by any strongly reachable contexts.
-	 */
-	private void cleanReferences() {
-		synchronized (services) {
-			for (Iterator it = services.values().iterator(); it.hasNext();) {
-				ServiceData data = (ServiceData) it.next();
-				// if there are no more references, discard the service
-				if (data.users.isEmpty()) {
-					data.tracker.close();
-					it.remove();
-				}
-			}
-		}
-	}
-
 	public boolean containsKey(String name, IEclipseContext context) {
-		cleanReferences();
 		// first look for a registered IContextFunction matching the name
 		if (getContextFunction(name) != null)
 			return true;
@@ -102,15 +94,33 @@
 
 	public void dispose() {
 		synchronized (services) {
-			for (Iterator it = services.values().iterator(); it.hasNext();)
-				((ServiceData) it.next()).tracker.close();
+			for (Iterator<ServiceData> it = services.values().iterator(); it.hasNext();)
+				it.next().tracker.close();
 			services.clear();
 		}
 	}
 
+	/**
+	 * Returns the service data corresponding to the given name, or <code>null</code> if no such
+	 * data is available.
+	 */
+	private ServiceData getServiceData(String name) {
+		ServiceData data = services.get(name);
+		if (data == null)
+			return null;
+		if (data.users.isEmpty()) {
+			data.tracker.close();
+			services.remove(name);
+			return null;
+		}
+		return data;
+	}
+
 	public Object lookup(String name, IEclipseContext originatingContext) {
-		cleanReferences();
-		ServiceData data = (ServiceData) services.get(name);
+		// services must be fully qualified type names
+		if (name == null || name.indexOf('.') == -1)
+			return null;
+		ServiceData data = getServiceData(name);
 		if (data == null) {
 			// first look for a registered IContextFunction matching the name
 			ServiceReference ref = getContextFunction(name);
@@ -155,23 +165,23 @@
 
 	public void modifiedService(ServiceReference reference, Object service) {
 		String name = serviceName(reference);
-		ServiceData data = (ServiceData) services.get(name);
+		ServiceData data = getServiceData(name);
 		// may have been cleaned up concurrently
 		if (data == null)
 			return;
-		for (Iterator it = data.users.keySet().iterator(); it.hasNext();)
-			((IEclipseContext) it.next()).set(name, service);
+		for (Iterator<IEclipseContext> it = data.users.keySet().iterator(); it.hasNext();)
+			it.next().set(name, service);
 	}
 
 	public void removedService(ServiceReference reference, Object service) {
 		String name = serviceName(reference);
 		// must set to null rather than removing so injection continues to work
-		ServiceData data = (ServiceData) services.get(name);
+		ServiceData data = getServiceData(name);
 		// may have been cleaned up concurrently
 		if (data == null)
 			return;
-		for (Iterator it = data.users.keySet().iterator(); it.hasNext();)
-			((IEclipseContext) it.next()).set(name, null);
+		for (Iterator<IEclipseContext> it = data.users.keySet().iterator(); it.hasNext();)
+			it.next().set(name, null);
 		bundleContext.ungetService(reference);
 	}
 
@@ -181,4 +191,38 @@
 	private String serviceName(ServiceReference reference) {
 		return ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
 	}
+
+	/**
+	 * Listen for changes on all contexts that have obtained a service from this strategy, so that
+	 * we can do appropriate cleanup of our caches when the requesting context is disposed.
+	 */
+	public boolean notify(ContextChangeEvent event) {
+		IEclipseContext context = getContext(event);
+		if (context == null)
+			return false;
+		if (event.getEventType() != ContextChangeEvent.DISPOSE) {
+			// do a lookup so the listener isn't removed
+			context.get(IContextConstants.PARENT);
+			return true;
+		}
+		synchronized (services) {
+			for (Iterator<ServiceData> it = services.values().iterator(); it.hasNext();) {
+				ServiceData data = it.next();
+				data.users.remove(context);
+				// if there are no more references, discard the service
+				if (data.users.isEmpty()) {
+					it.remove();
+					data.tracker.close();
+				}
+			}
+		}
+		return true;
+	}
+
+	private IEclipseContext getContext(ContextChangeEvent event) {
+		IObjectProvider provider = event.getContext();
+		if (provider instanceof ObjectProviderContext)
+			return ((ObjectProviderContext) provider).getContext();
+		return null;
+	}
 }