jodd.util.ClassLoaderStrategy Maven / Gradle / Ivy
// 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;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* Class loader strategy defines how classes should be loaded.
*/
@FunctionalInterface
public interface ClassLoaderStrategy {
/**
* Loads class with given name and optionally provided class loader.
*/
Class loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException;
/**
* Default Jodd class loader strategy.
* Loads a class with a given name dynamically, more reliable then Class.forName
.
*
* Class will be loaded using class loaders in the following order:
*
* - provided class loader (if any)
* Thread.currentThread().getContextClassLoader()}
* - caller classloader
*
*/
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 false
(default), classes will be loaded by Class.forName
.
* If true
, classes will be loaded by reflection and component types.
*/
public void setLoadArrayClassByComponentTypes(final boolean loadArrayClassByComponentTypes) {
this.loadArrayClassByComponentTypes = loadArrayClassByComponentTypes;
}
// ---------------------------------------------------------------- names
/**
* Prepares classname for loading, respecting the arrays.
* Returns null
if class name is not an array.
*/
public static String prepareArrayClassnameForLoading(String className) {
final int bracketCount = StringUtil.count(className, '[');
if (bracketCount == 0) {
// not an array
return null;
}
final String brackets = StringUtil.repeat('[', bracketCount);
final int bracketIndex = className.indexOf('[');
className = className.substring(0, bracketIndex);
final 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(final String className) {
final int dotIndex = className.indexOf('.');
if (dotIndex != -1) {
return -1;
}
return Arrays.binarySearch(PRIMITIVE_TYPE_NAMES, className);
}
// ---------------------------------------------------------------- load
/**
* Loads class by name.
*/
@Override
public Class loadClass(final String className, final ClassLoader classLoader) throws ClassNotFoundException {
final String arrayClassName = prepareArrayClassnameForLoading(className);
if ((className.indexOf('.') == -1) && (arrayClassName == null)) {
// maybe a primitive
final int primitiveNdx = getPrimitiveClassNameIndex(className);
if (primitiveNdx >= 0) {
return PRIMITIVE_TYPES[primitiveNdx];
}
}
// try #1 - using provided class loader
if (classLoader != null) {
final Class klass = loadClass(className, arrayClassName, classLoader);
if (klass != null) {
return klass;
}
}
// try #2 - using thread class loader
final ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
if ((currentThreadClassLoader != null) && (currentThreadClassLoader != classLoader)) {
final 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);
final Class callerClass = ClassUtil.getCallerClass();
final ClassLoader callerClassLoader = callerClass.getClassLoader();
if ((callerClassLoader != classLoader) && (callerClassLoader != currentThreadClassLoader)) {
final 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 (final 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 Class.forName
!
* We must use this since for JDK {@literal >=} 6 arrays will be not loaded using classloader,
* but only with forName
method. However, array loading strategy can be
* {@link #setLoadArrayClassByComponentTypes(boolean) changed}.
*/
protected Class loadClass(final String className, final String arrayClassName, final ClassLoader classLoader) {
if (arrayClassName != null) {
try {
if (loadArrayClassByComponentTypes) {
return loadArrayClassByComponentType(className, classLoader);
} else {
return Class.forName(arrayClassName, true, classLoader);
}
} catch (final ClassNotFoundException ignore) {
}
}
try {
return classLoader.loadClass(className);
} catch (final ClassNotFoundException ignore) {
}
return null;
}
/**
* Loads array class using component type.
*/
protected Class loadArrayClassByComponentType(final String className, final ClassLoader classLoader) throws ClassNotFoundException {
final int ndx = className.indexOf('[');
final int multi = StringUtil.count(className, '[');
final String componentTypeName = className.substring(0, ndx);
final Class componentType = loadClass(componentTypeName, classLoader);
if (multi == 1) {
return Array.newInstance(componentType, 0).getClass();
}
final 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();
}
}
}