blob: 2ed6436b17c6f3c8de8bb91cb1f07bd81f343921 [file] [log] [blame]
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.core.services.internal.context;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.e4.core.services.IDisposable;
import org.eclipse.e4.core.services.context.IEclipseContext;
import org.eclipse.e4.core.services.context.spi.ContextInjectionFactory;
import org.eclipse.e4.core.services.context.spi.IContextConstants;
import org.eclipse.e4.core.services.injector.AbstractObjectSupplier;
import org.eclipse.e4.core.services.injector.IBinding;
import org.eclipse.e4.core.services.injector.IInjector;
import org.eclipse.e4.core.services.injector.IObjectDescriptor;
import org.eclipse.e4.core.services.injector.IRequestor;
import org.eclipse.e4.core.services.injector.ObjectDescriptorFactory;
import org.eclipse.e4.core.services.internal.annotations.AnnotationsSupport;
/**
* Reflection-based dependency injector.
*/
public class InjectorImpl implements IInjector {
final static private String JAVA_OBJECT = "java.lang.Object"; //$NON-NLS-1$
// XXX remove
// plug-in class that gets replaced in Java 1.5+
final private static AnnotationsSupport annotationSupport = new AnnotationsSupport();
// TBD thread safety
private Map<String, AbstractObjectSupplier> extendedSuppliers = new HashMap<String, AbstractObjectSupplier>();
private Map<AbstractObjectSupplier, List<WeakReference<?>>> injectedObjects = new HashMap<AbstractObjectSupplier, List<WeakReference<?>>>();
private HashMap<Class<?>, Object> singletonCache = new HashMap<Class<?>, Object>();
private Map<Class<?>, Set<IBinding>> bindings = new HashMap<Class<?>, Set<IBinding>>();
public boolean inject(Object object, AbstractObjectSupplier objectSupplier) {
// Two stages: first, go and collect {requestor, descriptor[] }
ArrayList<Requestor> requestors = new ArrayList<Requestor>();
processClassHierarchy(object, false /* no static */, true /* track */, true /* normal order */,
requestors);
// Ask suppliers to fill actual values {requestor, descriptor[], actualvalues[] }
if (!resolveRequestorArgs(requestors, objectSupplier, false))
return false;
// Call requestors in order
for (Requestor requestor : requestors) {
try {
if (requestor.isResolved())
requestor.execute();
} catch (InvocationTargetException e) {
logError("Injection failed for the object \"" + object.toString()
+ "\". Unable to execute \"" + requestor.toString() + "\"");
return false;
} catch (InstantiationException e) {
logError("Injection failed for the object \"" + object.toString()
+ "\". Unable to execute \"" + requestor.toString() + "\"");
return false;
}
}
rememberInjectedObject(object, objectSupplier);
// TBD current tests assume that @PostConstruct methods will be
// called after injection; however, name implies that it is only
// called when the object is constructed. Fix this after the 1.4/1.5 merge.
try {
processPostConstruct(object, object.getClass(), objectSupplier,
new ArrayList<Class<?>>(5));
} catch (InvocationTargetException e) {
logError("Injection failed for the object \"" + object.toString()
+ "\". Unable to process post-construct methods.", e);
return false;
} catch (InstantiationException e) {
logError("Injection failed for the object \"" + object.toString()
+ "\". Unable to process post-construct methods.", e);
return false;
}
return true;
}
private void rememberInjectedObject(Object object, AbstractObjectSupplier objectSupplier) {
synchronized (injectedObjects) {
List<WeakReference<?>> list;
if (!injectedObjects.containsKey(objectSupplier)) {
list = new ArrayList<WeakReference<?>>();
injectedObjects.put(objectSupplier, list);
} else
list = injectedObjects.get(objectSupplier);
for (WeakReference<?> ref : list) {
if (object == ref.get())
return; // we already have it
}
list.add(new WeakReference<Object>(object));
}
}
private boolean forgetInjectedObject(Object object, AbstractObjectSupplier objectSupplier) {
synchronized (injectedObjects) {
if (!injectedObjects.containsKey(objectSupplier))
return false;
List<WeakReference<?>> list = injectedObjects.get(objectSupplier);
for (Iterator<WeakReference<?>> i = list.iterator(); i.hasNext();) {
WeakReference<?> ref = i.next();
if (object == ref.get())
i.remove();
return true;
}
return false;
}
}
private List<WeakReference<?>> forgetSupplier(AbstractObjectSupplier objectSupplier) {
synchronized (injectedObjects) {
if (!injectedObjects.containsKey(objectSupplier))
return null;
return injectedObjects.remove(objectSupplier);
}
}
private List<WeakReference<?>> getSupplierObjects(AbstractObjectSupplier objectSupplier) {
synchronized (injectedObjects) {
if (!injectedObjects.containsKey(objectSupplier))
return null;
return injectedObjects.get(objectSupplier);
}
}
public boolean uninject(Object object, AbstractObjectSupplier objectSupplier) {
if (!forgetInjectedObject(object, objectSupplier))
return false; // not injected at this time
// Two stages: first, go and collect {requestor, descriptor[] }
ArrayList<Requestor> requestors = new ArrayList<Requestor>();
processClassHierarchy(object, false /* no static */, 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[] }
if (!resolveRequestorArgs(requestors, null, true /* fill with nulls */))
return false;
// Call requestors in order
for (Requestor requestor : requestors) {
try {
requestor.execute();
} catch (InvocationTargetException e) {
logError("Uninjection failed for the object \"" + object.toString()
+ "\". Unable to execute \"" + requestor.toString() + "\"");
return false;
} catch (InstantiationException e) {
logError("Uninjection failed for the object \"" + object.toString()
+ "\". Unable to execute \"" + requestor.toString() + "\"");
return false;
}
}
return true;
}
public Object invoke(Object object, String methodName, AbstractObjectSupplier objectSupplier)
throws InvocationTargetException, CoreException {
Object result = invokeUsingClass(object, object.getClass(), methodName,
IInjector.NOT_A_VALUE, objectSupplier);
if (result == IInjector.NOT_A_VALUE) {
IStatus status = new Status(IStatus.ERROR, "org.eclipse.e4.core.services",
"Unable to find matching method to invoke");
throw new CoreException(status);
}
return result;
}
public Object invoke(Object object, String methodName, Object defaultValue,
AbstractObjectSupplier objectSupplier) throws InvocationTargetException {
return invokeUsingClass(object, object.getClass(), methodName, defaultValue, objectSupplier);
}
private Object invokeUsingClass(Object userObject, Class<?> currentClass, String methodName,
Object defaultValue, AbstractObjectSupplier objectSupplier)
throws InvocationTargetException {
Method[] methods = currentClass.getDeclaredMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if (!method.getName().equals(methodName))
continue;
MethodRequestor requestor = new MethodRequestor(method, userObject, false, false, true);
Object[] actualArgs = resolveArgs(requestor, objectSupplier, false);
int unresolved = unresolved(actualArgs);
if (unresolved != -1) {
logError("Injection failed for object \""
+ requestor.getRequestingObject().toString()
+ "\". Unable to find value for \""
+ requestor.getDependentObjects()[unresolved] + "\"");
return null;
}
requestor.setResolvedArgs(actualArgs);
try {
return requestor.execute();
} catch (InstantiationException e) {
// TBD clean up the error handling; need to propagate original exception
logError("Exception occured in the injected method " + method.getName(), e);
return null;
}
}
Class<?> superClass = currentClass.getSuperclass();
if (superClass == null) {
return defaultValue;
}
return invokeUsingClass(userObject, superClass, methodName, defaultValue, objectSupplier);
}
public Object make(Class<?> clazz, AbstractObjectSupplier objectSupplier)
throws InvocationTargetException, InstantiationException {
IObjectDescriptor descriptor = ObjectDescriptorFactory.make(clazz, false);
return make(descriptor, objectSupplier);
}
public Object make(IObjectDescriptor descriptor, AbstractObjectSupplier objectSupplier)
throws InvocationTargetException, InstantiationException {
IBinding binding = findBinding(descriptor);
if (binding == null) {
Class<?> desiredClass = descriptor.getElementClass();
return internalMake(desiredClass, objectSupplier);
}
return internalMake(binding.getImplementationClass(), objectSupplier);
}
private Object internalMake(Class<?> clazz, AbstractObjectSupplier objectSupplier)
throws InvocationTargetException, InstantiationException {
boolean isSingleton = clazz.isAnnotationPresent(Singleton.class);
if (isSingleton) {
synchronized (singletonCache) {
if (singletonCache.containsKey(clazz))
return singletonCache.get(clazz);
}
}
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
// Sort the constructors by descending number of constructor arguments
ArrayList<Constructor<?>> sortedConstructors = new ArrayList<Constructor<?>>(
constructors.length);
for (Constructor<?> constructor : constructors)
sortedConstructors.add(constructor);
Collections.sort(sortedConstructors, new Comparator<Constructor<?>>() {
public int compare(Constructor<?> c1, Constructor<?> c2) {
int l1 = c1.getParameterTypes().length;
int l2 = c2.getParameterTypes().length;
return l2 - l1;
}
});
for (Constructor<?> constructor : sortedConstructors) {
// skip private and protected constructors; allow public and package visibility
int modifiers = constructor.getModifiers();
if (((modifiers & Modifier.PRIVATE) != 0) || ((modifiers & Modifier.PROTECTED) != 0))
continue;
// unless this is the default constructor, it has to be tagged
InjectionProperties cProps = annotationSupport.getInjectProperties(constructor);
if (!cProps.shouldInject() && constructor.getParameterTypes().length != 0)
continue;
ConstructorRequestor requestor = new ConstructorRequestor(constructor);
Object[] actualArgs = resolveArgs(requestor, objectSupplier, false);
if (unresolved(actualArgs) != -1)
continue;
requestor.setResolvedArgs(actualArgs);
Object newInstance = requestor.execute();
if (newInstance != null) {
inject(newInstance, objectSupplier);
if (isSingleton) {
synchronized (singletonCache) { // TBD this is not quite right, synch the method
singletonCache.put(clazz, newInstance);
}
}
return newInstance;
}
}
logError("Could not find satisfiable constructor in class " + clazz.getName());
return null;
}
public boolean injectStatic(Class<?> clazz, AbstractObjectSupplier objectSupplier) {
// TBD add processing on a null object
Object object;
try {
object = make(clazz, objectSupplier);
} catch (InvocationTargetException e) {
// try-catch won't be necessary once we stop creating an object
e.printStackTrace();
return false;
} catch (InstantiationException e) {
// try-catch won't be necessary once we stop creating an object
e.printStackTrace();
return false;
}
// TBD this is copy/paste from as invoke() with static = true.
// Two stages: first, go and collect {requestor, descriptor[] }
ArrayList<Requestor> requestors = new ArrayList<Requestor>();
processClassHierarchy(object, true /* static */, true /* track */, true /* normal order */,
requestors);
// Ask suppliers to fill actual values {requestor, descriptor[], actualvalues[] }
if (!resolveRequestorArgs(requestors, objectSupplier, false))
return false;
// Call requestors in order
for (Requestor requestor : requestors) {
try {
requestor.execute();
} catch (InvocationTargetException e) {
logError("Injection failed for the object \"" + object.toString()
+ "\". Unable to execute \"" + requestor.toString() + "\"");
return false;
} catch (InstantiationException e) {
logError("Injection failed for the object \"" + object.toString()
+ "\". Unable to execute \"" + requestor.toString() + "\"");
return false;
}
}
return true;
}
public boolean update(IRequestor[] requestors, AbstractObjectSupplier objectSupplier) {
ArrayList<Requestor> list = new ArrayList<Requestor>(requestors.length);
for (IRequestor requestor : requestors) {
list.add((Requestor) requestor);
}
resolveRequestorArgs(list, objectSupplier, true);
// Call requestors in order
for (IRequestor requestor : requestors) {
try {
((Requestor) requestor).execute();
} catch (InvocationTargetException e) {
logError("Injection failed for the object \""
+ requestor.getRequestingObject().toString() + "\". Unable to execute \""
+ requestor.toString() + "\"");
return false;
} catch (InstantiationException e) {
logError("Injection failed for the object \""
+ requestor.getRequestingObject().toString() + "\". Unable to execute \""
+ requestor.toString() + "\"");
return false;
}
}
return true;
}
public boolean disposed(AbstractObjectSupplier objectSupplier) {
List<WeakReference<?>> references = getSupplierObjects(objectSupplier);
if (references == null)
return true;
Object[] objects = new Object[references.size()];
int count = 0;
for (WeakReference<?> ref : references) {
Object object = ref.get();
if (object != null) {
objects[count] = object;
count++;
}
}
for (int i = 0; i < count; i++) {
processPreDestory(objects[i], objects[i].getClass(), new ArrayList<Class<?>>(5));
uninject(objects[i], objectSupplier);
}
forgetSupplier(objectSupplier);
return true;
}
private void processPreDestory(Object userObject, Class<?> objectClass,
ArrayList<Class<?>> classHierarchy) {
Class<?> superClass = objectClass.getSuperclass();
if (superClass != null && !superClass.getName().equals(JAVA_OBJECT)) {
classHierarchy.add(objectClass);
processPreDestory(userObject, superClass, classHierarchy);
classHierarchy.remove(objectClass);
}
Method[] methods = objectClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getParameterTypes().length > 0) // TBD why?
continue;
if (!annotationSupport.isPreDestory(method))
continue;
if (!isOverridden(method, classHierarchy)) {
// TBD optional @PreDestory? might make sense if we allow args on those methods
MethodRequestor requestor = new MethodRequestor(method, userObject, false, false,
false);
try {
requestor.execute();
} catch (InvocationTargetException e) {
logError("Unable to call pre-destory method \"" + method.getName() + "\"", e);
} catch (InstantiationException e) {
logError("Unable to call pre-destory method \"" + method.getName() + "\"", e);
}
}
}
if (userObject instanceof IDisposable)
((IDisposable) userObject).dispose();
}
private boolean resolveRequestorArgs(ArrayList<Requestor> requestors,
AbstractObjectSupplier objectSupplier, boolean fillNulls) {
for (Requestor requestor : requestors) {
Object[] actualArgs = resolveArgs(requestor, objectSupplier, fillNulls);
int unresolved = unresolved(actualArgs);
if (unresolved == -1) {
requestor.setResolvedArgs(actualArgs);
continue;
}
if (requestor.isOptional())
requestor.setResolvedArgs(null);
else {
logError("Injection failed for object \""
+ requestor.getRequestingObject().toString()
+ "\". Unable to find value for \""
+ requestor.getDependentObjects()[unresolved] + "\"");
return false;
}
}
return true;
}
private Object[] resolveArgs(Requestor requestor, AbstractObjectSupplier objectSupplier,
boolean fillNulls) {
IObjectDescriptor[] descriptors = requestor.getDependentObjects();
// 0) initial fill - all are unresolved
Object[] actualArgs = new Object[descriptors.length];
for (int i = 0; i < actualArgs.length; i++) {
actualArgs[i] = NOT_A_VALUE;
}
// 1) check if we have a Provider<T>
for (int i = 0; i < actualArgs.length; i++) {
Class<?> providerClass = getProviderType(descriptors[i].getElementType());
if (providerClass == null)
continue;
actualArgs[i] = new ProviderImpl<Class<?>>(descriptors[i], this, objectSupplier);
descriptors[i] = null; // mark as used
}
// 2) use the primary supplier
if (objectSupplier != null) {
Object[] primarySupplierArgs = objectSupplier.get(descriptors, requestor);
for (int i = 0; i < actualArgs.length; i++) {
if (descriptors[i] == null)
continue; // already resolved
if (primarySupplierArgs[i] != NOT_A_VALUE) {
actualArgs[i] = primarySupplierArgs[i];
descriptors[i] = null; // mark as used
}
}
}
// 3) try extended suppliers
for (int i = 0; i < actualArgs.length; i++) {
if (descriptors[i] == null)
continue; // already resolved
AbstractObjectSupplier extendedSupplier = findExtendedSupplier(descriptors[i]);
if (extendedSupplier == null)
continue;
Object result = extendedSupplier.get(descriptors[i], requestor);
if (result != NOT_A_VALUE) {
actualArgs[i] = result;
descriptors[i] = null; // mark as used
}
}
// 4) try the bindings
for (int i = 0; i < actualArgs.length; i++) {
if (descriptors[i] == null)
continue; // already resolved
IBinding binding = findBinding(descriptors[i]);
if (binding != null) {
try {
actualArgs[i] = internalMake(binding.getImplementationClass(), objectSupplier);
} catch (InvocationTargetException e) {
logError("Unable to create object for class \""
+ binding.getImplementationClass() + "\".", e);
continue;
} catch (InstantiationException e) {
logError("Unable to create object for class \""
+ binding.getImplementationClass() + "\".", e);
continue;
}
}
}
// 5) post process
descriptors = requestor.getDependentObjects(); // reset nulled out values
for (int i = 0; i < descriptors.length; i++) {
// check that values are of a correct type
if (actualArgs[i] != null && actualArgs[i] != IInjector.NOT_A_VALUE) {
Class<?> descriptorsClass = descriptors[i].getElementClass();
if (!descriptorsClass.isAssignableFrom(actualArgs[i].getClass()))
actualArgs[i] = IInjector.NOT_A_VALUE;
}
// replace optional unresolved values with null
if (descriptors[i].isOptional() && actualArgs[i] == IInjector.NOT_A_VALUE)
actualArgs[i] = null;
else if (fillNulls && actualArgs[i] == IInjector.NOT_A_VALUE)
actualArgs[i] = null;
}
return actualArgs;
}
private AbstractObjectSupplier findExtendedSupplier(IObjectDescriptor descriptor) {
String[] qualifiers = descriptor.getQualifiers();
if (qualifiers == null)
return null;
for (String qualifier : qualifiers) {
if (extendedSuppliers.containsKey(qualifier))
return extendedSuppliers.get(qualifier);
}
return null;
}
private int unresolved(Object[] actualArgs) {
for (int i = 0; i < actualArgs.length; i++) {
if (actualArgs[i] == IInjector.NOT_A_VALUE)
return i;
}
return -1;
}
private void processClassHierarchy(Object userObject, boolean processStatic, boolean track,
boolean normalOrder, List<Requestor> requestors) {
processClass(userObject, (userObject == null) ? null : userObject.getClass(),
new ArrayList<Class<?>>(5), processStatic, track, normalOrder, requestors);
}
/**
* Make the processor visit all declared members on the given class and all superclasses
*/
private void processClass(Object userObject, Class<?> objectsClass,
ArrayList<Class<?>> classHierarchy, boolean processStatic, boolean track,
boolean normalOrder, List<Requestor> requestors) {
// order: superclass, fields, methods
if (objectsClass != null) {
Class<?> superClass = objectsClass.getSuperclass();
if (!superClass.getName().equals(JAVA_OBJECT)) {
classHierarchy.add(objectsClass);
processClass(userObject, superClass, classHierarchy, processStatic, track,
normalOrder, requestors);
classHierarchy.remove(objectsClass);
}
}
if (normalOrder) {
processFields(userObject, objectsClass, processStatic, track, requestors);
processMethods(userObject, objectsClass, classHierarchy, processStatic, track,
requestors);
} else {
processMethods(userObject, objectsClass, classHierarchy, processStatic, track,
requestors);
processFields(userObject, objectsClass, processStatic, track, requestors);
}
}
/**
* Make the processor visit all declared fields on the given class.
*/
private void processFields(Object userObject, Class<?> objectsClass, boolean processStatic,
boolean track, List<Requestor> requestors) {
Field[] fields = objectsClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (Modifier.isStatic(field.getModifiers()) != processStatic)
continue;
// XXX limit to "should inject"
InjectionProperties properties = annotationSupport.getInjectProperties(field);
// XXX this will be removed on 1.4/1.5 merge
if (field.getName().startsWith(IContextConstants.INJECTION_PREFIX))
properties.setInject(true);
if (!properties.shouldInject())
continue;
requestors.add(new FieldRequestor(field, userObject, track, properties.groupUpdates(),
properties.isOptional()));
}
}
/**
* Make the processor visit all declared methods on the given class.
*
* @throws InvocationTargetException
*/
private void processMethods(final Object userObject, Class<?> objectsClass,
ArrayList<Class<?>> classHierarchy, boolean processStatic, boolean track,
List<Requestor> requestors) {
Method[] methods = objectsClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
final Method method = methods[i];
if (isOverridden(method, classHierarchy))
continue; // process in the subclass
if (Modifier.isStatic(method.getModifiers()) != processStatic)
continue;
InjectionProperties properties = annotationSupport.getInjectProperties(method);
if (method.getName().startsWith(IContextConstants.INJECTION_PREFIX))
properties.setInject(true);
// replace with @Event("name") qualifier
// if (properties.getHandlesEvent() != null) {
// // XXX this is wrong, but it will be removed anyway
// ObjectDescriptor desc = ObjectDescriptor.make(IEventBroker.class);
// IEventBroker eventBroker = (IEventBroker) objectProvider.get(desc);
// eventBroker.subscribe(properties.getHandlesEvent(), null, new EventHandler() {
// public void handleEvent(Event event) {
// Object data = event.getProperty(IEventBroker.DATA);
// boolean wasAccessible = method.isAccessible();
// if (!wasAccessible) {
// method.setAccessible(true);
// }
// try {
// method.invoke(userObject, data);
// } catch (Exception e) {
// throw new RuntimeException(e);
// } finally {
// if (!wasAccessible) {
// method.setAccessible(false);
// }
// }
// }
// }, properties.getEventHeadless());
// continue;
// }
if (!properties.shouldInject())
continue;
requestors.add(new MethodRequestor(method, userObject, track,
properties.groupUpdates(), properties.isOptional()));
}
}
/**
* Checks if a given method is overridden with an injectable method.
*/
private boolean isOverridden(Method method, ArrayList<Class<?>> classHierarchy) {
int modifiers = method.getModifiers();
if (Modifier.isPrivate(modifiers))
return false;
if (Modifier.isStatic(modifiers))
return false;
// method is not private if we reached this line, check not(public OR protected)
boolean isDefault = !(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers));
for (Class<?> subClass : classHierarchy) {
try {
subClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
} catch (SecurityException e) {
continue;
} catch (NoSuchMethodException e) {
continue; // this is the desired outcome
}
if (isDefault) { // must be in the same package to override
Package originalPackage = method.getDeclaringClass().getPackage();
Package overridePackage = subClass.getPackage();
if (originalPackage == null && overridePackage == null)
return true;
if (originalPackage == null || overridePackage == null)
return false;
if (originalPackage.equals(overridePackage))
return true;
} else
return true;
}
return false;
}
private void processPostConstruct(Object userObject, Class<?> objectClass,
AbstractObjectSupplier objectSupplier, ArrayList<Class<?>> classHierarchy)
throws InvocationTargetException, InstantiationException {
Class<?> superClass = objectClass.getSuperclass();
if (superClass != null && !superClass.getName().equals(JAVA_OBJECT)) {
classHierarchy.add(objectClass);
processPostConstruct(userObject, superClass, objectSupplier, classHierarchy);
classHierarchy.remove(objectClass);
}
Method[] methods = objectClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (!isPostConstruct(method))
continue;
if (isOverridden(method, classHierarchy))
continue;
MethodRequestor requestor = new MethodRequestor(method, userObject, false, false, false);
Object[] actualArgs = resolveArgs(requestor, objectSupplier, false);
int unresolved = unresolved(actualArgs);
if (unresolved != -1) {
logError("Injection failed for object \""
+ requestor.getRequestingObject().toString()
+ "\". Unable to find value for \""
+ requestor.getDependentObjects()[unresolved] + "\"");
continue;
}
requestor.setResolvedArgs(actualArgs);
try {
requestor.execute();
} catch (InvocationTargetException e) {
logError("Unable to call post-construct method \"" + method.getName() + "\"", e);
} catch (InstantiationException e) {
logError("Unable to call post-construct method \"" + method.getName() + "\"", e);
}
}
}
// TBD simplify this: only one non-annotation and one "implements IInitializable"?
/**
* Returns whether the given method is a post-construction process method, as defined by the
* class comment of {@link ContextInjectionFactory}.
*/
private boolean isPostConstruct(Method method) {
boolean isPostConstruct = annotationSupport.isPostConstruct(method);
if (isPostConstruct)
return true;
if (!method.getName().equals(IContextConstants.INJECTION_SET_CONTEXT_METHOD))
return false;
Class<?>[] parms = method.getParameterTypes();
if (parms.length == 0)
return true;
if (parms.length == 1 && parms[0].equals(IEclipseContext.class))
return true;
return false;
}
/**
* Returns null if not a provider
*/
private Class<?> getProviderType(Type type) {
if (!(type instanceof ParameterizedType))
return null;
Type rawType = ((ParameterizedType) type).getRawType();
if (!(rawType instanceof Class<?>))
return null;
boolean isProvider = ((Class<?>) rawType).equals(Provider.class);
if (!isProvider)
return null;
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
if (actualTypes.length != 1)
return null;
if (!(actualTypes[0] instanceof Class<?>))
return null;
return (Class<?>) actualTypes[0];
}
public IBinding addBinding(Class<?> clazz) {
return addBinding(new Binding(clazz, this));
}
public IBinding addBinding(IBinding binding) {
Class<?> clazz = binding.getDescribedClass();
synchronized (bindings) {
if (bindings.containsKey(clazz)) {
Set<IBinding> collection = bindings.get(clazz);
String desiredQualifierName = binding.getQualifierName();
for (Iterator<IBinding> i = collection.iterator(); i.hasNext();) {
IBinding collectionBinding = i.next();
if (eq(collectionBinding.getQualifierName(), desiredQualifierName)) {
i.remove();
break;
}
}
collection.add(binding);
} else {
Set<IBinding> collection = new HashSet<IBinding>(1);
collection.add(binding);
bindings.put(clazz, collection);
}
}
return binding;
}
private IBinding findBinding(IObjectDescriptor descriptor) {
Class<?> desiredClass = getProviderType(descriptor.getElementType());
if (desiredClass == null)
desiredClass = descriptor.getElementClass();
synchronized (bindings) {
if (!bindings.containsKey(desiredClass))
return null;
Set<IBinding> collection = bindings.get(desiredClass);
String desiredQualifierName = descriptor.getQualifierValue(Named.class.getName());
for (Iterator<IBinding> i = collection.iterator(); i.hasNext();) {
IBinding collectionBinding = i.next();
if (eq(collectionBinding.getQualifierName(), desiredQualifierName))
return collectionBinding;
}
}
return null;
}
/**
* Are two, possibly null, string equal?
*/
private boolean eq(String str1, String str2) {
if (str1 == null && str2 == null)
return true;
if (str1 == null || str2 == null)
return false;
return str1.equals(str2);
}
// TBD implement logging
private void logError(String msg) {
logError(msg, new InjectionException());
}
private void logError(String msg, Throwable e) {
if (msg != null)
System.err.println(msg);
if (e != null)
e.printStackTrace();
}
}