001 /* 002 * Copyright 2003-2004 The Apache Software Foundation. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.apache.commons.mapper.util; 018 019 import java.io.Serializable; 020 import java.util.HashMap; 021 import java.util.Map; 022 023 /** 024 * <p> 025 * <code>ObjectFactory</code> maps string names to classes to be instantiated. 026 * Given a name it will construct a new object of the mapped type using 027 * reflection. Pass in a <code>Map</code> with entries in the form 028 * "logical name"="fully qualified class name". Both key and value must be 029 * Strings. 030 * </p> 031 * 032 * <p> 033 * For quick instantiation from a String, use the class method 034 * <code>ObjectFactory.construct(String)</code>. 035 * </p> 036 * 037 * <p> 038 * The classes to be constructed must have a public no-arg constructor. An 039 * Exception thrown during the loading of a Class or creation of an Object is 040 * considered a programmer error and is converted to an 041 * <code>IllegalArgumentException</code>. 042 * This greatly simplifies client code and indicates a misconfiguration of the 043 * <code>Map</code> passed to this factory. This class is thread-safe. 044 * </p> 045 * 046 * <p> 047 * Example: 048 * <pre> 049 * Map map = new HashMap(); 050 * map.put("list", "java.util.ArrayList"); 051 * map.put("set", "java.util.HashSet"); 052 * 053 * ObjectFactory factory = new ObjectFactory(map); 054 * Set mySet = (Set) factory.construct("set"); 055 * </pre> 056 * </p> 057 * 058 * <p> 059 * Using ObjectFactory without a map: 060 * <br/> 061 * <code>Set mySet = (Set) ObjectFactory.construct("java.util.HashSet");</code> 062 * </p> 063 * 064 * <p> 065 * This class is useful as a backing class for higher level factory classes. 066 * The higher level class can use <code>ObjectFactory</code> to do the mapping 067 * and creation work while it implements factory specific semantics and caching. 068 * </p> 069 */ 070 public class ObjectFactory implements Cloneable, Serializable { 071 072 /** 073 * Stores "name"="qualified class name". 074 */ 075 protected Map nameMap = new HashMap(); 076 077 /** 078 * Stores "name"=Class. Caches Class objects for quick lookup. These Class 079 * objects act as prototypes to create new instances from. 080 */ 081 private Map prototypes = new HashMap(); 082 083 /** 084 * Create an ObjectFactory with the given mapping of names to fully 085 * qualified class names. 086 * @param map A map of logical names to fully qualified class names. 087 */ 088 public ObjectFactory(Map map) { 089 super(); 090 this.nameMap = map; 091 } 092 093 /** 094 * Convenience method that creates an object of the class given as a string. 095 * @param className The fully qualified class name of the object to be 096 * created. 097 * @throws IllegalArgumentException if the Class for the given name could 098 * not be found. 099 */ 100 public static Object construct(String className) { 101 return newInstance(loadClass(className)); 102 } 103 104 /** 105 * Returns an object of the class associated with the given command. 106 * @throws IllegalArgumentException if given name was not found in the map, 107 * the Class for the name could not be found, or the Class is missing a 108 * no-arg constructor. 109 */ 110 public Object create(String name) { 111 return newInstance(this.getPrototype(name)); 112 } 113 114 /** 115 * Calls c.newInstance() and converts exceptions. 116 * @throws IllegalArgumentException if there is an error instantiating an 117 * object of the class. This usually indicates the class is missing a 118 * public no-arg constructor. 119 */ 120 private static Object newInstance(Class c) { 121 try { 122 return c.newInstance(); 123 124 } catch (InstantiationException e) { 125 throw new IllegalArgumentException( 126 c 127 + " could not be created: " 128 + e.getMessage() 129 + ". Check for public no-arg constructor."); 130 } catch (IllegalAccessException e) { 131 throw new IllegalArgumentException( 132 c 133 + " could not be created: " 134 + e.getMessage() 135 + ". Check for public no-arg constructor."); 136 } 137 } 138 139 /** 140 * Returns the Class object mapped to the given name. If not found in the 141 * cache, the Class object is stored there for quick retrieval. 142 * @throws IllegalArgumentException if the given name is not mapped to a 143 * class name or the Class could not be found. 144 */ 145 private Class getPrototype(String logicalName) { 146 147 Class c = (Class) this.prototypes.get(logicalName); 148 if (c == null) { 149 String className = (String) this.nameMap.get(logicalName); 150 151 if (className == null) { 152 throw new IllegalArgumentException( 153 "Name not found in map: " + logicalName); 154 } 155 156 c = loadClass(className); 157 158 this.prototypes.put(logicalName, c); 159 } 160 161 return c; 162 } 163 164 /** 165 * Attempts to load the Class object for the given class name. 166 * @param className The fully qualified class name to load. 167 * @return The Class object for the class name. 168 * @throws IllegalArgumentException if the Class is not found for the 169 * given name. 170 */ 171 private static Class loadClass(String className) { 172 try { 173 return Class.forName(className); 174 175 } catch (ClassNotFoundException e) { 176 throw new IllegalArgumentException( 177 "Class '" 178 + className 179 + "' could not be found: " 180 + e.getMessage()); 181 } 182 } 183 184 /** 185 * Gets a copy of the <code>Map</code> this factory is using to 186 * construct instances of classes. 187 */ 188 public Map getMap() { 189 return new HashMap(this.nameMap); 190 } 191 192 /** 193 * Returns true if the ObjectFactory's maps are equal. 194 */ 195 public boolean equals(Object o) { 196 if (o == this) { 197 return true; 198 } 199 200 if (o instanceof ObjectFactory) { 201 ObjectFactory of = (ObjectFactory) o; 202 return of.nameMap.equals(this.nameMap); 203 } 204 205 return false; 206 } 207 208 /** 209 * Returns a cloned instance of this ObjectFactory. 210 */ 211 public Object clone() { 212 ObjectFactory of = null; 213 try { 214 of = (ObjectFactory) super.clone(); 215 } catch (CloneNotSupportedException e) { 216 // Object does support clone 217 } 218 219 of.prototypes = new HashMap(this.prototypes); 220 of.nameMap = new HashMap(this.nameMap); 221 222 return of; 223 } 224 225 /** 226 * ObjectFactory's hashcode is based on its underlying Map's hashcodes. 227 */ 228 public int hashCode() { 229 return this.nameMap.hashCode() * 176543; 230 } 231 232 }