Bug 329282 - [DI] The current 'uninject' scenarios break the API contract
diff --git a/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java b/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java
index c748875..1d22684 100644
--- a/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java
+++ b/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java
@@ -46,7 +46,7 @@
  * </p>
  * <p>
  * When a context is disposed, the injection factory will attempt to notify all injected objects by
- * calling methods with the <tt>javax.annotation.PreDestroy</tt> annotation before resetting injected values to <code>null</code>.
+ * calling methods with the <tt>javax.annotation.PreDestroy</tt> annotation.
  * 
  * @noextend This class is not intended to be extended by clients.
  * @noinstantiate This class is not intended to be instantiated by clients.
@@ -124,7 +124,8 @@
 	}
 
 	/**
-	 * Un-injects the context from the object.
+	 * Un-injects the context from the object. The un-injection requires that all injected 
+	 * values were marked as optional, or the un-injection will fail.
 	 * 
 	 * @param object The domain object previously injected with the context
 	 * @param context The context previously injected into the object
diff --git a/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java b/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java
index e0f1509..97fe036 100644
--- a/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java
+++ b/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java
@@ -42,8 +42,7 @@
  * </p>
  * <p>
  * When supplier is disposed, the injector will attempt to notify all injected objects by
- * calling methods with the {@link PreDestroy} annotation before resetting injected values 
- * to <code>null</code>.
+ * calling methods with the {@link PreDestroy} annotation.
  * 
  * @noimplement This interface is not intended to be implemented by clients.
  * @noextend This interface is not intended to be extended by clients.
@@ -66,7 +65,8 @@
 	public void inject(Object object, PrimaryObjectSupplier objectSupplier) throws InjectionException;
 
 	/**
-	 * Un-injects the supplier from the object.
+	 * Un-injects the supplier from the object. All un-injected values have to be optional,
+	 * or un-injection will fail.
 	 * @param object the domain object previously injected with the supplier's data
 	 * @param objectSupplier primary object supplier for the injection
 	 * @throws InjectionException if an exception occurred while performing this operation
diff --git a/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java b/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java
index daf3c65..624bf6a 100644
--- a/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java
+++ b/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java
@@ -43,12 +43,17 @@
 import org.eclipse.e4.core.di.suppliers.IObjectDescriptor;
 import org.eclipse.e4.core.di.suppliers.IRequestor;
 import org.eclipse.e4.core.di.suppliers.PrimaryObjectSupplier;
+import org.eclipse.e4.core.internal.di.osgi.DIActivator;
+import org.eclipse.e4.core.internal.di.osgi.LogHelper;
 
 /**
  * Reflection-based dependency injector.
  */
 public class InjectorImpl implements IInjector {
 
+	final static private String DEBUG_INJECTION = "org.eclipse.e4.core.di/debug/injector"; //$NON-NLS-1$
+	final static private boolean shouldDebug = DIActivator.getDefault().getBooleanDebugOption(DEBUG_INJECTION, false);
+
 	final static private String JAVA_OBJECT = "java.lang.Object"; //$NON-NLS-1$
 
 	final private static Boolean DEFAULT_BOOLEAN = new Boolean(false);
@@ -160,15 +165,32 @@
 		if (!forgetInjectedObject(object, objectSupplier))
 			return; // not injected at this time
 		processAnnotated(PreDestroy.class, object, object.getClass(), objectSupplier, null, new ArrayList<Class<?>>(5));
-		// Two stages: first, go and collect {requestor, descriptor[] }
+
 		ArrayList<Requestor> requestors = new ArrayList<Requestor>();
 		processClassHierarchy(object, objectSupplier, null, true /* track */, false /* inverse order */, requestors);
-		// might still need to get resolved values from secondary suppliers
-		// Ask suppliers to fill actual values {requestor, descriptor[], actualvalues[] }
-		resolveRequestorArgs(requestors, null, null, true /* fill with nulls */, false, false);
 
-		// Call requestors in order
 		for (Requestor requestor : requestors) {
+			// Ask suppliers to fill actual values {requestor, descriptor[], actualvalues[] }
+			Object[] actualArgs = resolveArgs(requestor, null, null, true, false, false);
+			int unresolved = unresolved(actualArgs);
+			if (unresolved == -1) {
+				requestor.setResolvedArgs(actualArgs);
+			} else {
+				if (requestor.isOptional())
+					requestor.setResolvedArgs(null);
+				else {
+					if (shouldDebug) {
+						StringBuffer tmp = new StringBuffer();
+						tmp.append("Uninjecting object \""); //$NON-NLS-1$
+						tmp.append(object.toString());
+						tmp.append("\": dependency on \""); //$NON-NLS-1$
+						tmp.append(requestor.getDependentObjects()[unresolved].toString());
+						tmp.append("\" is not optional."); //$NON-NLS-1$
+						LogHelper.logError(tmp.toString(), null);
+					}
+					continue; // do not execute requestors with unresolved arguments
+				}
+			}
 			requestor.execute();
 		}
 	}
