Bug 423212: Allow to inject OSGi Bundle and BundleContext

- Rename @BundleContext to @OSGiBundle to prevent name clash between
parameter type and annotation.

- Support to inject org.osgi.framework.Bundle type, which is injectable
even if bundle's state is RESOLVED.

Bug-URL: https://bugs.eclipse.org/423212
Change-Id: I286010ab5f720d119b8c938409f131bfa87454d8
Signed-off-by: Markus Alexander Kuppe <bugs.eclipse.org@lemmster.de>
diff --git a/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF
index 2661a74..069557c 100644
--- a/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF
@@ -14,7 +14,7 @@
 Import-Package: javax.annotation;version="1.0.0",
  javax.inject;version="1.0.0"
 Service-Component: OSGI-INF/preferences.xml, OSGI-INF/events.xml,
- OSGI-INF/bundleContext.xml
+ OSGI-INF/osgiBundle.xml
 Export-Package: org.eclipse.e4.core.di.extensions;x-internal:=true,
  org.eclipse.e4.core.di.internal.extensions;x-friends:="org.eclipse.e4.ui.di"
 Bundle-Localization: fragment
diff --git a/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml b/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/osgiBundle.xml
similarity index 81%
rename from bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml
rename to bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/osgiBundle.xml
index a03d46c..9ceae67 100644
--- a/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml
+++ b/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/osgiBundle.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.eclipse.e4.core.di.extensions.bundleContext">
-   <implementation class="org.eclipse.e4.core.di.internal.extensions.BundleContextObjectSupplier"/>
-   <property name="dependency.injection.annotation" type="String" value="org.eclipse.e4.core.di.extensions.BundleContext"/>
+   <implementation class="org.eclipse.e4.core.di.internal.extensions.OSGiObjectSupplier"/>
+   <property name="dependency.injection.annotation" type="String" value="org.eclipse.e4.core.di.extensions.OSGiBundle"/>
    <service>
       <provide interface="org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier"/>
    </service>
