001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.commons.collections.functors;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.IOException;
022    import java.io.ObjectInputStream;
023    import java.io.ObjectOutputStream;
024    import java.io.Serializable;
025    import java.lang.reflect.InvocationTargetException;
026    import java.lang.reflect.Method;
027    
028    import org.apache.commons.collections.Factory;
029    import org.apache.commons.collections.FunctorException;
030    
031    /**
032     * Factory implementation that creates a new instance each time based on a prototype.
033     * 
034     * @since Commons Collections 3.0
035     * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
036     *
037     * @author Stephen Colebourne
038     */
039    public class PrototypeFactory {
040    
041        /**
042         * Factory method that performs validation.
043         * <p>
044         * Creates a Factory that will return a clone of the same prototype object
045         * each time the factory is used. The prototype will be cloned using one of these
046         * techniques (in order):
047         * <ul>
048         * <li>public clone method
049         * <li>public copy constructor
050         * <li>serialization clone
051         * <ul>
052         *
053         * @param prototype  the object to clone each time in the factory
054         * @return the <code>prototype</code> factory
055         * @throws IllegalArgumentException if the prototype is null
056         * @throws IllegalArgumentException if the prototype cannot be cloned
057         */
058        public static Factory getInstance(Object prototype) {
059            if (prototype == null) {
060                return ConstantFactory.NULL_INSTANCE;
061            }
062            try {
063                Method method = prototype.getClass().getMethod("clone", (Class[]) null);
064                return new PrototypeCloneFactory(prototype, method);
065    
066            } catch (NoSuchMethodException ex) {
067                try {
068                    prototype.getClass().getConstructor(new Class[] { prototype.getClass()});
069                    return new InstantiateFactory(
070                        prototype.getClass(),
071                        new Class[] { prototype.getClass()},
072                        new Object[] { prototype });
073    
074                } catch (NoSuchMethodException ex2) {
075                    if (prototype instanceof Serializable) {
076                        return new PrototypeSerializationFactory((Serializable) prototype);
077                    }
078                }
079            }
080            throw new IllegalArgumentException("The prototype must be cloneable via a public clone method");
081        }
082    
083        /**
084         * Constructor that performs no validation.
085         * Use <code>getInstance</code> if you want that.
086         */
087        private PrototypeFactory() {
088            super();
089        }
090    
091        // PrototypeCloneFactory
092        //-----------------------------------------------------------------------
093        /**
094         * PrototypeCloneFactory creates objects by copying a prototype using the clone method.
095         */
096        static class PrototypeCloneFactory implements Factory, Serializable {
097            
098            /** The serial version */
099            private static final long serialVersionUID = 5604271422565175555L;
100            
101            /** The object to clone each time */
102            private final Object iPrototype;
103            /** The method used to clone */
104            private transient Method iCloneMethod;
105    
106            /**
107             * Constructor to store prototype.
108             */
109            private PrototypeCloneFactory(Object prototype, Method method) {
110                super();
111                iPrototype = prototype;
112                iCloneMethod = method;
113            }
114    
115            /**
116             * Find the Clone method for the class specified.
117             */
118            private void findCloneMethod() {
119                try {
120                    iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null);
121    
122                } catch (NoSuchMethodException ex) {
123                    throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public ");
124                }
125            }
126    
127            /**
128             * Creates an object by calling the clone method.
129             * 
130             * @return the new object
131             */
132            public Object create() {
133                // needed for post-serialization
134                if (iCloneMethod == null) {
135                    findCloneMethod();
136                }
137    
138                try {
139                    return iCloneMethod.invoke(iPrototype, (Object[])null);
140    
141                } catch (IllegalAccessException ex) {
142                    throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex);
143                } catch (InvocationTargetException ex) {
144                    throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex);
145                }
146            }
147        }
148    
149        // PrototypeSerializationFactory
150        //-----------------------------------------------------------------------
151        /**
152         * PrototypeSerializationFactory creates objects by cloning a prototype using serialization.
153         */
154        static class PrototypeSerializationFactory implements Factory, Serializable {
155            
156            /** The serial version */
157            private static final long serialVersionUID = -8704966966139178833L;
158            
159            /** The object to clone via serialization each time */
160            private final Serializable iPrototype;
161    
162            /**
163             * Constructor to store prototype
164             */
165            private PrototypeSerializationFactory(Serializable prototype) {
166                super();
167                iPrototype = prototype;
168            }
169    
170            /**
171             * Creates an object using serialization.
172             * 
173             * @return the new object
174             */
175            public Object create() {
176                ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
177                ByteArrayInputStream bais = null;
178                try {
179                    ObjectOutputStream out = new ObjectOutputStream(baos);
180                    out.writeObject(iPrototype);
181    
182                    bais = new ByteArrayInputStream(baos.toByteArray());
183                    ObjectInputStream in = new ObjectInputStream(bais);
184                    return in.readObject();
185    
186                } catch (ClassNotFoundException ex) {
187                    throw new FunctorException(ex);
188                } catch (IOException ex) {
189                    throw new FunctorException(ex);
190                } finally {
191                    try {
192                        if (bais != null) {
193                            bais.close();
194                        }
195                    } catch (IOException ex) {
196                        // ignore
197                    }
198                    try {
199                        if (baos != null) {
200                            baos.close();
201                        }
202                    } catch (IOException ex) {
203                        // ignore
204                    }
205                }
206            }
207        }
208    
209    }