@@ -301,10 +323,19 @@
 	}
 
 	public void resolveArguments(IRequestor requestor, boolean initial) {
-		ArrayList<Requestor> list = new ArrayList<Requestor>(1);
 		Requestor internalRequestor = ((Requestor) requestor);
-		list.add(internalRequestor);
-		resolveRequestorArgs(list, internalRequestor.getPrimarySupplier(), internalRequestor.getTempSupplier(), true, initial, internalRequestor.shouldTrack());
+		Object[] actualArgs = resolveArgs(internalRequestor, internalRequestor.getPrimarySupplier(), internalRequestor.getTempSupplier(), false, initial, internalRequestor.shouldTrack());
+		int unresolved = unresolved(actualArgs);
+		if (unresolved == -1)
+			internalRequestor.setResolvedArgs(actualArgs);
+		else {
+			if (internalRequestor.isOptional())
+				internalRequestor.setResolvedArgs(null);
+			else {
+				String msg = resolutionError(internalRequestor, unresolved);
+				LogHelper.logError(msg, null);
+			}
+		}
 	}
 
 	public void disposed(PrimaryObjectSupplier objectSupplier) {
@@ -321,14 +352,17 @@
 			}
 		}
 		for (int i = 0; i < count; i++) {
-			uninject(objects[i], objectSupplier);
+			Object object = objects[i];
+			if (!forgetInjectedObject(object, objectSupplier))
+				continue; // not injected at this time
+			processAnnotated(PreDestroy.class, object, object.getClass(), objectSupplier, null, new ArrayList<Class<?>>(5));
 		}
 		forgetSupplier(objectSupplier);
 	}
 
