Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions core/src/main/java/lucee/commons/io/res/util/ResourceUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,20 @@ public final class ResourceUtil {

public static Resource toResourceExisting(PageContext pc, String path, Resource defaultValue) {
try {
return toResourceExisting(pc, path);
if (pc == null) {
pc = ThreadLocalPageContext.get();
if (pc == null) {
Config c = ThreadLocalPageContext.getConfig();
if (c != null) return toResourceExisting(c, path, defaultValue);
Resource res = ResourcesImpl.getFileResourceProvider().getResource(path);
return res.exists() ? res : defaultValue;
}
}
Resource res = _findExisting(pc, path, pc.getConfig().allowRealPath());
return res != null ? res : defaultValue;
}
catch (Throwable e) {
ExceptionUtil.rethrowIfNecessary(e);
catch (Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return defaultValue;
}
}
Expand All @@ -209,7 +219,7 @@ public static Resource toResourceExisting(PageContext pc, String path, Resource
* cast a String (argument destination) to a File Object, if destination is not an absolute, file
* object will be relative to current position (get from PageContext) file must exist otherwise
* throw exception
*
*
* @param pc Page Context to the current position in filesystem
* @param path relative or absolute path for file object
* @return file object from destination
Expand All @@ -229,20 +239,27 @@ public static Resource toResourceExisting(PageContext pc, String path) throws Ex

public static Resource toResourceExisting(PageContext pc, String path, boolean allowRealpath, Resource defaultValue) {
try {
return toResourceExisting(pc, path, allowRealpath);
Resource res = _findExisting(pc, path, allowRealpath);
return res != null ? res : defaultValue;
}
catch (Throwable e) {
ExceptionUtil.rethrowIfNecessary(e);
catch (Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return defaultValue;
}
}

public static Resource toResourceExisting(PageContext pc, String path, boolean allowRealpath) throws ExpressionException {
Resource res = _findExisting(pc, path, allowRealpath);
if (res != null) return res;
throw new ExpressionException("file or directory [" + StringUtil.max(path.replace('\\', '/'), 255, "...") + "] does not exist");
}

private static Resource _findExisting(PageContext pc, String path, boolean allowRealpath) {
path = path.replace('\\', '/');
Resource res = pc.getConfig().getResource(path);

if (res.exists()) return res;
else if (!allowRealpath) throw new ExpressionException("file or directory [" + StringUtil.max(path, 255, "...") + "] does not exist");
if (!allowRealpath) return null;

if (StringUtil.startsWith(path, '/')) {
PageContextImpl pci = (PageContextImpl) pc;
Expand All @@ -258,8 +275,7 @@ public static Resource toResourceExisting(PageContext pc, String path, boolean a
}
}
res = getRealResource(pc, path, res);
if (res.exists()) return res;
throw new ExpressionException("file or directory [" + StringUtil.max(path, 255, "...") + "] does not exist");
return res.exists() ? res : null;
}

public static Resource toResourceExisting(Config config, String path) throws ExpressionException {
Expand Down
140 changes: 91 additions & 49 deletions core/src/main/java/lucee/commons/lang/ClassUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import lucee.runtime.type.Array;
import lucee.runtime.type.util.ListUtil;
import lucee.transformer.dynamic.DynamicInvoker;
import lucee.transformer.dynamic.meta.Constructor;
import lucee.transformer.dynamic.meta.Method;

public final class ClassUtil {
Expand Down Expand Up @@ -98,55 +99,41 @@ private static Class checkPrimaryTypes(String className, Class defaultValue) {
Class res = checkPrimaryTypesBytecodeDef(className, null);
if (res != null) return res;

String lcClassName = className.toLowerCase();
// length-based fast exits to avoid String allocation on the common path
int len = className.length();
if (len < 3 || len > 19) return defaultValue; // shortest "int" = 3, longest "java.lang.character" = 19

String target;
boolean isRef = false;
if (lcClassName.startsWith("java.lang.")) {
lcClassName = lcClassName.substring(10);
if (len > 9) {
// only "java.lang.X" prefixed form possible
if (len < 14) return defaultValue; // "java.lang." + min 4 ("void") = 14
if (!className.regionMatches(true, 0, "java.lang.", 0, 10)) return defaultValue;
target = className.substring(10);
isRef = true;
if (target.length() > 9) return defaultValue;
}

if (lcClassName.length() > 9) return defaultValue; // short circuit longest below match is "character"

if (lcClassName.equals("void")) {
return void.class;
}
if (lcClassName.equals("boolean")) {
if (isRef) return Boolean.class;
return boolean.class;
}
if (lcClassName.equals("byte")) {
if (isRef) return Byte.class;
return byte.class;
}
if (lcClassName.equals("int")) {
return int.class;
}
if (lcClassName.equals("long")) {
if (isRef) return Long.class;
return long.class;
}
if (lcClassName.equals("float")) {
if (isRef) return Float.class;
return float.class;
}
if (lcClassName.equals("double")) {
if (isRef) return Double.class;
return double.class;
}
if (lcClassName.equals("char")) {
return char.class;
}
if (lcClassName.equals("short")) {
if (isRef) return Short.class;
return short.class;
}

if (lcClassName.equals("integer")) return Integer.class;
if (lcClassName.equals("character")) return Character.class;
if (lcClassName.equals("object")) return Object.class;
if (lcClassName.equals("string")) return String.class;
if (lcClassName.equals("null")) return Object.class;
if (lcClassName.equals("numeric")) return Double.class;
else {
target = className;
}

// equalsIgnoreCase doesn't allocate
if (target.equalsIgnoreCase("void")) return void.class;
if (target.equalsIgnoreCase("boolean")) return isRef ? Boolean.class : boolean.class;
if (target.equalsIgnoreCase("byte")) return isRef ? Byte.class : byte.class;
if (target.equalsIgnoreCase("int")) return int.class;
if (target.equalsIgnoreCase("long")) return isRef ? Long.class : long.class;
if (target.equalsIgnoreCase("float")) return isRef ? Float.class : float.class;
if (target.equalsIgnoreCase("double")) return isRef ? Double.class : double.class;
if (target.equalsIgnoreCase("char")) return char.class;
if (target.equalsIgnoreCase("short")) return isRef ? Short.class : short.class;

if (target.equalsIgnoreCase("integer")) return Integer.class;
if (target.equalsIgnoreCase("character")) return Character.class;
if (target.equalsIgnoreCase("object")) return Object.class;
if (target.equalsIgnoreCase("string")) return String.class;
if (target.equalsIgnoreCase("null")) return Object.class;
if (target.equalsIgnoreCase("numeric")) return Double.class;

return defaultValue;
}
Expand Down Expand Up @@ -392,11 +379,14 @@ private static Class loadClass(ClassLoader cl, String className, Class defaultVa
*/
public static Class loadClass(ClassLoader cl, String className) throws ClassException {

Set<Throwable> exceptions = new HashSet<Throwable>();
Class clazz = loadClass(cl, className, null, exceptions);

// fast path: skip the HashSet allocation when the class is found
Class clazz = loadClass(cl, className, null, null);
if (clazz != null) return clazz;

// slow path: capture exceptions for diagnostic
Set<Throwable> exceptions = new HashSet<Throwable>();
loadClass(cl, className, null, exceptions);

String msg = "cannot load class through its string name, because no definition for the class with the specified name [" + className + "] could be found";

// single exception
Expand Down Expand Up @@ -508,8 +498,30 @@ private static Class<?> __loadClass(ClassLoading cl, String className, Class<?>
* @return matching Class
* @throws ClassException
*/
private static final ClassValue<Constructor> NO_ARG_CONSTRUCTOR = new ClassValue<Constructor>() {
@Override
protected Constructor computeValue(Class<?> clazz) {
try {
DynamicInvoker di = DynamicInvoker.getExistingInstance();
return di.toClazz(clazz).getConstructor(EMPTY_OBJ, true, false);
}
catch (Throwable t) {
return null;
}
}
};

private static final ClassValue<ConcurrentHashMap<Class, Constructor>> ONE_ARG_CONSTRUCTOR_CACHE = new ClassValue<ConcurrentHashMap<Class, Constructor>>() {
@Override
protected ConcurrentHashMap<Class, Constructor> computeValue(Class<?> clazz) {
return new ConcurrentHashMap<>();
}
};

public static Object loadInstance(Class clazz) throws ClassException {
try {
Constructor cached = NO_ARG_CONSTRUCTOR.get(clazz);
if (cached != null) return cached.newInstance(EMPTY_OBJ);
return Reflector.getConstructorInstance(clazz, EMPTY_OBJ, false).invoke();
}
catch (InstantiationException e) {
Expand Down Expand Up @@ -587,6 +599,36 @@ public static Object loadInstance(Class clazz, Object[] args) throws ClassExcept
if (args == null || args.length == 0) return loadInstance(clazz);

try {
// 1-arg fast path: cache resolved Constructor by (clazz, arg0.getClass()), convert args explicitly per call
if (args.length == 1 && args[0] != null) {
Class argClass = args[0].getClass();
ConcurrentHashMap<Class, Constructor> classCache = ONE_ARG_CONSTRUCTOR_CACHE.get(clazz);
Constructor cached = classCache.get(argClass);
if (cached == null) {
try {
DynamicInvoker di = DynamicInvoker.getExistingInstance();
// Lookup mutates args[], use a copy so caller's args is preserved for the slow-path fallback
Object[] probe = new Object[] { args[0] };
cached = di.toClazz(clazz).getConstructor(probe, true, true);
if (cached != null) classCache.put(argClass, cached);
}
catch (Throwable t) {
// fall through to slow path
}
}
if (cached != null) {
try {
Class[] paramTypes = cached.getArgumentClasses();
Object converted = Reflector.convertSafe(args[0], Reflector.toReferenceClass(paramTypes[0]), null);
if (converted != Reflector.UNCONVERTIBLE) {
return cached.newInstance(new Object[] { converted });
}
}
catch (lucee.runtime.exp.PageException pe) {
// fall through to slow path
}
}
}
return Reflector.getConstructorInstance(clazz, args, false).invoke();
}
catch (SecurityException e) {
Expand Down
18 changes: 16 additions & 2 deletions core/src/main/java/lucee/runtime/config/ConfigImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ public abstract class ConfigImpl extends ConfigBase implements ConfigPro {
private static final long CACHE_DIR_SIZE_DEFAULT = 1024L * 1024L * 100L;

private final Map<String, PhysicalClassLoader> rpcClassLoaders = new ConcurrentHashMap<String, PhysicalClassLoader>();
private volatile ClassLoader defaultRpcClassLoader;
private PhysicalClassLoader directClassLoader;
private Map<String, DataSource> datasourcesAll;
private Map<String, DataSource> datasourcesNoQoQ;
Expand Down Expand Up @@ -3262,12 +3263,23 @@ protected void setSessionScopeDir(Resource sessionScopeDir) {

@Override
public ClassLoader getRPCClassLoader(boolean reload) throws IOException {
return PhysicalClassLoaderFactory.getRPCClassLoader(this, getJavaSettings(), reload);
ClassLoader cached = defaultRpcClassLoader;
if (!reload && cached != null) return cached;
ClassLoader cl = PhysicalClassLoaderFactory.getRPCClassLoader(this, getJavaSettings(), reload);
if (!reload) defaultRpcClassLoader = cl;
return cl;
}

@Override
public ClassLoader getRPCClassLoader(boolean reload, JavaSettings js) throws IOException {
return PhysicalClassLoaderFactory.getRPCClassLoader(this, js != null ? js : getJavaSettings(), reload);
if (js == null || js == getJavaSettings()) {
ClassLoader cached = defaultRpcClassLoader;
if (!reload && cached != null) return cached;
ClassLoader cl = PhysicalClassLoaderFactory.getRPCClassLoader(this, getJavaSettings(), reload);
if (!reload) defaultRpcClassLoader = cl;
return cl;
}
return PhysicalClassLoaderFactory.getRPCClassLoader(this, js, reload);
}

private static final Object dclt = new SerializableObject();
Expand All @@ -3291,6 +3303,7 @@ public PhysicalClassLoader getDirectClassLoader(boolean reload) throws IOExcepti

public void clearRPCClassLoader() {
rpcClassLoaders.clear();
defaultRpcClassLoader = null;
}

@Override
Expand Down Expand Up @@ -6316,6 +6329,7 @@ public ConfigImpl resetJavaSettings() {
synchronized (javaSettingsInstances) {
if (javaSettings != null) {
javaSettings = null;
defaultRpcClassLoader = null;
}
}
}
Expand Down
65 changes: 65 additions & 0 deletions core/src/main/java/lucee/runtime/functions/other/JavaProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,39 @@ public final class JavaProxy implements Function {

private static final long serialVersionUID = 2696152022196556309L;

private static final java.util.concurrent.ConcurrentHashMap<ClassLoader, java.util.concurrent.ConcurrentHashMap<String, Class<?>>> CLASS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();

public static Class<?> tryCachedLoad(PageContext pc, String className) {
try {
ClassLoader cl = ((PageContextImpl) pc).getRPCClassLoader(null);
java.util.concurrent.ConcurrentHashMap<String, Class<?>> classCache = CLASS_CACHE.get(cl);
if (classCache == null) return null;
return classCache.get(className);
}
catch (Exception e) {
return null;
}
}

public static void cacheClassLookup(PageContext pc, String className, Class<?> cls) {
if (cls == null) return;
try {
ClassLoader cl = ((PageContextImpl) pc).getRPCClassLoader(null);
getClassCacheFor(cl).put(className, cls);
}
catch (Exception e) {
// ignore — cache miss is recoverable
}
}

private static java.util.concurrent.ConcurrentHashMap<String, Class<?>> getClassCacheFor(ClassLoader cl) {
java.util.concurrent.ConcurrentHashMap<String, Class<?>> classCache = CLASS_CACHE.get(cl);
if (classCache == null) {
classCache = CLASS_CACHE.computeIfAbsent(cl, k -> new java.util.concurrent.ConcurrentHashMap<>());
}
return classCache;
}

public static Object call(PageContext pc, String className) throws PageException {
return call(pc, className, null, null, null);
}
Expand Down Expand Up @@ -141,6 +174,38 @@ else if (Decision.isStruct(pathOrName)) {
private static Class<?> loadClassByPath(PageContext pc, String className, String[] paths) throws PageException {

PageContextImpl pci = (PageContextImpl) pc;

// Fast path: no paths means the default RPC classloader resolves this className. Cache the result.
if (paths == null) {
try {
ClassLoader cl = pci.getRPCClassLoader(null);
java.util.concurrent.ConcurrentHashMap<String, Class<?>> classCache = getClassCacheFor(cl);
Class<?> cached = classCache.get(className);
if (cached != null) return cached;
Class<?> loaded;
try {
loaded = ClassUtil.loadClass(cl, className);
}
catch (ClassException ce) {
if (className.indexOf('.') == -1) {
try {
loaded = ClassUtil.loadClass(cl, "java.lang." + className);
}
catch (ClassException e) {
throw ce;
}
}
else throw ce;
}
classCache.put(className, loaded);
return loaded;
}
catch (Exception e) {
if (e instanceof PageException) throw (PageException) e;
throw Caster.toPageException(e);
}
}

java.util.List<Resource> resources = new ArrayList<Resource>();

if (paths != null && paths.length > 0) {
Expand Down
Loading
Loading