// ********************************************************************** // // <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; } }