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    }