001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.common.utils;
025
026import java.lang.reflect.Constructor;
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.Method;
029
030import dk.netarkivet.common.exceptions.ArgumentNotValid;
031import dk.netarkivet.common.exceptions.IOFailure;
032import dk.netarkivet.common.exceptions.PermissionDenied;
033
034/**
035 * Generic class for creating class instances from class names given in settings.
036 *
037 * @param <T> the object-type returned by this class.
038 */
039public class SettingsFactory<T> {
040
041    /**
042     * Creates a new class of the class given in the settings field.
043     * <p>
044     * If the loaded class has a getInstance() method that matches the given arguments, that will be called to create
045     * the class, otherwise a matching constructor will be called, if it exists. This sequence allows for creating
046     * singletons.
047     * <p>
048     * Due to limitations of the Java Reflection API, the parameters of the getInstance method declared on the loaded
049     * class must match the given arguments exactly, without subclassing, interface implementation or unboxing. In
050     * particular, since any primitive types are automatically boxed when passed to this method, getInstance() methods
051     * with primitive type formal parameters will not be found.
052     *
053     * @param settingsField A field in the Settings class.
054     * @param args The arguments that will be passed to the getInstance method or the constructor. These will also be
055     * used to determine which getInstance method or constructor to find.
056     * @param <T> the object-type returned by this method.
057     * @return A new instance of type T created by calling getInstance() or by invoking a constructor.
058     * @throws ArgumentNotValid if settingsField is null or the invoked method or constructor threw an exception.
059     * @throws IOFailure if there are unrecoverable errors reflecting upon the class.
060     * @throws PermissionDenied if the class or methods cannot be accessed.
061     */
062    @SuppressWarnings({"unchecked", "rawtypes"})
063    public static <T> T getInstance(String settingsField, Object... args) {
064        ArgumentNotValid.checkNotNull(settingsField, "String settingsField");
065        String className = Settings.get(settingsField);
066        try {
067            Class<T> aClass = (Class<T>) Class.forName(className);
068            Class[] classArgs = new Class[args.length];
069            int i = 0;
070            for (Object o : args) {
071                classArgs[i] = o.getClass();
072                ++i;
073            }
074            Method m = null;
075            try {
076                m = aClass.getMethod("getInstance", classArgs);
077            } catch (NoSuchMethodException e) {
078                // The exception is ignored, as we have an alternative
079                // approach in searching for constructors.
080                Constructor<T> c = null;
081                try {
082                    c = aClass.getConstructor(classArgs);
083                } catch (NoSuchMethodException e1) {
084                    throw new ArgumentNotValid("No suitable getInstance() or" + " constructor for class '" + className
085                            + "'", e1);
086                }
087                try {
088                    return c.newInstance(args);
089                } catch (InvocationTargetException e1) {
090                    throw new ArgumentNotValid("Error creating singleton " + "of class '" + className + "': ",
091                            e1.getCause());
092                }
093            }
094            try {
095                return (T) m.invoke(null, args);
096            } catch (InvocationTargetException e) {
097                throw new ArgumentNotValid("Error creating singleton of class '" + className + "': ", e.getCause());
098            }
099        } catch (IllegalAccessException e) {
100            throw new PermissionDenied("Cannot access class '" + className + "' defined by '" + settingsField + "'", e);
101        } catch (ClassNotFoundException e) {
102            throw new IOFailure("Error finding class '" + className + "' defined by '" + settingsField + "'", e);
103        } catch (InstantiationException e) {
104            throw new IOFailure("Error while instantiating class '" + className + "' defined by '" + settingsField
105                    + "'", e);
106        } catch (ClassCastException e) {
107            throw new IOFailure("Set class '" + className + "' is of wrong type" + " defined by '" + settingsField
108                    + "'", e);
109        }
110    }
111
112}