blob: 88829a8c95efc3d5a1e4e6162aa4a96c868f20a6 [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.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.e4.core.internal.services.ServicesActivator;
import org.eclipse.e4.core.services.context.spi.IContextConstants;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.core.services.injector.IInjector;
import org.eclipse.e4.core.services.injector.IObjectProvider;
import org.eclipse.e4.core.services.injector.ObjectDescriptor;
import org.eclipse.e4.core.services.internal.annotations.AnnotationsSupport;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
/**
* Reflection-based dependency injector.
*/
public class InjectorImpl implements IInjector {
final static private String DEBUG_INJECTOR = "org.eclipse.e4.core.services/debug/injector"; //$NON-NLS-1$
final static private boolean shouldTrace = ServicesActivator.getDefault()
.getBooleanDebugOption(DEBUG_INJECTOR, false);
final static private String JAVA_OBJECT = "java.lang.Object"; //$NON-NLS-1$
// plug-in class that gets replaced in Java 1.5+
final private static AnnotationsSupport annotationSupport = new AnnotationsSupport(); // XXX
// remove;
public InjectorImpl() {
// placeholder
}
public boolean inject(Object userObject, IObjectProvider objectProvider) {
boolean result = false;
try {
result = processClassHierarchy(userObject, false /* process static */, objectProvider);
} catch (InvocationTargetException e) {
logExternalError("Exception occured while processing injecting", userObject, e);
}
objectProvider.runAndTrack(new InjectionClass(userObject, objectProvider), null);
return result;
}
// TBD use null object to inject statics
public boolean injectStatic(Class clazz, IObjectProvider objectProvider) {
try {
Object object = make(clazz, objectProvider);
return processClassHierarchy(object, true /* process static */, objectProvider);
} catch (InvocationTargetException e) {
// try-catch won't be necessary once we stop creating an object
e.printStackTrace();
} catch (InstantiationException e) {
// try-catch won't be necessary once we stop creating an object
e.printStackTrace();
}
return false;
}
/**
* Make the processor visit all declared members on the given class and all superclasses
*
* @throws InvocationTargetException
*/
private boolean processClass(Object userObject, Class objectsClass, ArrayList classHierarchy,
boolean processStatic, IObjectProvider objectProvider) throws InvocationTargetException {
// order: superclass, fields, methods
if (objectsClass != null) {
Class superClass = objectsClass.getSuperclass();
if (!superClass.getName().equals(JAVA_OBJECT)) {
classHierarchy.add(objectsClass);
if (!processClass(userObject, superClass, classHierarchy, processStatic,
objectProvider))
return false;
classHierarchy.remove(objectsClass);
}
}
if (!processFields(userObject, objectsClass, processStatic, objectProvider))
return false;
if (!processMethods(userObject, objectsClass, classHierarchy, processStatic, objectProvider))
return false;
return true;
}
private boolean processClassHierarchy(Object userObject, boolean processStatic,
IObjectProvider objectProvider) throws InvocationTargetException {
if (!processClass(userObject, (userObject == null) ? null : userObject.getClass(),
new ArrayList(5), processStatic, objectProvider))
return false;
return true;
}
/**
* Make the processor visit all declared fields on the given class.
*/
private boolean processFields(Object userObject, Class objectsClass, boolean processStatic,
IObjectProvider objectProvider) {
Field[] fields = objectsClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (Modifier.isStatic(field.getModifiers()) != processStatic)
continue;
InjectionProperties properties = annotationSupport.getInjectProperties(field,
objectProvider);
if (field.getName().startsWith(IContextConstants.INJECTION_PREFIX))
properties.setInject(true);
if (!properties.shouldInject())
continue;
objectProvider.runAndTrack(new InjectionField(userObject, objectProvider, field,
properties.groupUpdates()), null);
}
return true;
}
/**
* Make the processor visit all declared methods on the given class.
*
* @throws InvocationTargetException
*/
private boolean processMethods(final Object userObject, Class objectsClass,
ArrayList classHierarchy, boolean processStatic, IObjectProvider objectProvider)
throws InvocationTargetException {
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,
objectProvider);
if (method.getName().startsWith(IContextConstants.INJECTION_PREFIX))
properties.setInject(true);
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;
objectProvider.runAndTrack(new InjectionMethod(userObject, objectProvider, method,
properties.groupUpdates()), null);
}
return true;
}
// TBD this is the same method as in the class injector
/**
* Checks if a given method is overridden with an injectable method.
*/
private boolean isOverridden(Method method, ArrayList 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 (Iterator i = classHierarchy.iterator(); i.hasNext();) {
Class subClass = (Class) i.next();
Method override = null;
try {
override = subClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
} catch (SecurityException e) {
continue;
} catch (NoSuchMethodException e) {
continue; // this is the desired outcome
}
if (override != null) {
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;
}
public Object invoke(Object userObject, String methodName, IObjectProvider objectProvider)
throws InvocationTargetException, CoreException {
Method[] methods = userObject.getClass().getDeclaredMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if (!method.getName().equals(methodName))
continue;
InjectionMethod injectMethod = new InjectionMethod(userObject, objectProvider, method,
false);
try {
return injectMethod.invoke(false, false);
} catch (InjectionException e) {
IStatus status = new Status(IStatus.ERROR, "org.eclipse.e4.core.services",
"Unable to invoke method");
throw new CoreException(status);
}
}
IStatus status = new Status(IStatus.ERROR, "org.eclipse.e4.core.services",
"Unable to find matching method to invoke");
throw new CoreException(status);
}
public Object invoke(Object userObject, String methodName, Object defaultValue,
IObjectProvider objectProvider) throws InvocationTargetException {
return invokeUsingClass(userObject, userObject.getClass(), methodName, defaultValue,
objectProvider);
}
public Object invokeUsingClass(Object userObject, Class currentClass, String methodName,
Object defaultValue, IObjectProvider objectProvider) throws InvocationTargetException {
Method[] methods = currentClass.getDeclaredMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if (!method.getName().equals(methodName))
continue;
InjectionMethod injectMethod = new InjectionMethod(userObject, objectProvider, method,
false);
try {
return injectMethod.invoke(false, false);
} catch (InjectionException e) {
// TBD? abort or continue?
logExternalError("Exception occured in the injectd method " + method.getName(),
userObject, e);
}
}
Class superClass = currentClass.getSuperclass();
if (superClass == null) {
return defaultValue;
}
return invokeUsingClass(userObject, superClass, methodName, defaultValue, objectProvider);
}
public Object make(Class clazz, IObjectProvider objectProvider)
throws InvocationTargetException, InstantiationException {
Constructor[] constructors = clazz.getDeclaredConstructors();
// Sort the constructors by descending number of constructor arguments
ArrayList sortedConstructors = new ArrayList(constructors.length);
for (int i = 0; i < constructors.length; i++)
sortedConstructors.add(constructors[i]);
Collections.sort(sortedConstructors, new Comparator() {
public int compare(Object c1, Object c2) {
int l1 = ((Constructor) c1).getParameterTypes().length;
int l2 = ((Constructor) c2).getParameterTypes().length;
return l2 - l1;
}
});
for (Iterator i = sortedConstructors.iterator(); i.hasNext();) {
Constructor constructor = (Constructor) i.next();
// skip private and protected constructors; allow public and package visibility
if (((constructor.getModifiers() & Modifier.PRIVATE) != 0)
|| ((constructor.getModifiers() & Modifier.PROTECTED) != 0))
continue;
// unless this is the default constructor, it has to be tagged
InjectionProperties cProps = annotationSupport.getInjectProperties(constructor,
objectProvider);
if (!cProps.shouldInject() && constructor.getParameterTypes().length != 0)
continue;
InjectionConstructor injectedConstructor = new InjectionConstructor(null,
objectProvider, constructor);
Object newInstance = injectedConstructor.make();
if (newInstance != null) {
inject(newInstance, objectProvider);
return newInstance;
}
}
if (shouldTrace)
System.out
.println("Could not find satisfiable constructor in class " + clazz.getName());
return null;
}
private void logExternalError(String msg, Object destination, Exception e) {
System.out.println(msg + " " + destination.toString()); //$NON-NLS-1$
if (e != null)
e.printStackTrace();
// TBD convert this into real logging
// String msg = NLS.bind("Injection failed", destination.toString());
// RuntimeLog.log(new Status(IStatus.WARNING,
// IRuntimeConstants.PI_COMMON, 0, msg, e));
}
}