/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.util; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; /** * Utility class for returning an instance of an interface. * * @author bryan */ public class ClassPathUtil { private static final Logger log = Logger.getLogger(ClassPathUtil.class); /** * True iff the {@link #log} level is DEBUG or less. */ final static private boolean DEBUG = log.isDebugEnabled(); /** * BLZG-1703: we cash resolved classes in a map. We use a synchronized * map rather than a ConcurrentHashMap since the latter does not support * null values (which we use to indicate that resolving failed, e.g. for * the optional GPU add-on optimizers). */ final static private Map<ClassPathUtilRequestConfig, Class<?>> cache = Collections.synchronizedMap(new HashMap<ClassPathUtilRequestConfig, Class<?>>()); public static <T> T classForName(final String preferredClassName, final Class<T> defaultClass, final Class<T> sharedInterface) { return classForName(preferredClassName, defaultClass, sharedInterface, ClassPathUtil.class.getClassLoader()); } /** * Return an instance of the shared interface. If possible, an instance of * the preferred class will be used. If that class is not found or is does * not extend the specified interface, then an instance of the default class * will be used. * * @param preferredClassName * The name of the preferred class to use. The class may need to * have a zero argument public constructor in order to be * instantiated by this this method. * @param defaultClass * The default class to use (optional). When non- * <code>null</code>, the default class must implement the shared * interface. * @param sharedClassOrInterface * A class or interface that the preferred class must implement * if it is to be instantiated. * @param classLoader * The class loader to use. * * @return An instance of the preferred class if it can be found, implements * the shared interface, and the security checks permit its * instantiation -or- an instance of the default class (if given) * and otherwise <code>null</code>. * * @throws IllegalArgumentException * if the preferred class name is <code>null</code>. * @throws IllegalArgumentException * if defaultClass is given and does not implement the shared * interface. * @throws RuntimeException * if an attempt to instantiate the default class results in a * {@link SecurityException} or {@link IllegalAccessException}. */ @SuppressWarnings("unchecked") public static <T> T classForName(final String preferredClassName, final Class<? extends T> defaultClass, final Class<T> sharedClassOrInterface, final ClassLoader classLoader) { // throws an IllegalArgumentException if preferredClassName, sharedClassOrInterface, // or classLoader are null final ClassPathUtilRequestConfig requestConfig = new ClassPathUtilRequestConfig( preferredClassName, defaultClass, sharedClassOrInterface, classLoader); try { // first try lookup in cache and take early exit if present if (cache.containsKey(requestConfig)) { final Class<?> cls = cache.get(requestConfig); return cls == null ? null : (T) cls.newInstance(); } if (defaultClass != null && !sharedClassOrInterface.isAssignableFrom(defaultClass)) { // The default class must extend the shared interface. throw new IllegalArgumentException(); } // Do not initialize the class when it is loaded. final boolean initialize = false; // Use the caller's class loader to find the preferred class. final Class<?> cls = Class.forName(preferredClassName, initialize, classLoader); if (sharedClassOrInterface.isAssignableFrom(cls)) { // Found preferred class. Is instance of shared interface. if (log.isInfoEnabled()) { log.info("Found " + cls.getCanonicalName()); } // remember for next lookup in cache cache.put(requestConfig, cls); // Return instance of preferred class. return (T) cls.newInstance(); } // Class is not instance of shared interface. Can not use. Will return default class instance. log.warn(cls.getCanonicalName() + " does not extend " + sharedClassOrInterface.getCanonicalName()); // fall through } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { // Could not find preferred class. Will use default instance. Do NOT log @ WARN. if (DEBUG) { log.debug("Not found: " + preferredClassName); } // fall through } /* * Return an instance of the default class (if given). */ if (defaultClass == null) { // remember for next lookup in cache cache.put(requestConfig, null); // If there is no default class, return null. return null; } try { if (DEBUG) { log.debug("Using defaultClass: " + defaultClass.getCanonicalName()); } // remember for next lookup in cache cache.put(requestConfig, defaultClass); // Return an instance of the default class. return (T) defaultClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * Configuration representing a request for a given class based * on preferred name, default class, shared class or instance, and the * class loader to be used. * * @author <a href="mailto:ms@metaphacts.com">Michael Schmidt</a> */ private static class ClassPathUtilRequestConfig { final protected String preferredClassName; final protected Class<?> defaultClass; final protected Class<?> sharedClassOrInterface; final protected ClassLoader classLoader; /** * Initialize the config. preferredClassName, sharedClassOrInterface, and * the classLoader must be non null, otherwise and {@link IllegalArgumentException} * is thrown. * * @param preferredClassName * @param defaultClass * @param sharedClassOrInterface * @param classLoader */ public ClassPathUtilRequestConfig( final String preferredClassName, final Class<?> defaultClass, final Class<?> sharedClassOrInterface, final ClassLoader classLoader) throws IllegalArgumentException { if (preferredClassName == null) throw new IllegalArgumentException(); if (sharedClassOrInterface == null) throw new IllegalArgumentException(); if (classLoader == null) throw new IllegalArgumentException(); this.preferredClassName = preferredClassName; this.defaultClass = defaultClass; this.sharedClassOrInterface = sharedClassOrInterface; this.classLoader = classLoader; } @Override public int hashCode() { int hashCode = 1; hashCode = 37 * hashCode + preferredClassName.hashCode(); hashCode = 37 * hashCode + sharedClassOrInterface.hashCode(); hashCode = 37 * hashCode + classLoader.hashCode(); if (defaultClass!=null) { hashCode = 37 * hashCode + defaultClass.hashCode(); } return hashCode; } @Override public boolean equals(Object other) { if (other==null || !(other instanceof ClassPathUtilRequestConfig)) { return false; } final ClassPathUtilRequestConfig otherAsConfig = (ClassPathUtilRequestConfig)other; boolean equals = true; // preferredClassName non null by construction equals &= preferredClassName.equals(otherAsConfig.preferredClassName); // sharedClassOrInterface non null by construction equals &= sharedClassOrInterface.equals(otherAsConfig.sharedClassOrInterface); // classLoader non null by construction equals &= classLoader.equals(otherAsConfig.classLoader); // default class may be null equals &= defaultClass==null ? otherAsConfig.defaultClass==null : defaultClass.equals(otherAsConfig.defaultClass); return equals; } } }