// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/util/ComponentFactory.java,v $
// $RCSfile: ComponentFactory.java,v $
// $Revision: 1.15 $
// $Date: 2006/05/19 15:26:26 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.bbn.openmap.BasicI18n;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.event.ProgressEvent;
import com.bbn.openmap.event.ProgressSupport;
/**
* The OpenMap ComponentFactory is a class that can construct objects from class
* names, with the added capability of passing the new object a Properties
* object to initialize itself. The new object may also receive a property
* prefix to use to scope its properties from the Properties object. It is
* sensitive to the OpenMap paradigm of marker names in a list: That a list of
* objects can be defined as a space separated names (marker names) within a
* String. Those marker names can serve as a prefix for other properties within
* a Properties object, as well as the prefix for a '.class' property to define
* the class name for the new object.
*/
public class ComponentFactory {
public static Logger logger = Logger.getLogger("com.bbn.openmap.util.ComponentFactory");
/**
* The property to use for the class name of new objects - ".class". Expects
* that a prefix will be prepended to it.
*/
public static final String DotClassNameProperty = ".class";
/**
* A property to use for the class name of new objects - "class". Can be
* used with the PropUtils.objectsFromProperties method as the defining
* property.
*/
public static final String ClassNameProperty = "class";
/**
* The singleton instance of the ComponentFactory.
*/
private static ComponentFactory singleton;
protected ComponentFactory() {
}
/**
* Method call to retrieve the singleton instance of the ComponentFactory.
*
* @return ComponentFactory.
*/
protected static ComponentFactory getInstance() {
if (singleton == null) {
singleton = new ComponentFactory();
}
return singleton;
}
/**
* Set the singleton instance of the ComponentFactory.
*
* @param cf
*/
protected static void setInstance(ComponentFactory cf) {
singleton = cf;
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param properties Properties object containing the details.
* @return Vector containing the new Objects.
*/
public static Vector<?> create(Vector<String> markerNames, Properties properties) {
return getInstance()._create(markerNames, null, properties, null, false);
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param prefix The prefix that should be prepended to the marker names.
* @param properties Properties object containing the details.
* @return Vector containing the new Objects.
*/
public static Vector<?> create(Vector<String> markerNames, String prefix, Properties properties) {
return getInstance()._create(markerNames, prefix, properties, null, false);
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param prefix The prefix that should be prepended to the marker names.
* @param properties Properties object containing the details.
* @param progressSupport ProgressSupport object to provide progress updates
* to. It's OK if this is null to not have progress events sent.
* @return Vector containing the new Objects.
*/
public static Vector<?> create(Vector<String> markerNames, String prefix, Properties properties, ProgressSupport progressSupport) {
return getInstance()._create(markerNames, prefix, properties, progressSupport, false);
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param properties Properties object containing the details.
* @param progressSupport ProgressSupport object to provide progress updates
* to. It's OK if this is null to not have progress events sent.
* @return Vector containing the new Objects.
*/
public static Vector<?> create(Vector<String> markerNames, Properties properties, ProgressSupport progressSupport) {
return getInstance()._create(markerNames, null, properties, progressSupport, false);
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param properties Properties object containing the details.
* @param progressSupport ProgressSupport object to provide progress updates
* to. It's OK if this is null to not have progress events sent.
* @param matchInOutVectorSize if true, then if there is any trouble
* creating an object, it's marker name will be placed in the
* returned vector instead of a component. If false, only valid
* objects will be returned in the vector.
* @return Vector containing the new Objects. If a component could not be
* created, the markerName is returned in its place, so you can
* figure out which one couldn't be created. In any case, the size
* of the returned vector is the same size as the markerNames
* vector, so you can figure out which markerNames go with which
* objects.
*/
public static Vector<?> create(Vector<String> markerNames, Properties properties, ProgressSupport progressSupport,
boolean matchInOutVectorSize) {
return getInstance()._create(markerNames, null, properties, progressSupport, matchInOutVectorSize);
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param prefix The prefix that should be prepended to the marker names.
* @param properties Properties object containing the details.
* @param progressSupport ProgressSupport object to provide progress updates
* to. It's OK if this is null to not have progress events sent.
* @param matchInOutVectorSize if true, then if there is any trouble
* creating an object, it's marker name will be placed in the
* returned vector instead of a component. If false, only valid
* objects will be returned in the vector.
* @return Vector containing the new Objects. If a component could not be
* created, the markerName is returned in its place, so you can
* figure out which one couldn't be created. In any case, the size
* of the returned vector is the same size as the markerNames
* vector, so you can figure out which markerNames go with which
* objects.
*/
public static Vector<?> create(Vector<String> markerNames, String prefix, Properties properties,
ProgressSupport progressSupport, boolean matchInOutVectorSize) {
return getInstance()._create(markerNames, prefix, properties, progressSupport, matchInOutVectorSize);
}
/**
* Given a Vector of marker name Strings, and a Properties object, look in
* the Properties object for the markerName.class property to get a class
* name to create each object. Then, if the new objects are
* PropertyConsumers, use the marker name as a property prefix to get
* properties for that object out of the Properties.
*
* @param markerNames String of space separated marker names.
* @param prefix The prefix that should be prepended to the marker names.
* @param properties Properties object containing the details.
* @param progressSupport ProgressSupport object to provide progress updates
* to. It's OK if this is null to not have progress events sent.
* @param matchInOutVectorSize if true, then if there is any trouble
* creating an object, it's marker name will be placed in the
* returned vector instead of a component. If false, only valid
* objects will be returned in the vector.
* @return Vector containing the new Objects. If a component could not be
* created, the markerName is returned in its place, so you can
* figure out which one couldn't be created. In any case, the size
* of the returned vector is the same size as the markerNames
* vector, so you can figure out which markerNames go with which
* objects.
*/
protected Vector<?> _create(Vector<String> markerNames, String prefix, Properties properties, ProgressSupport progressSupport,
boolean matchInOutVectorSize) {
int size = markerNames.size();
Vector<Object> vector = new Vector<Object>(size);
if (progressSupport != null) {
progressSupport.fireUpdate(ProgressEvent.UPDATE, "Creating Components", 100, 0);
}
for (int i = 0; i < size; i++) {
String componentName = PropUtils.getScopedPropertyPrefix(prefix) + markerNames.elementAt(i);
String classProperty = componentName + DotClassNameProperty;
String className = properties.getProperty(classProperty);
if (className == null) {
logger.warning("Failed to locate property \"" + componentName + "\" with class \"" + classProperty
+ "\"\n Skipping component \"" + componentName + "\"");
if (matchInOutVectorSize) {
vector.add(componentName);
}
continue;
}
if (progressSupport != null) {
progressSupport.fireUpdate(ProgressEvent.UPDATE, "Creating Components", size, i);
}
Object component = create(className, componentName, properties);
if (component != null) {
vector.add(component);
if (logger.isLoggable(Level.FINE)) {
logger.fine("ComponentFactory: [" + className + "(" + i + ")] created");
}
} else {
if (matchInOutVectorSize) {
vector.add(componentName);
}
logger.info("[" + componentName + " : " + className + "(" + i
+ ")] NOT created. -- Set logging flag to FINE/FINER for details.");
}
}
if (progressSupport != null) {
progressSupport.fireUpdate(ProgressEvent.UPDATE, "Configuring...", size, size);
}
return vector;
}
/**
* Create a single object.
*
* @param className Class name to instantiate, empty constructor.
* @return object if all goes well, null if not.
*/
public static Object create(String className) {
return create(className, (Object[]) null, null, null);
}
/**
* Create a single object.
*
* @param className Class name to instantiate.
* @param properties Properties to use to initialize the object, if the
* object is a PropertyConsumer.
* @return object if all goes well, null if not.
*/
public static Object create(String className, Properties properties) {
return create(className, (Object[]) null, null, properties);
}
/**
* Create a single object. If you want it to complain about classes it can't
* find, then set the 'basic' debug flag.
*
* @param className Class name to instantiate.
* @param prefix Properties prefix to use by the object to scope its
* properties.
* @param properties Properties to use to initialize the object, if the
* object is a PropertyConsumer.
* @return Object or null if it couldn't be created
*/
public static Object create(String className, String prefix, Properties properties) {
return create(className, (Object[]) null, prefix, properties);
}
/**
* Create a single object. If you want it to complain about classes it can't
* find, then set the 'basic' debug flag.
*
* @param className Class name to instantiate.
* @param constructorArgs an Object array of arguments to use in the
* constructor of the component.
* @return object if all goes well, null if anything bad happens.
*/
public static Object create(String className, Object[] constructorArgs) {
return create(className, constructorArgs, null, null, null);
}
/**
* Create a single object. If you want it to complain about classes it can't
* find, then set the 'basic' debug flag.
*
* @param className Class name to instantiate.
* @param constructorArgs an Object array of arguments to use in the
* constructor of the component.
* @param argClasses an array of classes to use to scope which constructor
* to use. If null, then an array will be built from the
* constructorArgs.
* @return object if all goes well, null if anything bad happens.
*/
public static Object create(String className, Object[] constructorArgs, Class<?>[] argClasses) {
return create(className, constructorArgs, argClasses, null, null);
}
/**
* Create a single object. If you want it to complain about classes it can't
* find, then set the 'basic' debug flag.
*
* @param className Class name to instantiate.
* @param constructorArgs an Object array of arguments to use in the
* constructor of the component.
* @param prefix Properties prefix to use by the object to scope its
* properties.
* @param properties Properties to use to initialize the object, if the
* object is a PropertyConsumer.
* @return object if all goes well, null if anything bad happens.
*/
public static Object create(String className, Object[] constructorArgs, String prefix, Properties properties) {
return create(className, constructorArgs, null, prefix, properties);
}
/**
* Create a single object. If you want it to complain about classes it can't
* find, then set the 'basic' debug flag.
*
* @param className Class name to instantiate.
* @param constructorArgs an Object array of arguments to use in the
* constructor of the component.
* @param argClasses an array of classes to use to scope which constructor
* to use. If null, then an array will be built from the
* constructorArgs.
* @param prefix Properties prefix to use by the object to scope its
* properties.
* @param properties Properties to use to initialize the object, if the
* object is a PropertyConsumer.
* @return object if all goes well, null if anything bad happens.
*/
public static Object create(String className, Object[] constructorArgs, Class<?>[] argClasses, String prefix,
Properties properties) {
return getInstance()._create(className, constructorArgs, argClasses, prefix, properties);
}
/**
* Create a single object. If you want it to complain about classes it can't
* find, then set the 'basic' debug flag.
*
* @param className Class name to instantiate.
* @param constructorArgs an Object array of arguments to use in the
* constructor of the component.
* @param argClasses an array of classes to use to scope which constructor
* to use. If null, then an array will be built from the
* constructorArgs.
* @param prefix Properties prefix to use by the object to scope its
* properties.
* @param properties Properties to use to initialize the object, if the
* object is a PropertyConsumer.
* @return object if all goes well, null if anything bad happens.
*/
protected Object _create(String className, Object[] constructorArgs, Class<?>[] argClasses, String prefix, Properties properties) {
String errorMessage = null;
Throwable exceptionCaught = null;
boolean DEBUG = false;
try {
if (logger.isLoggable(Level.FINER)) {
DEBUG = true;
logger.finer("creating: " + className);
}
// Apparently, this fails in certain cases where OpenMap is being
// used as a plugin in a NetBeans or Eclipse architecture and the
// application classloader isn't aware of the plugins classes. It
// limits the creation of the object to classes in the caller's
// classloader.
// Class newObjClass = Class.forName(className.trim());
// replaced with:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = this.getClass().getClassLoader();
}
Class<?> newObjClass = Class.forName(className.trim(), true, cl);
if (DEBUG)
logger.finer(" - got class for " + className);
if (argClasses == null) {
if (constructorArgs != null && constructorArgs.length > 0) {
argClasses = new Class[constructorArgs.length];
for (int i = 0; i < argClasses.length; i++) {
argClasses[i] = constructorArgs[i].getClass();
}
} else {
// If empty, make null
constructorArgs = null;
}
}
if (DEBUG) {
StringBuffer sb = new StringBuffer();
if (constructorArgs == null) {
sb.append("null");
} else {
for (int i = 0; i < constructorArgs.length; i++) {
sb.append(constructorArgs[i].getClass().getName());
if (i < constructorArgs.length - 1)
sb.append(", ");
}
}
logger.finer(" - created class arguments [" + sb.toString() + "]");
}
Constructor<?> constructor = null;
Object obj = null;
try {
constructor = newObjClass.getConstructor(argClasses);
if (DEBUG)
logger.finer(" - got constructor");
// Create component
obj = constructor.newInstance(constructorArgs);
if (DEBUG)
logger.finer(" - got object");
} catch (NoSuchMethodException nsmei) {
/*
* The argClasses may have subclasses of what the desired
* constructor needs, so we need to check explicitly.
*/
obj = createWithSubclassConstructorArgs(newObjClass, argClasses, constructorArgs);
if (DEBUG && obj != null)
logger.finer(" - got object on try #2");
}
if (obj instanceof PropertyConsumer && properties != null) {
if (DEBUG) {
logger.finer(" setting properties with prefix \"" + prefix + "\"");
}
((PropertyConsumer) obj).setProperties(prefix, properties);
if (Debug.debugging(BasicI18n.DEBUG_CREATE)) {
/*
* If we're interested in creating resource bundle files, we
* should cause these PropertyConsumers to ask for their
* property info, since this is where most of the elective
* GUI strings are queried and found.
*/
((PropertyConsumer) obj).getPropertyInfo(null);
}
if (DEBUG)
logger.finer(" - set properties");
}
return obj;
} catch (NoSuchMethodException nsme) {
exceptionCaught = nsme;
errorMessage = "NoSuchMethodException: " + nsme.getMessage();
} catch (InstantiationException ie) {
exceptionCaught = ie;
errorMessage = "InstantiationException: " + ie.getMessage() + " - Might be trying to create an abstract class";
} catch (IllegalAccessException iae) {
if (DEBUG)
iae.printStackTrace();
exceptionCaught = iae;
errorMessage = "IllegalAccessException: " + iae.getMessage();
} catch (IllegalArgumentException iae2) {
if (DEBUG)
iae2.printStackTrace();
exceptionCaught = iae2;
errorMessage = "IllegalArgumentException: " + iae2.getMessage();
} catch (InvocationTargetException ite) {
if (DEBUG)
ite.printStackTrace();
exceptionCaught = ite;
errorMessage = "InvocationTargetException: " + ite.getMessage();
} catch (ClassNotFoundException cnfe) {
exceptionCaught = cnfe;
errorMessage = "ClassNotFoundException: " + cnfe.getMessage();
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Failed to create \"" + className
+ (prefix != null ? "\" using component marker name \"" + prefix + "\"" : "") + " - error message: "
+ errorMessage);
if (exceptionCaught != null) {
logger.log(Level.WARNING, "Exception reported is as follows:", exceptionCaught);
}
}
return null;
}
/**
* Method to create Object with arguments.
*
* @param newObjClass the Class to be created.
* @param argClasses an array of Classes describing the arguments.
* @param constructorArgs an array of Objects for arguments.
* @return Object created from the Class and arguments.
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
protected Object createWithSubclassConstructorArgs(Class<?> newObjClass, Class<?>[] argClasses, Object[] constructorArgs)
throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
boolean DEBUG = logger.isLoggable(Level.FINER);
int numArgClasses = 0;
if (argClasses != null) {
numArgClasses = argClasses.length;
}
Constructor<?>[] constructors = newObjClass.getConstructors();
int numConstructors = constructors.length;
if (DEBUG) {
logger.finer(" - searching " + numConstructors + " possible constructor" + (numConstructors == 1 ? "" : "s"));
}
for (int i = 0; i < numConstructors; i++) {
Constructor<?> constructor = constructors[i];
Class<?>[] arguments = constructor.getParameterTypes();
int numArgs = arguments.length;
// First, check the number of arguments for a match
if (numArgs != numArgClasses) {
if (DEBUG) {
logger.finer(" - constructor " + i + " with " + numArgs + " arguments not a match");
}
continue; // Nope, not it.
}
// OK, empty constructor desired, punch...
// If argClasses == null, then numArgs will equal zero. Makes the
// compiler happy.
if (numArgs == 0 || argClasses == null) {
if (DEBUG) {
logger.finer(" - constructor " + i + " with no arguments is a match");
}
return constructor;
}
// Check to see if the argument classes of the Constructor
// are assignable to the desired argClasses being sought.
boolean good = false;
for (int j = 0; j < numArgs; j++) {
if (arguments[j] == argClasses[j]) {
if (DEBUG) {
logger.finer(" - direct arg class match, arg " + j);
}
good = true; // Maintain true...
} else if (arguments[j].isAssignableFrom(argClasses[j])) {
// Doesn't work quite yet. May have to check for
// super-super class,etc, and we still get an
// IllegalArgumentException due to argument type
// mismatch.
// Is this even necessary? Don't think so...
argClasses[j] = argClasses[j].getSuperclass();
if (DEBUG) {
logger.finer(" - superclass arg class match, arg " + j + " reassigning to " + argClasses[j].toString());
}
good = true; // Maintain true...
// } else if (constructorArgs[j] instanceof
// Number) {
// if (DEBUG) {
// Debug.output(" - Number type match, arg " + j);
// }
// good = true; // Maintain true...
} else {
if (DEBUG) {
logger.finer(" - arg class mismatch on arg " + j + ", bailing (" + arguments[j].getName() + " vs. "
+ argClasses[j].getName() + ")");
}
good = false; // Punch with false
break;
}
}
if (good) {
if (DEBUG) {
logger.finer(" - creating object");
}
Object obj = constructor.newInstance(constructorArgs);
if (DEBUG) {
logger.finer(" - created object");
}
return obj;
}
}
return null;
}
}