org.jruby.javasupport.proxy.JavaProxyClassFactory Maven / Gradle / Ivy
/***** BEGIN LICENSE BLOCK *****
* Version: EPL 2.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v20.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006 Kresten Krab Thorup
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.javasupport.proxy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import org.jruby.Ruby;
import org.jruby.runtime.Helpers;
import org.jruby.util.ASM;
import org.jruby.util.ArraySupport;
import org.jruby.util.ClassDefiningClassLoader;
import org.jruby.util.OneShotClassLoader;
import org.jruby.util.cli.Options;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import static org.jruby.javasupport.JavaClass.EMPTY_CLASS_ARRAY;
import static org.jruby.RubyInstanceConfig.JAVA_VERSION;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
public class JavaProxyClassFactory {
private static final Logger LOG = LoggerFactory.getLogger(JavaProxyClassFactory.class);
static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
static final Type JAVA_LANG_CLASS_TYPE = Type.getType(Class.class);
private static final org.objectweb.asm.commons.Method forName = org.objectweb.asm.commons.Method
.getMethod("java.lang.Class forName(java.lang.String)");
private static final String INVOCATION_HANDLER_FIELD_NAME = "__handler";
private static final String PROXY_CLASS_FIELD_NAME = "__proxy_class";
private static final Type PROXY_METHOD_TYPE = Type.getType(JavaProxyMethod.class);
private static final Type PROXY_CLASS_TYPE = Type.getType(JavaProxyClass.class);
private static final Type INVOCATION_HANDLER_TYPE = Type.getType(JavaProxyInvocationHandler.class);
// public Object invoke(Object receiver, JavaProxyMethod method, Object[] args)
private static final org.objectweb.asm.commons.Method invoke = org.objectweb.asm.commons.Method
.getMethod("java.lang.Object invoke(java.lang.Object, " + PROXY_METHOD_TYPE.getClassName() + ", java.lang.Object[])");
private static final Type INTERNAL_PROXY_HELPER_TYPE = Type.getType(InternalJavaProxyHelper.class);
// public static JavaProxyClass initProxyClass(Class)
private static final org.objectweb.asm.commons.Method initProxyClass = org.objectweb.asm.commons.Method
.getMethod(JavaProxyClass.class.getName() + " initProxyClass(java.lang.Class)");
// public static JavaProxyMethod initProxyMethod(JavaProxyClass proxyClass, String name, String desc, boolean hasSuper)
private static final org.objectweb.asm.commons.Method initProxyMethod = org.objectweb.asm.commons.Method
.getMethod(PROXY_METHOD_TYPE.getClassName() + " initProxyMethod("
+ JavaProxyClass.class.getName() + ",java.lang.String,java.lang.String,boolean)");
private static final Type JAVA_PROXY_TYPE = Type.getType(InternalJavaProxy.class);
private static final AtomicInteger counter = new AtomicInteger(0);
private static int nextId() { return counter.incrementAndGet(); }
public static JavaProxyClassFactory createFactory() {
final String factoryClassName = Options.JI_PROXYCLASSFACTORY.load();
JavaProxyClassFactory factory = null;
if ( factoryClassName != null ) {
try {
Class clazz = Class.forName(factoryClassName);
Object instance = clazz.newInstance();
if ( instance instanceof JavaProxyClassFactory ) {
factory = (JavaProxyClassFactory) instance;
LOG.info("Created proxy class factory: {}", factory);
} else {
LOG.error("Invalid proxy class factory: {}", instance);
}
}
catch (ClassNotFoundException e) {
LOG.error("ClassNotFoundException creating proxy class factory: ", e);
}
catch (InstantiationException e) {
LOG.error("InstantiationException creating proxy class factory: ", e);
}
catch (IllegalAccessException e) {
LOG.error("IllegalAccessException creating proxy class factory: ", e);
}
}
return factory != null ? factory : new JavaProxyClassFactory();
}
static ThreadLocal runtimeTLS = new ThreadLocal<>();
public final JavaProxyClass genProxyClass(final Ruby runtime, ClassDefiningClassLoader loader,
String targetClassName, Class superClass, Class[] interfaces, Set names)
throws InvocationTargetException {
final Ruby prev = runtimeTLS.get();
runtimeTLS.set(runtime);
try {
return newProxyClass(runtime, loader, targetClassName, superClass, interfaces, names);
}
finally { runtimeTLS.set(prev); }
}
public JavaProxyClass newProxyClass(final Ruby runtime, ClassDefiningClassLoader loader,
String targetClassName, Class superClass, Class[] interfaces, Set names)
throws InvocationTargetException {
if (targetClassName == null) {
targetClassName = targetClassName(superClass);
}
validateArgs(runtime, targetClassName, superClass);
Type selfType = Type.getType('L' + toInternalClassName(targetClassName) + ';');
Map methods = collectMethods(superClass, interfaces, names);
return generate(loader, targetClassName, superClass, interfaces, methods, selfType);
}
private JavaProxyClass generate(ClassDefiningClassLoader loader, String targetClassName,
Class superClass, Class[] interfaces,
Map methods, Type selfType) {
ClassWriter cw = beginProxyClass(targetClassName, superClass, interfaces, loader);
GeneratorAdapter clazzInit = createClassInitializer(selfType, cw);
generateConstructors(superClass, selfType, cw);
generate___getProxyClass(selfType, cw);
generate___getInvocationHandler(selfType, cw);
generateProxyMethods(superClass, methods, selfType, cw, clazzInit);
// finish class initializer
clazzInit.returnValue();
clazzInit.endMethod();
// end class
cw.visitEnd();
Class clazz = loader.defineClass(selfType.getClassName(), cw.toByteArray());
// trigger class initialization for the class
try {
Field proxy_class = clazz.getDeclaredField(PROXY_CLASS_FIELD_NAME);
// proxy_class.setAccessible(true); // field is public
return (JavaProxyClass) proxy_class.get(clazz);
}
catch (NoSuchFieldException|IllegalAccessException ex) {
Helpers.throwException(ex); return null; // re-throws (although unexpected)
}
}
private static String targetClassName(final Class> clazz) {
// We always prepend an org.jruby.proxy package to the beginning
// because java and javax packages are protected and signed
// jars prevent us generating new classes with those package
// names. See JRUBY-2439.
final String fullName = clazz.getName();
final int idx = fullName.lastIndexOf('.');
String className = idx == -1 ? fullName : fullName.substring(idx + 1);
return proxyPackageName(fullName)
.append('.').append(className)
.append("$Proxy").append(nextId()).toString();
}
private static ClassWriter beginProxyClass(final String className,
final Class superClass, final Class[] interfaces, final ClassDefiningClassLoader loader) {
ClassWriter cw = ASM.newClassWriter(loader.asClassLoader());
// start class
cw.visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
toInternalClassName(className), /*signature*/ null,
toInternalClassName(superClass),
interfaceNamesForProxyClass(interfaces));
// private final JavaProxyInvocationHandler __handler;
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL,
INVOCATION_HANDLER_FIELD_NAME,
INVOCATION_HANDLER_TYPE.getDescriptor(), null, null
).visitEnd();
// /* public */ static final JavaProxyClass __proxy_class;
cw.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
PROXY_CLASS_FIELD_NAME,
PROXY_CLASS_TYPE.getDescriptor(), null, null
).visitEnd();
return cw;
}
private static String[] interfaceNamesForProxyClass(final Class[] interfaces) {
String[] interfaceNames = new String[interfaces.length + 1];
for (int i = 0; i < interfaces.length; i++) {
interfaceNames[i] = toInternalClassName(interfaces[i]);
}
// all proxies implement our InternalJavaProxy interface :
interfaceNames[interfaces.length] = toInternalClassName(InternalJavaProxy.class);
return interfaceNames;
}
private static void generateProxyMethods(Class superClass,
Map methods, Type selfType, ClassVisitor cw,
GeneratorAdapter clazzInit) {
for (MethodData md: methods.values()) {
Type superClassType = Type.getType(superClass);
generateProxyMethod(selfType, superClassType, cw, clazzInit, md);
}
}
/**
* @see InternalJavaProxy
*/
private static void generate___getInvocationHandler(Type selfType, ClassVisitor cw) {
// public JavaProxyInvocationHandler ___getInvocationHandler() { return this.__handler; }
// make getter for handler (due implements InternalJavaProxy)
GeneratorAdapter gh = new GeneratorAdapter(Opcodes.ACC_PUBLIC,
new org.objectweb.asm.commons.Method("___getInvocationHandler",
INVOCATION_HANDLER_TYPE, EMPTY_TYPE_ARRAY), null,
EMPTY_TYPE_ARRAY, cw);
gh.loadThis();
gh.getField(selfType, INVOCATION_HANDLER_FIELD_NAME, INVOCATION_HANDLER_TYPE);
gh.returnValue();
gh.endMethod();
}
/**
* @see InternalJavaProxy
*/
private static void generate___getProxyClass(Type selfType, ClassVisitor cw) {
// public JavaProxyClass __getProxyClass() { return /* static */ __proxy_class; }
// make getter for proxy class (due implements InternalJavaProxy)
GeneratorAdapter gpc = new GeneratorAdapter(Opcodes.ACC_PUBLIC,
new org.objectweb.asm.commons.Method("___getProxyClass",
PROXY_CLASS_TYPE, EMPTY_TYPE_ARRAY), null,
EMPTY_TYPE_ARRAY, cw);
gpc.getStatic(selfType, PROXY_CLASS_FIELD_NAME, PROXY_CLASS_TYPE);
gpc.returnValue();
gpc.endMethod();
}
private static void generateConstructors(Class superClass, Type selfType, ClassVisitor cw) {
Constructor[] cons = superClass.getDeclaredConstructors();
for (int i = 0; i < cons.length; i++) {
// if the constructor is private, pretend it doesn't exist
if (Modifier.isPrivate(cons[i].getModifiers())) continue;
// otherwise, define everything and let some of them fail at invocation
generateConstructor(selfType, cons[i], cw);
}
}
private static GeneratorAdapter createClassInitializer(Type selfType, ClassVisitor cw) {
GeneratorAdapter clazzInit = new GeneratorAdapter(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
new org.objectweb.asm.commons.Method("", Type.VOID_TYPE, EMPTY_TYPE_ARRAY),
null, EMPTY_TYPE_ARRAY, cw);
clazzInit.visitLdcInsn(selfType.getClassName());
clazzInit.invokeStatic(JAVA_LANG_CLASS_TYPE, forName);
clazzInit.invokeStatic(INTERNAL_PROXY_HELPER_TYPE, initProxyClass);
clazzInit.dup();
clazzInit.putStatic(selfType, PROXY_CLASS_FIELD_NAME, PROXY_CLASS_TYPE);
// __proxy_class = InternalJavaProxyHelper.initProxyClass( Class.forName(className) );
return clazzInit;
}
private static void generateProxyMethod(Type selfType, Type superType,
ClassVisitor cw, GeneratorAdapter clazzInit, MethodData md) {
if (!md.generateProxyMethod()) return;
org.objectweb.asm.commons.Method m = md.getMethod();
Type[] ex = toTypes(md.getExceptions());
String field_name = "__mth$" + md.getName() + md.scrambledSignature();
// private static JavaProxyMethod __mth$sort$java_util_Comparator;
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
field_name, PROXY_METHOD_TYPE.getDescriptor(), null, null);
fv.visitEnd();
// static { ... } initializer block
clazzInit.dup();
clazzInit.push(m.getName());
clazzInit.push(m.getDescriptor());
clazzInit.push(md.isImplemented());
// JavaProxyMethod initProxyMethod(JavaProxyClass proxyClass, String name, String desc, boolean hasSuper)
clazzInit.invokeStatic(INTERNAL_PROXY_HELPER_TYPE, initProxyMethod);
// __mth$sort$java_util_Comparator = initProxyMethod(...)
clazzInit.putStatic(selfType, field_name, PROXY_METHOD_TYPE);
org.objectweb.asm.commons.Method sm = new org.objectweb.asm.commons.Method(
"__super$" + m.getName(), m.getReturnType(), m.getArgumentTypes()
);
//
// construct the proxy method
//
GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, ex, cw);
ga.loadThis();
ga.getField(selfType, INVOCATION_HANDLER_FIELD_NAME, INVOCATION_HANDLER_TYPE);
// if the method is extending something, then we have to test if the handler is initialized...
if (md.isImplemented()) {
ga.dup();
Label ok = ga.newLabel();
ga.ifNonNull(ok);
ga.loadThis();
ga.loadArgs();
ga.invokeConstructor(superType, m);
ga.returnValue();
ga.mark(ok);
}
ga.loadThis();
ga.getStatic(selfType, field_name, PROXY_METHOD_TYPE);
if (m.getArgumentTypes().length == 0) {
// load static empty array
ga.getStatic(JAVA_PROXY_TYPE, "NO_ARGS", toType(Object[].class));
} else {
// box arguments
ga.loadArgArray();
}
Label before = ga.mark();
ga.invokeInterface(INVOCATION_HANDLER_TYPE, invoke);
Label after = ga.mark();
ga.unbox(m.getReturnType());
ga.returnValue();
// this is a simple rethrow handler
Label rethrow = ga.mark();
ga.visitInsn(Opcodes.ATHROW);
for (int i = 0; i < ex.length; i++) {
ga.visitTryCatchBlock(before, after, rethrow, ex[i].getInternalName());
}
ga.visitTryCatchBlock(before, after, rethrow, "java/lang/Error");
ga.visitTryCatchBlock(before, after, rethrow, "java/lang/RuntimeException");
Type thr = toType(Throwable.class);
Label handler = ga.mark();
Type udt = toType(UndeclaredThrowableException.class);
int loc = ga.newLocal(thr);
ga.storeLocal(loc, thr);
ga.newInstance(udt);
ga.dup();
ga.loadLocal(loc, thr);
ga.invokeConstructor(udt, org.objectweb.asm.commons.Method.getMethod("void (java.lang.Throwable)"));
ga.throwException();
ga.visitTryCatchBlock(before, after, handler, "java/lang/Throwable");
ga.endMethod();
//
// construct the super-proxy method
//
if (md.isImplemented()) {
GeneratorAdapter ga2 = new GeneratorAdapter(Opcodes.ACC_PUBLIC, sm, null, ex, cw);
ga2.loadThis();
ga2.loadArgs();
ga2.invokeConstructor(superType, m);
ga2.returnValue();
ga2.endMethod();
}
}
private static Class[] generateConstructor(Type selfType, Constructor constructor, ClassVisitor cw) {
Class[] superConstructorParameterTypes = constructor.getParameterTypes();
Class[] newConstructorParameterTypes = new Class[superConstructorParameterTypes.length + 1];
ArraySupport.copy(superConstructorParameterTypes, newConstructorParameterTypes, 0, superConstructorParameterTypes.length);
newConstructorParameterTypes[superConstructorParameterTypes.length] = JavaProxyInvocationHandler.class;
int access = Opcodes.ACC_PUBLIC;
String name1 = "";
String signature = null;
Class[] superConstructorExceptions = constructor.getExceptionTypes();
boolean superConstructorVarArgs = constructor.isVarArgs();
org.objectweb.asm.commons.Method super_m = new org.objectweb.asm.commons.Method(
name1, Type.VOID_TYPE, toTypes(superConstructorParameterTypes));
org.objectweb.asm.commons.Method m = new org.objectweb.asm.commons.Method(
name1, Type.VOID_TYPE, toTypes(newConstructorParameterTypes));
String[] exceptionNames = toInternalNames( superConstructorExceptions );
MethodVisitor mv = cw.visitMethod(access, m.getName(), m.getDescriptor(), signature, exceptionNames);
// marking with @SafeVarargs so that we can correctly detect proxied var-arg constructors :
if ( superConstructorVarArgs ) mv.visitAnnotation(Type.getDescriptor(VarArgs.class), true);
GeneratorAdapter ga = new GeneratorAdapter(access, m, mv);
ga.loadThis();
ga.loadArgs(0, superConstructorParameterTypes.length);
ga.invokeConstructor(toType(constructor.getDeclaringClass()), super_m);
ga.loadThis();
ga.loadArg(superConstructorParameterTypes.length);
ga.putField(selfType, INVOCATION_HANDLER_FIELD_NAME, INVOCATION_HANDLER_TYPE);
// do a void return
ga.returnValue();
ga.endMethod();
return newConstructorParameterTypes;
}
static boolean isVarArgs(final Constructor> ctor) {
return ctor.isVarArgs() || ctor.getAnnotation(VarArgs.class) != null;
}
//static boolean isVarArgs(final Method method) {
// return method.isVarArgs() || method.getAnnotation(VarArgs.class) != null;
//}
private static String toInternalClassName(Class clazz) {
return toInternalClassName(clazz.getName());
}
private static String toInternalClassName(String name) {
return name.replace('.', '/');
}
private static Type toType(Class clazz) {
return Type.getType(clazz);
}
private static Type[] toTypes(Class[] params) {
Type[] types = new Type[params.length];
for (int i = 0; i < types.length; i++) {
types[i] = Type.getType(params[i]);
}
return types;
}
private static String[] toInternalNames(final Class[] params) {
if (params == null) return null;
String[] names = new String[params.length];
for (int i = 0; i < names.length; ++i) {
names[i] = Type.getType(params[i]).getInternalName();
}
return names;
}
private static Map collectMethods(
final Class superClass,
final Class[] interfaces,
final Set names) {
Map methods = new HashMap<>();
HashSet allClasses = new HashSet<>();
addClass(allClasses, methods, superClass, names);
addInterfaces(allClasses, methods, interfaces, names);
return methods;
}
static final class MethodData {
final Set methods = new HashSet();
final Method mostSpecificMethod;
final Class[] mostSpecificParameterTypes;
//private boolean hasPublicDecl = false;
MethodData(final Method method) {
this.mostSpecificMethod = method;
this.mostSpecificParameterTypes = mostSpecificMethod.getParameterTypes();
//hasPublicDecl = method.getDeclaringClass().isInterface() || Modifier.isPublic(method.getModifiers());
}
private StringBuilder scrambledSignature() {
StringBuilder sb = new StringBuilder();
for ( Class param : getParameterTypes() ) {
sb.append('$');
final char[] name = param.getName().toCharArray();
for (int i = 0; i < name.length; i++) {
final char c;
switch ( c = name[i] ) {
case '.' : sb.append('_'); break;
case '[' : sb.append('1'); break;
case ';' : sb.append('2'); break;
default : sb.append(c);
}
}
}
return sb;
}
public Class getDeclaringClass() {
return mostSpecificMethod.getDeclaringClass();
}
private org.objectweb.asm.commons.Method getMethod() {
return new org.objectweb.asm.commons.Method(getName(), Type
.getType(getReturnType()), getType(getParameterTypes()));
}
private static Type[] getType(Class[] parameterTypes) {
Type[] result = new Type[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
result[i] = Type.getType(parameterTypes[i]);
}
return result;
}
private String getName() {
return mostSpecificMethod.getName();
}
private Class[] getParameterTypes() {
return mostSpecificParameterTypes;
}
private Class[] getExceptions() {
final IdentityHashMap exceptions = new IdentityHashMap<>(8);
for ( final Method method : this.methods ) {
Class[] exTypes = method.getExceptionTypes();
for (int i = 0; i < exTypes.length; i++) {
final Class> exType = exTypes[i];
if ( exceptions.containsKey(exType) ) continue;
boolean add = true;
Iterator it = exceptions.keySet().iterator();
while ( it.hasNext() ) {
final Class> curType = it.next();
if ( curType.isAssignableFrom(exType) ) {
add = false;
break;
}
if ( exType.isAssignableFrom(curType) ) {
it.remove();
add = true;
}
}
if ( add ) exceptions.put(exType, null);
}
}
return exceptions.isEmpty() ? EMPTY_CLASS_ARRAY : exceptions.keySet().toArray(new Class[ exceptions.size() ]);
}
private boolean generateProxyMethod() {
return ! isFinal() && ! isPrivate();
}
private void add(Method method) {
methods.add(method);
//hasPublicDecl |= Modifier.isPublic(method.getModifiers());
}
Class getReturnType() {
return mostSpecificMethod.getReturnType();
}
boolean isFinal() {
if ( mostSpecificMethod.getDeclaringClass().isInterface() ) {
return false;
}
return Modifier.isFinal( mostSpecificMethod.getModifiers() );
}
boolean isPrivate() {
if ( mostSpecificMethod.getDeclaringClass().isInterface() ) {
return false;
}
return Modifier.isPrivate( mostSpecificMethod.getModifiers() );
}
boolean isImplemented() {
if ( mostSpecificMethod.getDeclaringClass().isInterface() ) {
return false;
}
return ! Modifier.isAbstract( mostSpecificMethod.getModifiers() );
}
}
static final class MethodKey {
private final String name;
private final Class[] arguments;
MethodKey(final Method method) {
this.name = method.getName();
this.arguments = method.getParameterTypes();
}
@Override
public boolean equals(Object obj) {
if ( obj instanceof MethodKey ) {
MethodKey key = (MethodKey) obj;
return name.equals(key.name) && Arrays.equals(arguments, key.arguments);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
final StringBuilder str = new StringBuilder().append(name);
str.append('(');
final int last = arguments.length - 1;
for ( int i=0; i= 0 ) str.append(arguments[last].getName());
str.append(')');
return str.toString();
}
}
private static void addInterfaces(
final Set allClasses,
final Map methods,
final Class[] ifaces,
final Set names) {
for ( int i = 0; i < ifaces.length; i++ ) {
addInterface(allClasses, methods, ifaces[i], names);
}
}
private static void addInterface(
final Set allClasses,
final Map methods,
final Class iface,
final Set names) {
if ( allClasses.add(iface) ) {
addMethods(methods, iface, names);
addInterfaces(allClasses, methods, iface.getInterfaces(), names);
}
}
private static void addMethods(
final Map methods,
final Class classOrIface,
final Set names) {
final Method[] decMethods = classOrIface.getDeclaredMethods();
for ( int i = 0; i < decMethods.length; i++ ) {
final Method decMethod = decMethods[i];
if ( names.contains(decMethod.getName()) ) {
addMethod(methods, decMethod);
}
}
}
private static void addMethod(final Map methods, final Method method) {
final int mod = method.getModifiers();
if ( Modifier.isStatic(mod) || Modifier.isPrivate(mod) ) {
return;
}
MethodKey methodKey = new MethodKey(method);
MethodData methodData = methods.get(methodKey);
if (methodData == null) {
methodData = new MethodData(method);
methods.put(methodKey, methodData);
}
methodData.add(method);
}
private static void addClass(
final Set allClasses,
final Map methods,
final Class clazz,
final Set names) {
if ( allClasses.add(clazz) ) {
addMethods(methods, clazz, names);
Class superClass = clazz.getSuperclass();
if ( superClass != null ) {
addClass(allClasses, methods, superClass, names);
}
addInterfaces(allClasses, methods, clazz.getInterfaces(), names);
}
}
private static void validateArgs(Ruby runtime, String targetClassName, Class superClass) {
if ( Modifier.isFinal(superClass.getModifiers()) ) {
throw runtime.newTypeError("cannot extend final class " + superClass.getName());
}
if ( ! hasPublicOrProtectedConstructor(superClass) ) {
throw runtime.newTypeError("class " + superClass.getName() + " doesn't have a public or protected constructor");
}
String targetPackage = packageName(targetClassName);
String packagePath = targetPackage.replace('.', '/');
if (packagePath.startsWith("java")) {
throw runtime.newTypeError("cannot add classes to package " + packagePath);
}
final Package pkg = Package.getPackage(packagePath);
if ( pkg != null && pkg.isSealed() ) {
throw runtime.newTypeError("package " + pkg + " is sealed");
}
}
private static boolean hasPublicOrProtectedConstructor(final Class clazz) {
Constructor[] constructors = clazz.getDeclaredConstructors();
for ( Constructor constructor : constructors ) {
final int mod = constructor.getModifiers();
if ( Modifier.isPublic(mod) || Modifier.isProtected(mod) ) {
return true;
}
}
return false;
}
private static String packageName(String clazzName) {
int idx = clazzName.lastIndexOf('.');
if ( idx == -1 ) return "";
return clazzName.substring(0, idx);
}
private static StringBuilder proxyPackageName(final String className) {
final String proxyPackagePrefix = "org.jruby.proxy";
final StringBuilder str = new StringBuilder(proxyPackagePrefix.length() + className.length() + 8);
final int idx = className.lastIndexOf('.');
str.append(proxyPackagePrefix);
return idx == -1 ? str : str.append('.').append(className.substring(0, idx));
}
/**
* Variable arguments marker for generated constructor.
* @note could have used @SafeVarargs but it's Java 7+
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public static @interface VarArgs {}
@Deprecated
public final JavaProxyClass genProxyClass(final Ruby runtime, ClassLoader loader,
String targetClassName, Class superClass, Class[] interfaces, Set names)
throws InvocationTargetException {
if (loader instanceof ClassDefiningClassLoader) {
return genProxyClass(runtime, (ClassDefiningClassLoader) loader, targetClassName, superClass, interfaces, names);
}
return genProxyClass(runtime, (ClassDefiningClassLoader) new OneShotClassLoader(loader), targetClassName, superClass, interfaces, names);
}
@Deprecated
public JavaProxyClass newProxyClass(final Ruby runtime, ClassLoader loader,
String targetClassName, Class superClass, Class[] interfaces, Set names)
throws InvocationTargetException {
if (loader instanceof ClassDefiningClassLoader) {
return newProxyClass(runtime, (ClassDefiningClassLoader) loader, targetClassName, superClass, interfaces, names);
}
return newProxyClass(runtime, (ClassDefiningClassLoader) new OneShotClassLoader(loader), targetClassName, superClass, interfaces, names);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy