Created
April 5, 2014 21:51
-
-
Save dimzon/9998535 to your computer and use it in GitHub Desktop.
FastReflection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Inspired by https://github.com/EsotericSoftware/reflectasm | |
* the main idea is to take away introspection from ReflectASM | |
* | |
* You must obtain reflection member (Constructor,Method or Field) | |
* yourself then pass it to FastReflection.of() method to obtain | |
* code generated access-object... | |
* | |
* Also (since it operate per-member basis) it takes away ugly switches | |
* into generated bytecode, supports parametrized constructors and | |
* static fields/members... | |
*/ | |
package com.esotericsoftware.reflectasm.dimzon; | |
import org.objectweb.asm.ClassWriter; | |
import org.objectweb.asm.MethodVisitor; | |
import org.objectweb.asm.Type; | |
import java.io.File; | |
import java.lang.reflect.Member; | |
import java.lang.reflect.Modifier; | |
import java.nio.charset.StandardCharsets; | |
import java.security.MessageDigest; | |
import java.security.NoSuchAlgorithmException; | |
import java.util.ArrayList; | |
import java.util.Objects; | |
import static org.objectweb.asm.Opcodes.*; | |
@SuppressWarnings("UnusedDeclaration") | |
public final class FastReflection { | |
public static void main(String[] args) throws Throwable { | |
Class ps = System.out.getClass(); | |
java.lang.reflect.Method psMethod = ps.getMethod("println", Object.class); | |
Method println = of(psMethod); | |
println.invoke(System.out, "System.out"); | |
println.invoke(System.err, "System.err"); | |
java.lang.reflect.Method nanoTime = System.class.getMethod("nanoTime"); | |
println.invoke(System.out, of(nanoTime).invokeStatic()); | |
println.invoke(System.err, of(nanoTime).invokeStatic()); | |
java.lang.reflect.Constructor<File> fileConstructor = File.class.getConstructor(String.class); | |
Object o = of(fileConstructor).newInstance("c:\\bla.txt"); | |
System.out.println(o); | |
java.lang.reflect.Field field = System.class.getField("out"); | |
Object o1 = of(field).get(null); | |
System.out.println(o1); | |
} | |
private static final Object[] EMPTY_VARARGS = new Object[0]; | |
private static final ThreadLocal<MessageDigest> md5 = new ThreadLocal<MessageDigest>() { | |
@Override | |
protected MessageDigest initialValue() { | |
try { | |
return MessageDigest.getInstance("MD5"); | |
} catch (NoSuchAlgorithmException nsae) { | |
throw new RuntimeException(nsae); | |
} | |
} | |
}; | |
private static String memberAccessClassName(Member member) { | |
byte[] md5Bytes = md5.get().digest(member.toString().getBytes(StandardCharsets.UTF_8)); | |
String sign = member.getDeclaringClass().getName() | |
+ "__" | |
+ javax.xml.bind.DatatypeConverter.printHexBinary(md5Bytes); | |
if (sign.startsWith("java.")) sign = "reflectasm." + sign; | |
return sign; | |
} | |
public static Constructor of(java.lang.reflect.Constructor ctor) { | |
Objects.requireNonNull(ctor); | |
Class accessClass; | |
String accessClassName = memberAccessClassName(ctor); | |
AccessClassLoader_2 loader = AccessClassLoader_2.get(ctor.getDeclaringClass()); | |
//noinspection SynchronizationOnLocalVariableOrMethodParameter | |
synchronized (loader) { | |
try { | |
accessClass = loader.loadClass(accessClassName); | |
} catch (ClassNotFoundException ignored) { | |
byte[] byteCode = createAccessor(accessClassName, ctor); | |
accessClass = loader.defineClass(accessClassName, byteCode); | |
} | |
} | |
try { | |
Constructor m = (Constructor) accessClass.newInstance(); | |
m._member = ctor; | |
return m; | |
} catch (Throwable e) { | |
throw new RuntimeException("Error constructing constructor access class: " + accessClassName, e); | |
} | |
} | |
public static Method of(java.lang.reflect.Method method) { | |
Objects.requireNonNull(method); | |
Class accessClass; | |
String accessClassName = memberAccessClassName(method); | |
AccessClassLoader_2 loader = AccessClassLoader_2.get(method.getDeclaringClass()); | |
//noinspection SynchronizationOnLocalVariableOrMethodParameter | |
synchronized (loader) { | |
try { | |
accessClass = loader.loadClass(accessClassName); | |
} catch (ClassNotFoundException ignored) { | |
byte[] byteCode = createAccessor(accessClassName, method); | |
accessClass = loader.defineClass(accessClassName, byteCode); | |
} | |
} | |
try { | |
Method m = (Method) accessClass.newInstance(); | |
m._member = method; | |
return m; | |
} catch (Throwable e) { | |
throw new RuntimeException("Error constructing method access class: " + accessClassName, e); | |
} | |
} | |
public static Field of(java.lang.reflect.Field field) { | |
Objects.requireNonNull(field); | |
Class accessClass; | |
String accessClassName = memberAccessClassName(field); | |
AccessClassLoader_2 loader = AccessClassLoader_2.get(field.getDeclaringClass()); | |
//noinspection SynchronizationOnLocalVariableOrMethodParameter | |
synchronized (loader) { | |
try { | |
accessClass = loader.loadClass(accessClassName); | |
} catch (ClassNotFoundException ignored) { | |
byte[] byteCode = createAccessor(accessClassName, field); | |
accessClass = loader.defineClass(accessClassName, byteCode); | |
} | |
} | |
try { | |
Field m = (Field) accessClass.newInstance(); | |
m._member = field; | |
return m; | |
} catch (Throwable e) { | |
throw new RuntimeException("Error constructing field access class: " + accessClassName, e); | |
} | |
} | |
private static byte[] createAccessor(String accessClassName, java.lang.reflect.Constructor<?> member) { | |
String className = member.getDeclaringClass().getName(); | |
String classNameInternal = internalClassName(className); | |
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); | |
injectConstructor(accessClassName, Constructor.class, cw); | |
{ | |
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "newInstance", | |
"([Ljava/lang/Object;)Ljava/lang/Object;", null, null); | |
mv.visitCode(); | |
mv.visitTypeInsn(NEW, classNameInternal); | |
mv.visitInsn(DUP); | |
StringBuilder buffer = new StringBuilder(128); | |
buffer.setLength(0); | |
buffer.append('('); | |
injectVarArgs(mv, buffer, member.getParameterTypes(), 1); | |
buffer.append(")V"); | |
mv.visitMethodInsn(INVOKESPECIAL, classNameInternal, "<init>", buffer.toString()); | |
mv.visitInsn(ARETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
cw.visitEnd(); | |
return cw.toByteArray(); | |
} | |
private static String internalClassName(String classicClassName) { | |
return classicClassName.replace('.', '/'); | |
} | |
private static byte[] createAccessor(String accessClassName, java.lang.reflect.Field member) { | |
String className = member.getDeclaringClass().getName(); | |
String classNameInternal = internalClassName(className); | |
boolean isStatic = Modifier.isStatic(member.getModifiers()); | |
Class<?> memberType = member.getType(); | |
Type fieldType = Type.getType(memberType); | |
int fieldTypeSort = fieldType.getSort(); | |
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); | |
injectConstructor(accessClassName, Field.class, cw); | |
MethodVisitor mv; | |
{ | |
mv = cw.visitMethod(ACC_PUBLIC, "get", | |
"(Ljava/lang/Object;)Ljava/lang/Object;", null, null); | |
mv.visitCode(); | |
if (!isStatic) { | |
mv.visitVarInsn(ALOAD, 1); | |
mv.visitTypeInsn(CHECKCAST, classNameInternal); | |
} | |
mv.visitFieldInsn(isStatic ? GETSTATIC : GETFIELD, classNameInternal, member.getName(), Type.getDescriptor(memberType)); | |
injectBoxing(mv, fieldTypeSort); | |
mv.visitInsn(ARETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
{ | |
mv = cw.visitMethod(ACC_PUBLIC, "set", | |
"(Ljava/lang/Object;Ljava/lang/Object;)V", null, null); | |
mv.visitCode(); | |
if (!isStatic) { | |
mv.visitVarInsn(ALOAD, 1); | |
mv.visitTypeInsn(CHECKCAST, classNameInternal); | |
} | |
mv.visitVarInsn(ALOAD, 2); | |
injectUnBoxing(mv, fieldType); | |
mv.visitFieldInsn(isStatic ? PUTSTATIC : PUTFIELD, classNameInternal, member.getName(), fieldType.getDescriptor()); | |
mv.visitInsn(RETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
switch (fieldTypeSort) { | |
case Type.BOOLEAN: | |
injectPrimitiveGetterSetter("Boolean", IRETURN, ILOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.BYTE: | |
injectPrimitiveGetterSetter("Byte", IRETURN, ILOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.CHAR: | |
injectPrimitiveGetterSetter("Char", IRETURN, ILOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.SHORT: | |
injectPrimitiveGetterSetter("Short", IRETURN, ILOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.INT: | |
injectPrimitiveGetterSetter("Int", IRETURN, ILOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.FLOAT: | |
injectPrimitiveGetterSetter("Float", FRETURN, FLOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.LONG: | |
injectPrimitiveGetterSetter("Long", LRETURN, LLOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
case Type.DOUBLE: | |
injectPrimitiveGetterSetter("Double", DRETURN, DLOAD, | |
fieldType, fieldTypeSort, cw, member, isStatic, classNameInternal); | |
break; | |
} | |
cw.visitEnd(); | |
return cw.toByteArray(); | |
} | |
private static void injectPrimitiveGetterSetter(String name, int returnValueInstruction, int loadValueInstruction, | |
Type fieldType, int fieldTypeSort, ClassWriter cw, | |
java.lang.reflect.Field field, | |
boolean isStatic, String classNameInternal) { | |
final String typeNameInternal = fieldType.getDescriptor(); | |
MethodVisitor mv; | |
{ | |
mv = cw.visitMethod(ACC_PUBLIC, "get" + name, "(Ljava/lang/Object;)" + typeNameInternal, null, null); | |
mv.visitCode(); | |
if (!isStatic) { | |
mv.visitVarInsn(ALOAD, 1); | |
mv.visitTypeInsn(CHECKCAST, classNameInternal); | |
} | |
mv.visitFieldInsn(isStatic ? GETSTATIC : GETFIELD, classNameInternal, field.getName(), typeNameInternal); | |
mv.visitInsn(returnValueInstruction); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
{ | |
mv = cw.visitMethod(ACC_PUBLIC, "set" + name, "(Ljava/lang/Object;" + typeNameInternal + ")V", null, null); | |
mv.visitCode(); | |
if (!isStatic) { | |
mv.visitVarInsn(ALOAD, 1); | |
mv.visitTypeInsn(CHECKCAST, classNameInternal); | |
} | |
mv.visitVarInsn(loadValueInstruction,2); | |
mv.visitFieldInsn(isStatic ? PUTSTATIC : PUTFIELD, classNameInternal, field.getName(), typeNameInternal); | |
mv.visitInsn(RETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
} | |
private static byte[] createAccessor(String accessClassName, java.lang.reflect.Method member) { | |
String className = member.getDeclaringClass().getName(); | |
String classNameInternal = internalClassName(className); | |
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); | |
injectConstructor(accessClassName, Method.class, cw); | |
{ | |
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke", | |
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null); | |
mv.visitCode(); | |
boolean isStatic = Modifier.isStatic(member.getModifiers()); | |
if (!isStatic) { | |
mv.visitVarInsn(ALOAD, 1); | |
mv.visitTypeInsn(CHECKCAST, classNameInternal); | |
} | |
StringBuilder buffer = new StringBuilder(128); | |
buffer.setLength(0); | |
buffer.append('('); | |
Class[] paramTypes = member.getParameterTypes(); | |
injectVarArgs(mv, buffer, paramTypes, 2); | |
buffer.append(')'); | |
buffer.append(Type.getDescriptor(member.getReturnType())); | |
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, classNameInternal, member.getName(), buffer.toString()); | |
int retValType = Type.getType(member.getReturnType()).getSort(); | |
injectBoxing(mv, retValType); | |
mv.visitInsn(ARETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
cw.visitEnd(); | |
return cw.toByteArray(); | |
} | |
private static void injectBoxing(MethodVisitor mv, int retValType) { | |
switch (retValType) { | |
case Type.VOID: | |
mv.visitInsn(ACONST_NULL); | |
break; | |
case Type.BOOLEAN: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); | |
break; | |
case Type.BYTE: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); | |
break; | |
case Type.CHAR: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); | |
break; | |
case Type.SHORT: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); | |
break; | |
case Type.INT: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); | |
break; | |
case Type.FLOAT: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"); | |
break; | |
case Type.LONG: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); | |
break; | |
case Type.DOUBLE: | |
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); | |
break; | |
} | |
} | |
private static void injectVarArgs(MethodVisitor mv, StringBuilder buffer, Class[] paramTypes, int varArgsPosition) { | |
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { | |
mv.visitVarInsn(ALOAD, varArgsPosition); | |
mv.visitIntInsn(BIPUSH, paramIndex); | |
mv.visitInsn(AALOAD); | |
Type paramType = Type.getType(paramTypes[paramIndex]); | |
injectUnBoxing(mv, paramType); | |
buffer.append(paramType.getDescriptor()); | |
} | |
} | |
private static void injectUnBoxing(MethodVisitor mv, Type paramType) { | |
int paramTypeSort = paramType.getSort(); | |
switch (paramTypeSort) { | |
case Type.BOOLEAN: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); | |
break; | |
case Type.BYTE: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "byteValue", "()B"); | |
break; | |
case Type.CHAR: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"); | |
break; | |
case Type.SHORT: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "shortValue", "()S"); | |
break; | |
case Type.INT: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I"); | |
break; | |
case Type.FLOAT: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F"); | |
break; | |
case Type.LONG: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J"); | |
break; | |
case Type.DOUBLE: | |
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); | |
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D"); | |
break; | |
case Type.ARRAY: | |
mv.visitTypeInsn(CHECKCAST, paramType.getDescriptor()); | |
break; | |
case Type.OBJECT: | |
mv.visitTypeInsn(CHECKCAST, paramType.getInternalName()); | |
break; | |
} | |
} | |
private static void injectConstructor(String accessClassName, Class<?> baseClass, ClassWriter cw) { | |
String accessClassNameInternal = internalClassName(accessClassName); | |
String baseClassNameInternal = internalClassName(baseClass.getName()); | |
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, baseClassNameInternal, | |
null); | |
{ | |
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); | |
mv.visitCode(); | |
mv.visitVarInsn(ALOAD, 0); | |
mv.visitMethodInsn(INVOKESPECIAL, baseClassNameInternal, "<init>", "()V"); | |
mv.visitInsn(RETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
} | |
} | |
public static abstract class Constructor { | |
private java.lang.reflect.Constructor<?> _member; | |
public java.lang.reflect.Constructor<?> member() { | |
return _member; | |
} | |
public final Object newInstance() { | |
return newInstance(EMPTY_VARARGS); | |
} | |
public abstract Object newInstance(Object... args); | |
@Override | |
public String toString() { | |
return member().toString(); | |
} | |
} | |
public static abstract class Method { | |
private java.lang.reflect.Method _member; | |
public java.lang.reflect.Method member() { | |
return _member; | |
} | |
public final Object invokeStatic() { | |
return invoke(null, EMPTY_VARARGS); | |
} | |
public final Object invokeStatic(Object... args) { | |
return invoke(null, args); | |
} | |
public final Object invoke(Object instance) { | |
return invoke(instance, EMPTY_VARARGS); | |
} | |
public abstract Object invoke(Object instance, Object... args); | |
@Override | |
public String toString() { | |
return member().toString(); | |
} | |
} | |
public static abstract class Field { | |
abstract public void set(Object instance, Object value); | |
public void setBoolean(Object instance, boolean value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setByte(Object instance, byte value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setShort(Object instance, short value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setInt(Object instance, int value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setLong(Object instance, long value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setDouble(Object instance, double value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setFloat(Object instance, float value) { | |
throw new UnsupportedOperationException(); | |
} | |
public void setChar(Object instance, char value) { | |
throw new UnsupportedOperationException(); | |
} | |
abstract public Object get(Object instance); | |
public char getChar(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public boolean getBoolean(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public byte getByte(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public short getShort(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public int getInt(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public long getLong(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public double getDouble(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
public float getFloat(Object instance) { | |
throw new UnsupportedOperationException(); | |
} | |
private java.lang.reflect.Field _member; | |
public java.lang.reflect.Field member() { | |
return _member; | |
} | |
@Override | |
public String toString() { | |
return member().toString(); | |
} | |
} | |
/************************************************************* | |
* taken from https://github.com/EsotericSoftware/reflectasm | |
*/ | |
@SuppressWarnings("unchecked") | |
private static class AccessClassLoader_2 extends ClassLoader { | |
static private final ArrayList<AccessClassLoader_2> accessClassLoaders = new ArrayList(); | |
static AccessClassLoader_2 get(Class type) { | |
ClassLoader parent = type.getClassLoader(); | |
synchronized (accessClassLoaders) { | |
//noinspection ForLoopReplaceableByForEach | |
for (int i = 0, n = accessClassLoaders.size(); i < n; i++) { | |
AccessClassLoader_2 accessClassLoader = accessClassLoaders.get(i); | |
if (accessClassLoader.getParent() == parent) return accessClassLoader; | |
} | |
AccessClassLoader_2 accessClassLoader = new AccessClassLoader_2(parent); | |
accessClassLoaders.add(accessClassLoader); | |
return accessClassLoader; | |
} | |
} | |
private AccessClassLoader_2(ClassLoader parent) { | |
super(parent); | |
} | |
private static final Class[] wellKnownClasses = new Class[]{ | |
FastReflection.class, | |
FastReflection.Constructor.class, | |
FastReflection.Method.class, | |
FastReflection.Field.class | |
}; | |
protected synchronized java.lang.Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | |
// These classes come from the classloader that loaded AccessClassLoader_2. | |
for (Class wellKnownClass : wellKnownClasses) { | |
if (name.equals(wellKnownClass.getName())) return wellKnownClass; | |
} | |
// All other classes come from the classloader that loaded the type we are accessing. | |
return super.loadClass(name, resolve); | |
} | |
Class<?> defineClass(String name, byte[] bytes) throws ClassFormatError { | |
try { | |
// Attempt to load the access class in the same loader, which makes protected and default access members accessible. | |
java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{String.class, byte[].class, int.class, | |
int.class}); | |
method.setAccessible(true); | |
//noinspection RedundantArrayCreation,UnnecessaryBoxing | |
return (Class) method.invoke(getParent(), new Object[]{name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length)}); | |
} catch (Exception ignored) { | |
} | |
return defineClass(name, bytes, 0, bytes.length); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment