blob: b75f7cbf02bee40982390326e33af29295912580 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 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.ui.tweaklets.dependencyinjection;
import java.lang.reflect.Constructor;
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.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.tweaklets.dependencyinjection.ClassIdentifier;
import org.eclipse.ui.services.IServiceLocator;
/**
* Factory that uses reflection to create instances by constructor injection.
* The factory attempts to create the target class by invoking the greediest
* satisfiable constructor. That is, the constructor with the greatest number of
* arguments where all the arguments can be found as services.
*
* <p>
* This factory should only be used in situations where a component only has one
* constructor, or where it is completely obvious which constructor is the
* greediest. If a component has many different constructors or needs to take
* arguments to its constructor which can't simply be located by using the
* argument types as service keys, clients should implement a custom factory
* rather than trying to reuse this class.
* </p>
*
* <p>
* This class is normally referred to in a plugin.xml file as part of some
* extension markup. This will work for for any extension point that looks for
* objects of type DIFactory. (such as the org.eclipse.ui.views extension
* point).
* </p>
*
* <p>
* For example, the following XML markup would create the
* org.eclipse.ui.examples.components.views.context.RedirectAllView view by
* passing any dependencies it needs into its constructor. Notice that CIFactory
* needs to be given the name of the class to create by separating it with a
* colon.
* </p>
* <code>
* <view
* class="org.eclipse.ui.services.CIFactory:org.eclipse.ui.examples.components.views.context.RedirectAllView"
* category="org.eclipse.ui.examples.components.context"
* name="RedirectAllView"
* id="org.eclipse.ui.examples.components.views.context.RedirectAllView"/>
*
* </code>
*
* <p>
* Not intended to be subclassed or instantiated by clients.
* </p>
*
* @since 3.4
*/
public class CIFactory extends DIFactory implements IExecutableExtension {
private ClassIdentifier classId = null;
private Class targetClass = null;
/**
* Should only be called when this factory is created through an extension
* point. If this constructor is used, the caller MUST call
* setInitializationData before attempting to use the factory. Call one of
* the other constructors if instantiating this object programmatically.
*/
public CIFactory() {
}
public void setInitializationData(IConfigurationElement config,
String propertyName, Object data) throws CoreException {
if (!(data instanceof String)) {
throw new CoreException(StatusUtil.newStatus(IStatus.ERROR,
"initialization data must be a string", null)); //$NON-NLS-1$
}
classId = new ClassIdentifier(config.getContributor().getName(),
(String) data);
}
public Object createObject(IServiceLocator serviceLocator) {
Class targetClass = this.targetClass;
// Find the greediest satisfiable constructor
if (targetClass == null) {
try {
targetClass = classId.loadClass();
} catch (ClassNotFoundException e) {
throw new RuntimeException(
"could not find class " + classId.getTypeName() + " in " + classId.getNamespace(), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
Constructor targetConstructor = null;
Constructor[] constructors = targetClass.getConstructors();
// Optimization: if there's only one constructor, use it.
if (constructors.length == 1) {
targetConstructor = constructors[0];
} else {
ArrayList toSort = new ArrayList();
for (int i = 0; i < constructors.length; i++) {
Constructor constructor = constructors[i];
// Filter out non-public constructors
if ((constructor.getModifiers() & Modifier.PUBLIC) != 0) {
toSort.add(constructor);
}
}
// Sort the constructors by descending number of constructor
// arguments
Collections.sort(toSort, new Comparator() {
/*
* (non-Javadoc)
*
* @see java.util.Comparator#compare(java.lang.Object,
* java.lang.Object)
*/
public int compare(Object arg0, Object arg1) {
Constructor c1 = (Constructor) arg0;
Constructor c2 = (Constructor) arg1;
int l1 = c1.getParameterTypes().length;
int l2 = c2.getParameterTypes().length;
return l1 - l2;
}
});
// Find the first satisfiable constructor
for (Iterator iter = toSort.iterator(); iter.hasNext()
&& targetConstructor == null;) {
Constructor next = (Constructor) iter.next();
boolean satisfiable = true;
Class[] params = next.getParameterTypes();
for (int i = 0; i < params.length && satisfiable; i++) {
Class clazz = params[i];
if (!serviceLocator.hasService(clazz)) {
satisfiable = false;
}
}
if (satisfiable) {
targetConstructor = next;
}
}
}
if (targetConstructor == null) {
throw new RuntimeException(
"could not find satisfiable constructor in class " + classId.getTypeName() + " in " + classId.getNamespace()); //$NON-NLS-1$//$NON-NLS-2$
}
Class[] paramKeys = targetConstructor.getParameterTypes();
try {
Object[] params = new Object[paramKeys.length];
for (int i = 0; i < params.length; i++) {
params[i] = serviceLocator.getService(paramKeys[i]);
}
return targetConstructor.newInstance(params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}