// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.util.cl; import jodd.util.ClassUtil; import jodd.util.StringUtil; import java.lang.reflect.Array; import java.util.Arrays; /** * Default Jodd class loader strategy. * Loads a class with a given name dynamically, more reliable then <code>Class.forName</code>. * <p> * Class will be loaded using class loaders in the following order: * <ul> * <li>provided class loader (if any)</li> * <li><code>Thread.currentThread().getContextClassLoader()}</code></li> * <li>caller classloader</li> * </ul> */ public class DefaultClassLoaderStrategy implements ClassLoaderStrategy { /** * List of primitive type names. */ public static final String[] PRIMITIVE_TYPE_NAMES = new String[] { "boolean", "byte", "char", "double", "float", "int", "long", "short", }; /** * List of primitive types that matches names list. */ public static final Class[] PRIMITIVE_TYPES = new Class[] { boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, short.class, }; /** * List of primitive bytecode characters that matches names list. */ public static final char[] PRIMITIVE_BYTECODE_NAME = new char[] { 'Z', 'B', 'C', 'D', 'F', 'I', 'J', 'S' }; // ---------------------------------------------------------------- flags protected boolean loadArrayClassByComponentTypes = false; /** * Returns arrays class loading strategy. */ public boolean isLoadArrayClassByComponentTypes() { return loadArrayClassByComponentTypes; } /** * Defines arrays class loading strategy. * If <code>false</code> (default), classes will be loaded by <code>Class.forName</code>. * If <code>true</code>, classes will be loaded by reflection and component types. */ public void setLoadArrayClassByComponentTypes(boolean loadArrayClassByComponentTypes) { this.loadArrayClassByComponentTypes = loadArrayClassByComponentTypes; } // ---------------------------------------------------------------- names /** * Prepares classname for loading, respecting the arrays. * Returns <code>null</code> if class name is not an array. */ public static String prepareArrayClassnameForLoading(String className) { int bracketCount = StringUtil.count(className, '['); if (bracketCount == 0) { // not an array return null; } String brackets = StringUtil.repeat('[', bracketCount); int bracketIndex = className.indexOf('['); className = className.substring(0, bracketIndex); int primitiveNdx = getPrimitiveClassNameIndex(className); if (primitiveNdx >= 0) { className = String.valueOf(PRIMITIVE_BYTECODE_NAME[primitiveNdx]); return brackets + className; } else { return brackets + 'L' + className + ';'; } } /** * Detects if provided class name is a primitive type. * Returns >= 0 number if so. */ private static int getPrimitiveClassNameIndex(String className) { int dotIndex = className.indexOf('.'); if (dotIndex != -1) { return -1; } return Arrays.binarySearch(PRIMITIVE_TYPE_NAMES, className); } // ---------------------------------------------------------------- load /** * Loads class by name. */ public Class loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException { String arrayClassName = prepareArrayClassnameForLoading(className); if ((className.indexOf('.') == -1) && (arrayClassName == null)) { // maybe a primitive int primitiveNdx = getPrimitiveClassNameIndex(className); if (primitiveNdx >= 0) { return PRIMITIVE_TYPES[primitiveNdx]; } } // try #1 - using provided class loader if (classLoader != null) { Class klass = loadClass(className, arrayClassName, classLoader); if (klass != null) { return klass; } } // try #2 - using thread class loader ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader(); if ((currentThreadClassLoader != null) && (currentThreadClassLoader != classLoader)) { Class klass = loadClass(className, arrayClassName, currentThreadClassLoader); if (klass != null) { return klass; } } // try #3 - using caller classloader, similar as Class.forName() //Class callerClass = ReflectUtil.getCallerClass(2); Class callerClass = ClassUtil.getCallerClass(); ClassLoader callerClassLoader = callerClass.getClassLoader(); if ((callerClassLoader != classLoader) && (callerClassLoader != currentThreadClassLoader)) { Class klass = loadClass(className, arrayClassName, callerClassLoader); if (klass != null) { return klass; } } // try #4 - everything failed, try alternative array loader if (arrayClassName != null) { try { return loadArrayClassByComponentType(className, classLoader); } catch (ClassNotFoundException ignore) { } } throw new ClassNotFoundException("Class not found: " + className); } /** * Loads a class using provided class loader. * If class is an array, it will be first loaded using the <code>Class.forName</code>! * We must use this since for JDK {@literal >=} 6 arrays will be not loaded using classloader, * but only with <code>forName</code> method. However, array loading strategy can be * {@link #setLoadArrayClassByComponentTypes(boolean) changed}. */ protected Class loadClass(String className, String arrayClassName, ClassLoader classLoader) { if (arrayClassName != null) { try { if (loadArrayClassByComponentTypes) { return loadArrayClassByComponentType(className, classLoader); } else { return Class.forName(arrayClassName, true, classLoader); } } catch (ClassNotFoundException ignore) { } } try { return classLoader.loadClass(className); } catch (ClassNotFoundException ignore) { } return null; } /** * Loads array class using component type. */ protected Class loadArrayClassByComponentType(String className, ClassLoader classLoader) throws ClassNotFoundException { int ndx = className.indexOf('['); int multi = StringUtil.count(className, '['); String componentTypeName = className.substring(0, ndx); Class componentType = loadClass(componentTypeName, classLoader); if (multi == 1) { return Array.newInstance(componentType, 0).getClass(); } int[] multiSizes; if (multi == 2) { multiSizes = new int[] {0, 0}; } else if (multi == 3) { multiSizes = new int[] {0, 0, 0}; } else { multiSizes = (int[]) Array.newInstance(int.class, multi); } return Array.newInstance(componentType, multiSizes).getClass(); } }