diff --git a/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/OSGiBundle.java
similarity index 63%
rename from bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java
rename to bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/OSGiBundle.java
index d5e29fe..3a16936 100644
--- a/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java
+++ b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/OSGiBundle.java
@@ -15,15 +15,22 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import javax.inject.Qualifier;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 
 /**
- * A method or field annotated with {@link BundleContext} will be injected with the
- * {@link org.osgi.framework.BundleContext} from the bundle contain the class.
+ * A method or field of type {@link org.osgi.framework.BundleContext} and 
+ * annotated with {@link OSGiBundle} will be injected with the from the bundle 
+ * containing the class if the annotated type is a {@link BundleContext} 
+ * and the bundle's state is {@link Bundle#ACTIVE}.
+ * <p>
+ * If the method or field type is of {@link Bundle}, the bundle containing 
+ * the class will be injected even for bundles in the {@link Bundle#RESOLVED} state.
  */
 @Qualifier
 @Documented
 @Target({ElementType.PARAMETER, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
-public @interface BundleContext {
+public @interface OSGiBundle {
 	// Nop
 }
diff --git a/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/OSGiObjectSupplier.java
similarity index 63%
rename from bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java
rename to bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/OSGiObjectSupplier.java
index f2547e4..cde905c 100644
--- a/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java
+++ b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/OSGiObjectSupplier.java
@@ -9,6 +9,7 @@
  ******************************************************************************/
 package org.eclipse.e4.core.di.internal.extensions;
 
+import java.lang.reflect.Type;
 import java.util.HashMap;
 import java.util.Map;
 import org.eclipse.e4.core.di.InjectionException;
@@ -23,37 +24,49 @@
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.SynchronousBundleListener;
 
-public class BundleContextObjectSupplier extends ExtendedObjectSupplier {
+public class OSGiObjectSupplier extends ExtendedObjectSupplier {
 
 	/**
 	 * A Map of Requestor to BundleListener. Each Requestor will only ever request its own bundle and thus there is a 1:1 relationship between R and BL.
 	 */
 	private final Map<IRequestor, BundleListener> requestor2listener = new HashMap<IRequestor, BundleListener>();
 
-	private final BundleContext localBundleContext = FrameworkUtil.getBundle(BundleContextObjectSupplier.class).getBundleContext();
+	private final BundleContext localBundleContext = FrameworkUtil.getBundle(OSGiObjectSupplier.class).getBundleContext();
 
 	@Override
 	public Object get(IObjectDescriptor descriptor, IRequestor requestor, boolean track, boolean group) {
 		final Class<?> requestingObjectClass = requestor.getRequestingObjectClass();
-		final Bundle bundle = FrameworkUtil.getBundle(requestingObjectClass);
 
-		// Cannot use BundleListener as a BL can only be registered with a BC (which might be null)
-		if (track) {
-			if (!requestor2listener.containsKey(requestor)) {
-				track(bundle, requestor);
+		final Type desiredType = descriptor.getDesiredType();
+		if (BundleContext.class.equals(desiredType)) {
+			final Bundle bundle = FrameworkUtil.getBundle(requestingObjectClass);
+
+			// Cannot use BundleListener as a BL can only be registered with a BC (which might be null)
+			if (track) {
+				if (!requestor2listener.containsKey(requestor)) {
+					track(bundle, requestor);
+				}
+			} else {
+				untrack(requestor);
 			}
-		} else {
-			untrack(requestor);
-		}
 
-		final BundleContext bundleContext = bundle.getBundleContext();
-		if (bundleContext != null) {
-			return bundleContext;
-		} else if (descriptor.getQualifier(Optional.class) != null) {
-			// Do not have a bundle context but requestor has marked the parameter/field optional
-			return null;
+			final BundleContext bundleContext = bundle.getBundleContext();
+			if (bundleContext != null) {
+				return bundleContext;
+			} else if (descriptor.getQualifier(Optional.class) != null) {
+				// Do not have a bundle context but requestor has marked the parameter/field optional
+				return null;
+			}
+			throw new InjectionException("Unable to inject BundleContext: " + bundle.getSymbolicName() + " bundle is not active or starting/stopping"); //$NON-NLS-1$  //$NON-NLS-2$
+		} else if (Bundle.class.equals(desiredType)) {
+			// Not tracking the Bundle's life-cycle because the B instance does 
+			// not change whether a bundle is ACTIVE or RESOLVED. The only
+			// thing worth tracking is when a bundle switches to the INSTALLED 
+			// state. However, the requestor will go away along with its bundle anyway. 
+			return FrameworkUtil.getBundle(requestingObjectClass);
 		}
-		throw new InjectionException("Unable to inject BundleContext: " + bundle.getSymbolicName() + " bundle is not active or starting/stopping"); //$NON-NLS-1$  //$NON-NLS-2$
+		// Annotation used with unsupported type
+		return null;
 	}
 
 	private void untrack(final IRequestor requestor) {
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java
similarity index 63%
rename from tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java
rename to tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java
index 9a09a21..eb9ac08 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java
@@ -17,21 +17,22 @@
 import org.eclipse.e4.core.contexts.EclipseContextFactory;
 import org.eclipse.e4.core.contexts.IEclipseContext;
 import org.eclipse.e4.core.di.annotations.Optional;
-import org.eclipse.e4.core.di.extensions.BundleContext;
+import org.eclipse.e4.core.di.extensions.OSGiBundle;
 import org.eclipse.e4.core.internal.tests.CoreTestsActivator;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 
-public class InjectionBundleContextTest extends TestCase {
+public class InjectionOSGiTest extends TestCase {
 
-	// classed used as a user of the @BundleContext annotation
+	// classed used as a user of the @OSGiBundle annotation
 	static class InjectionTarget {
 
-		private org.osgi.framework.BundleContext ctx;
+		private BundleContext ctx;
 
 		@Inject
 		public void setBundleContext(
-				@BundleContext @Optional org.osgi.framework.BundleContext ctx) {
+				@OSGiBundle @Optional BundleContext ctx) {
 			this.ctx = ctx;
 		}
 
@@ -39,9 +40,30 @@
 			return this.ctx != null;
 		}
 
-		public org.osgi.framework.BundleContext getContext() {
+		public BundleContext getContext() {
 			return this.ctx;
 		}
+
+		private Bundle b;
+
+		@Inject
+		public void setBundle(
+				@OSGiBundle Bundle b) {
+					this.b = b;
+		}
+
+		public Bundle getBundle() {
+			return this.b;
+		}
+		
+		@Inject
+		public void setFoo(@OSGiBundle Object o) {
+			// make sure we don't fail when incompatible type requested
+		}
+	}
+	
+	// classed used as a user of the @OSGiBundle annotation
+	static class InjectionBundleTarget extends InjectionTarget {
 	}
 
 	private InjectionTarget target;
@@ -51,7 +73,7 @@
 	protected void tearDown() throws Exception {
 		bundle.start();
 
-		final org.osgi.framework.BundleContext bundleContext = CoreTestsActivator
+		final BundleContext bundleContext = CoreTestsActivator
 				.getDefault().getBundleContext();
 		final IEclipseContext localContext = EclipseContextFactory
 				.getServiceContext(bundleContext);
@@ -65,7 +87,7 @@
 	protected void setUp() throws Exception {
 		super.setUp();
 
-		final org.osgi.framework.BundleContext bundleContext = CoreTestsActivator
+		final BundleContext bundleContext = CoreTestsActivator
 				.getDefault().getBundleContext();
 		bundle = bundleContext.getBundle();
 
@@ -85,7 +107,7 @@
 		assertTrue(target.hasContext());
 
 		// Check also that the BundleContext instance has indeed changed
-		final org.osgi.framework.BundleContext firstContext = target
+		final BundleContext firstContext = target
 				.getContext();
 
 		// uninject
@@ -96,8 +118,23 @@
 		bundle.start();
 		assertTrue(target.hasContext());
 
-		final org.osgi.framework.BundleContext secondContext = target
+		final BundleContext secondContext = target
 				.getContext();
 		assertNotSame(firstContext, secondContext);
 	}
+	
+	public void testBundleInject() throws BundleException {
+		// inject
+		assertNotNull(target.getBundle());
+
+		// Contrary to the BC, the Bundle is available even for RESOLVED bundles
+		bundle.stop();
+
+		// not null but resolved _and_ still usable
+		assertNotNull(target.getBundle());
+		assertTrue(target.getBundle().getState() == Bundle.RESOLVED);
+		assertNotNull(target.getBundle().getSymbolicName());
+		
+		assertNull(target.getContext());
+	}
 }
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java
index bfd00bd..a30dfc0 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java
@@ -48,7 +48,7 @@
 import org.eclipse.e4.core.internal.tests.di.InjectionResultLeakTest;
 import org.eclipse.e4.core.internal.tests.di.InvokeTest;
 import org.eclipse.e4.core.internal.tests.di.RecursiveObjectCreationTest;
-import org.eclipse.e4.core.internal.tests.di.extensions.InjectionBundleContextTest;
+import org.eclipse.e4.core.internal.tests.di.extensions.InjectionOSGiTest;
 import org.eclipse.e4.core.internal.tests.di.extensions.InjectionEventTest;
 import org.eclipse.e4.core.internal.tests.di.extensions.InjectionMixedSuppliersTest;
 import org.eclipse.e4.core.internal.tests.di.extensions.InjectionPreferencesTest;
@@ -63,7 +63,7 @@
 		addTestSuite(InjectionPreferencesTest.class);
 		addTestSuite(InjectionMixedSuppliersTest.class);
 		addTestSuite(InjectionEventTest.class);
-		addTestSuite(InjectionBundleContextTest.class);
+		addTestSuite(InjectionOSGiTest.class);
 
 		// DI
 		addTestSuite(InjectionOrderTest.class);