-	private void resolveRequestorArgs(ArrayList<Requestor> requestors, PrimaryObjectSupplier objectSupplier, PrimaryObjectSupplier tempSupplier, boolean fillNulls, boolean initial, boolean track) {
+	private void resolveRequestorArgs(ArrayList<Requestor> requestors, PrimaryObjectSupplier objectSupplier, PrimaryObjectSupplier tempSupplier, boolean uninject, boolean initial, boolean track) {
 		for (Requestor requestor : requestors) {
-			Object[] actualArgs = resolveArgs(requestor, objectSupplier, tempSupplier, fillNulls, initial, track);
+			Object[] actualArgs = resolveArgs(requestor, objectSupplier, tempSupplier, uninject, initial, track);
 			int unresolved = unresolved(actualArgs);
 			if (unresolved == -1) {
 				requestor.setResolvedArgs(actualArgs);
@@ -343,16 +377,20 @@
 	}
 
 	private void reportUnresolvedArgument(Requestor requestor, int argIndex) {
+		throw new InjectionException(resolutionError(requestor, argIndex));
+	}
+
+	private String resolutionError(Requestor requestor, int argIndex) {
 		StringBuffer tmp = new StringBuffer();
 		tmp.append("Unable to process \""); //$NON-NLS-1$
 		tmp.append(requestor.toString());
 		tmp.append("\": no actual value was found for the argument \""); //$NON-NLS-1$
 		tmp.append(requestor.getDependentObjects()[argIndex].toString());
 		tmp.append("\"."); //$NON-NLS-1$
-		throw new InjectionException(tmp.toString());
+		return tmp.toString();
 	}
 
-	private Object[] resolveArgs(Requestor requestor, PrimaryObjectSupplier objectSupplier, PrimaryObjectSupplier tempSupplier, boolean fillNulls, boolean initial, boolean track) {
+	private Object[] resolveArgs(Requestor requestor, PrimaryObjectSupplier objectSupplier, PrimaryObjectSupplier tempSupplier, boolean uninject, boolean initial, boolean track) {
 		IObjectDescriptor[] descriptors = requestor.getDependentObjects();
 
 		// 0) initial fill - all values are unresolved
@@ -395,7 +433,7 @@
 		}
 
 		// 5) create simple classes (implied bindings) - unless we uninject or optional
-		if (!fillNulls && !requestor.isOptional()) {
+		if (!uninject && !requestor.isOptional()) {
 			for (int i = 0; i < actualArgs.length; i++) {
 				if (actualArgs[i] != NOT_A_VALUE)
 					continue; // already resolved
@@ -436,7 +474,7 @@
 					actualArgs[i] = IInjector.NOT_A_VALUE;
 			}
 			if (actualArgs[i] == IInjector.NOT_A_VALUE) { // still unresolved?
-				if (fillNulls || descriptors[i].hasQualifier(Optional.class)) { // uninject or optional - fill defaults
+				if (descriptors[i].hasQualifier(Optional.class)) { // uninject or optional - fill defaults
 					Class<?> descriptorsClass = getDesiredClass(descriptors[i].getDesiredType());
 					if (descriptorsClass.isPrimitive()) {
 						if (descriptorsClass.equals(boolean.class))
diff --git a/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/osgi/LogHelper.java b/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/osgi/LogHelper.java
index be7f561..b34b7bc 100644
--- a/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/osgi/LogHelper.java
+++ b/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/osgi/LogHelper.java
@@ -18,8 +18,16 @@
 	static final private String plugin_name = "org.eclipse.e4.core"; //$NON-NLS-1$
 
 	static public void logError(String msg, Throwable e) {
+		log(msg, FrameworkLogEntry.ERROR, e);
+	}
+
+	static public void logWarning(String msg, Throwable e) {
+		log(msg, FrameworkLogEntry.WARNING, e);
+	}
+
+	static public void log(String msg, int severity, Throwable e) {
 		FrameworkLog log = DIActivator.getDefault().getFrameworkLog();
-		FrameworkLogEntry logEntry = new FrameworkLogEntry(plugin_name, FrameworkLogEntry.ERROR, 0, msg, 0, e, null);
+		FrameworkLogEntry logEntry = new FrameworkLogEntry(plugin_name, severity, 0, msg, 0, e, null);
 		log.log(logEntry);
 	}
 }
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/AnnotationsInjectionTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/AnnotationsInjectionTest.java
index 6fe02d4..1630774 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/AnnotationsInjectionTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/AnnotationsInjectionTest.java
@@ -170,7 +170,7 @@
 
 			@SuppressWarnings("unused")
 			@Inject
-			public void injectedMethod(@Named("valueMethod") Object arg) {
+			public void injectedMethod(@Optional @Named("valueMethod") Object arg) {
 				try {
 					assertTrue(injectedField != null);
 				} catch (AssertionFailedError e) {
@@ -363,7 +363,7 @@
 		context.dispose();
 		
 		assertEquals(1, object.preDestoryCalled);
-		assertNull(object.value);
-		assertNull(object.directFieldInjection);
+		assertNotNull(object.value);
+		assertNotNull(object.directFieldInjection);
 	}
 }
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ContextInjectionDisposeTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ContextInjectionDisposeTest.java
index ef3af96..bb75144 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ContextInjectionDisposeTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ContextInjectionDisposeTest.java
@@ -20,6 +20,7 @@
 import org.eclipse.e4.core.contexts.ContextInjectionFactory;
 import org.eclipse.e4.core.contexts.EclipseContextFactory;
 import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.core.di.annotations.Optional;
 
 /**
  * Tests for injection handling of context dispose, and handling disposal of injected objects.
@@ -92,10 +93,10 @@
 		assertEquals(fieldValue, object.Field);
 		assertEquals(methodValue, object.methodValue);
 
-		// disposing context should clear values
+		// disposing context calls @PreDestory, but does not clear injected values
 		context.dispose();
-		assertNull(object.Field);
-		assertNull(object.methodValue);
+		assertNotNull(object.Field);
+		assertNotNull(object.methodValue);
 		assertTrue(object.disposeInvoked);
 	}
 
@@ -115,7 +116,7 @@
 
 			@SuppressWarnings("unused")
 			@Inject
-			public void InjectedMethod(String arg) {
+			public void InjectedMethod(@Optional String arg) {
 				methodValue = arg;
 			}
 		}
@@ -134,7 +135,7 @@
 		// disposing the context does.
 		ContextInjectionFactory.uninject(object, context);
 
-		assertNull(object.Field);
+		assertEquals(fieldValue, object.Field);
 		assertNull(object.methodValue);
 		assertTrue(object.disposeInvoked);
 	}
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ExtraDependenciesTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ExtraDependenciesTest.java
index 2ca0087..2328442 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ExtraDependenciesTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ExtraDependenciesTest.java
@@ -96,8 +96,8 @@
 		// check that disposal of the injected context causes disposal of the injected object
 		context.dispose();
 		assertTrue(object.disposed);
-		assertNull(object.string);
-		assertNull(object.integer);
+		assertNotNull(object.string);
+		assertNotNull(object.integer);
 		assertNull(object.other);
 	}
 
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/InjectStaticContextTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/InjectStaticContextTest.java
index 64270d4..f0f9033 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/InjectStaticContextTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/InjectStaticContextTest.java
@@ -21,6 +21,7 @@
 import org.eclipse.e4.core.contexts.EclipseContextFactory;
 import org.eclipse.e4.core.contexts.IEclipseContext;
 import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.core.di.annotations.Optional;
 
 /**
  * Tests for the context injection functionality using 2 contexts 
@@ -39,12 +40,12 @@
 		public int preDestroyCalled = 0;
 
 		@Inject
-		public void contextSet(IEclipseContext context) {
+		public void contextSet(@Optional IEclipseContext context) {
 			injectedContext = context;
 		}
 		
 		@Inject
-		public void setA(@Named("a") String aString) {
+		public void setA(@Optional @Named("a") String aString) {
 			this.aString = aString;
 		}
 		
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ObjectBasic.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ObjectBasic.java
index 294adc6..b55bfb9 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ObjectBasic.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/contexts/inject/ObjectBasic.java
@@ -15,6 +15,7 @@
 import javax.inject.Inject;
 
 import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.core.di.annotations.Optional;
 
 /**
  * Test class to check injection mechanism
@@ -22,7 +23,7 @@
 public class ObjectBasic {
 
 	// Injected directly
-	@Inject
+	@Inject @Optional
 	public String injectedString;
 	@Inject
 	private Integer injectedInteger;
@@ -50,7 +51,7 @@
 	}
 
 	@Inject
-	public void arguments(Float f, Character c) {
+	public void arguments(Float f, @Optional Character c) {
 		setMethodCalled2++;
 		this.f = f;
 		this.c = c;
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/DisposeClassLinkTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/DisposeClassLinkTest.java
index 2efc39d..354c65b 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/DisposeClassLinkTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/DisposeClassLinkTest.java
@@ -118,7 +118,7 @@
 
 	    context.dispose();
 
-	    assertNull("The object should have been uninjected", obj.context);
+	    assertNotNull(obj.context);
 	    assertEquals("@PostConstruct should only have been called once", 1, obj.postConstruct);
 	    assertEquals("@PreDestroy should have been called during uninjection", 1, obj.preDestroy);
 	}
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/InjectBaseTypeTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/InjectBaseTypeTest.java
index 18360d4..37e4f72 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/InjectBaseTypeTest.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/InjectBaseTypeTest.java
@@ -47,7 +47,7 @@
 		@Inject @Named("test_byte")
 		public byte byteField;
 		
-		@Inject @Named("test_boolean")
+		@Inject @Optional @Named("test_boolean")
 		public boolean booleanField;
 		
 		@Inject @Named("test_char")
@@ -96,19 +96,21 @@
 		// test end-of-life reset of values
 		ContextInjectionFactory.uninject(testClass, context);
 		
-		assertEquals(0, testClass.intField);
-		assertEquals(0, testClass.intFieldOptional);
-		assertEquals(0L, testClass.longField);
-		assertEquals(0f, testClass.floatField);
-		assertEquals(0.0d, testClass.doubleField);
-		assertEquals((short)0, testClass.shortField);
-		assertEquals((byte)0, testClass.byteField);
-		assertEquals(false, testClass.booleanField);
-		assertEquals((char)0, testClass.charField);
+		// optional fields are reset to default;
+		// non-optional keep their values
+		assertEquals(12, testClass.intField);
+		assertEquals(0, testClass.intFieldOptional); // optional
+		assertEquals(124564523466L, testClass.longField);
+		assertEquals(12.34f, testClass.floatField);
+		assertEquals(12.34534534563463466546d, testClass.doubleField);
+		assertEquals((short)10, testClass.shortField);
+		assertEquals((byte)55, testClass.byteField);
+		assertEquals(false, testClass.booleanField); // optional
+		assertEquals('a', testClass.charField);
 		
-		assertEquals(0, testClass.intArg);
-		assertEquals((char)0, testClass.charArg);
-		assertEquals(false, testClass.booleanArg);
+		assertEquals(12, testClass.intArg);
+		assertEquals('a', testClass.charArg);
+		assertEquals(true, testClass.booleanArg);
 		
 	}
 }