package com.github.fge.grappa.transform.load;
import com.github.fge.grappa.transform.ParserTransformException;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* Wrapper to provide reflective access to a {@link ClassLoader}
*
* <p>Necessary in order to grant correct access to package private fields of
* parser classes.</p>
*/
public final class ReflectiveClassLoader
implements AutoCloseable
{
private static final String CLASSLOADER = "java.lang.ClassLoader";
private static final String FIND_LOADED_CLASS = "findLoadedClass";
private static final String DEFINE_CLASS = "defineClass";
private final ClassLoader loader;
private final Method findClass;
private final Method loadClass;
/**
* Constructor
*
* @param loader the classloader to use
*/
public ReflectiveClassLoader(final ClassLoader loader)
{
this.loader = Objects.requireNonNull(loader);
try {
final Class<?> loaderClass = Class.forName(CLASSLOADER);
findClass = loaderClass.getDeclaredMethod(FIND_LOADED_CLASS,
String.class);
loadClass = loaderClass.getDeclaredMethod(DEFINE_CLASS,
String.class, byte[].class, int.class, int.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new ParserTransformException(
"unable to find the needed methods", e);
}
final ParserTransformException exception = new ParserTransformException(
"could not change the necessary access modifiers");
try {
findClass.setAccessible(true);
} catch (SecurityException e) {
exception.addSuppressed(e);
}
try {
loadClass.setAccessible(true);
} catch (SecurityException e) {
exception.addSuppressed(e);
}
if (exception.getSuppressed().length > 0)
throw exception;
}
/**
* Returns the class with the given name if it has already been loaded by
* the given class loader
*
* <p>If the class has not been loaded yet, this method returns {@code
* null}.</p>
*
* @param className the full name of the class to be loaded
* @return the class instance, if found
*/
@Nullable
public Class<?> findClass(final String className)
{
Objects.requireNonNull(className);
try {
return (Class<?>) findClass.invoke(loader, className);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new ParserTransformException("unable to find class by name ("
+ className + ')', e);
}
}
/**
* Loads the class defined with the given name and bytecode using the given
* class loader
*
* <p>Since package and class idendity includes the ClassLoader instance
* used to load a class, we use reflection on the given class loader to
* define generated classes.</p>
*
* <p>If we used our own class loader (in order to be able to access the
* protected "defineClass" method), we would likely still be able to load
* generated classes; however, they would not have access to package-private
* classes and members of their super classes.</p>
*
* @param className the full name of the class to be loaded
* @param bytecode the bytecode of the class to load
* @return the class instance
*/
public Class<?> loadClass(final String className, final byte[] bytecode)
{
Objects.requireNonNull(className);
Objects.requireNonNull(bytecode);
try {
return (Class<?>) loadClass.invoke(loader, className, bytecode, 0,
bytecode.length);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new ParserTransformException("unable to load class by name",
e);
}
}
@Override
public void close()
{
final ParserTransformException exception = new ParserTransformException(
"could not close classloader properly");
try {
findClass.setAccessible(false);
} catch (SecurityException e) {
exception.addSuppressed(e);
}
try {
loadClass.setAccessible(false);
} catch (SecurityException e) {
exception.addSuppressed(e);
}
if (exception.getSuppressed().length > 0)
throw exception;
}
}