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 */
017package org.apache.commons.collections4.functors;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.ObjectInputStream;
023import java.io.ObjectOutputStream;
024import java.io.Serializable;
025import java.lang.reflect.InvocationTargetException;
026import java.lang.reflect.Method;
027
028import org.apache.commons.collections4.Factory;
029import org.apache.commons.collections4.FunctorException;
030
031/**
032 * Factory implementation that creates a new instance each time based on a prototype.
033 *
034 * @since 3.0
035 * @version $Id: PrototypeFactory.html 972421 2015-11-14 20:00:04Z tn $
036 */
037public class PrototypeFactory {
038
039    /**
040     * Factory method that performs validation.
041     * <p>
042     * Creates a Factory that will return a clone of the same prototype object
043     * each time the factory is used. The prototype will be cloned using one of these
044     * techniques (in order):
045     * <ul>
046     * <li>public clone method
047     * <li>public copy constructor
048     * <li>serialization clone
049     * <ul>
050     *
051     * @param <T>  the type the factory creates
052     * @param prototype  the object to clone each time in the factory
053     * @return the <code>prototype</code> factory, or a {@link ConstantFactory#NULL_INSTANCE} if
054     * the {@code prototype} is {@code null}
055     * @throws IllegalArgumentException if the prototype cannot be cloned
056     */
057    @SuppressWarnings("unchecked")
058    public static <T> Factory<T> prototypeFactory(final T prototype) {
059        if (prototype == null) {
060            return ConstantFactory.<T>constantFactory(null);
061        }
062        try {
063            final Method method = prototype.getClass().getMethod("clone", (Class[]) null);
064            return new PrototypeCloneFactory<T>(prototype, method);
065
066        } catch (final NoSuchMethodException ex) {
067            try {
068                prototype.getClass().getConstructor(new Class<?>[] { prototype.getClass() });
069                return new InstantiateFactory<T>(
070                    (Class<T>) prototype.getClass(),
071                    new Class<?>[] { prototype.getClass() },
072                    new Object[] { prototype });
073            } catch (final NoSuchMethodException ex2) {
074                if (prototype instanceof Serializable) {
075                    return (Factory<T>) new PrototypeSerializationFactory<Serializable>((Serializable) prototype);
076                }
077            }
078        }
079        throw new IllegalArgumentException("The prototype must be cloneable via a public clone method");
080    }
081
082    /**
083     * Restricted constructor.
084     */
085    private PrototypeFactory() {
086        super();
087    }
088
089    // PrototypeCloneFactory
090    //-----------------------------------------------------------------------
091    /**
092     * PrototypeCloneFactory creates objects by copying a prototype using the clone method.
093     */
094    static class PrototypeCloneFactory<T> implements Factory<T>, Serializable {
095
096        /** The serial version */
097        private static final long serialVersionUID = 5604271422565175555L;
098
099        /** The object to clone each time */
100        private final T iPrototype;
101        /** The method used to clone */
102        private transient Method iCloneMethod;
103
104        /**
105         * Constructor to store prototype.
106         */
107        private PrototypeCloneFactory(final T prototype, final Method method) {
108            super();
109            iPrototype = prototype;
110            iCloneMethod = method;
111        }
112
113        /**
114         * Find the Clone method for the class specified.
115         */
116        private void findCloneMethod() {
117            try {
118                iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null);
119            } catch (final NoSuchMethodException ex) {
120                throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public ");
121            }
122        }
123
124        /**
125         * Creates an object by calling the clone method.
126         *
127         * @return the new object
128         */
129        @SuppressWarnings("unchecked")
130        public T create() {
131            // needed for post-serialization
132            if (iCloneMethod == null) {
133                findCloneMethod();
134            }
135
136            try {
137                return (T) iCloneMethod.invoke(iPrototype, (Object[]) null);
138            } catch (final IllegalAccessException ex) {
139                throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex);
140            } catch (final InvocationTargetException ex) {
141                throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex);
142            }
143        }
144    }
145
146    // PrototypeSerializationFactory
147    //-----------------------------------------------------------------------
148    /**
149     * PrototypeSerializationFactory creates objects by cloning a prototype using serialization.
150     */
151    static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T>, Serializable {
152
153        /** The serial version */
154        private static final long serialVersionUID = -8704966966139178833L;
155
156        /** The object to clone via serialization each time */
157        private final T iPrototype;
158
159        /**
160         * Constructor to store prototype
161         */
162        private PrototypeSerializationFactory(final T prototype) {
163            super();
164            iPrototype = prototype;
165        }
166
167        /**
168         * Creates an object using serialization.
169         *
170         * @return the new object
171         */
172        @SuppressWarnings("unchecked")
173        public T create() {
174            final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
175            ByteArrayInputStream bais = null;
176            try {
177                final ObjectOutputStream out = new ObjectOutputStream(baos);
178                out.writeObject(iPrototype);
179
180                bais = new ByteArrayInputStream(baos.toByteArray());
181                final ObjectInputStream in = new ObjectInputStream(bais);
182                return (T) in.readObject();
183
184            } catch (final ClassNotFoundException ex) {
185                throw new FunctorException(ex);
186            } catch (final IOException ex) {
187                throw new FunctorException(ex);
188            } finally {
189                try {
190                    if (bais != null) {
191                        bais.close();
192                    }
193                } catch (final IOException ex) {
194                    // ignore
195                }
196                try {
197                    baos.close();
198                } catch (final IOException ex) {
199                    // ignore
200                }
201            }
202        }
203    }
204
205}