package org.jruby.javasupport;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.headius.backport9.modules.Modules;
import org.jcodings.Encoding;
import org.jruby.*;
import org.jruby.javasupport.binding.Initializer;
import org.jruby.javasupport.proxy.JavaProxyClass;
import org.jruby.javasupport.proxy.JavaProxyConstructor;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.Constants;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodN;
import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodZero;
import org.jruby.java.addons.ArrayJavaAddons;
import org.jruby.java.addons.ClassJavaAddons;
import org.jruby.java.addons.IOJavaAddons;
import org.jruby.java.addons.KernelJavaAddons;
import org.jruby.java.addons.StringJavaAddons;
import org.jruby.java.codegen.RealClassGenerator;
import org.jruby.java.dispatch.CallableSelector;
import org.jruby.java.proxies.ArrayJavaProxy;
import org.jruby.java.proxies.ArrayJavaProxyCreator;
import org.jruby.java.proxies.ConcreteJavaProxy;
import org.jruby.java.proxies.MapJavaProxy;
import org.jruby.java.proxies.InterfaceJavaProxy;
import org.jruby.java.proxies.JavaInterfaceTemplate;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.java.proxies.RubyObjectHolderProxy;
import org.jruby.java.util.SystemPropertiesMap;
import org.jruby.javasupport.proxy.JavaProxyClassFactory;
import org.jruby.util.*;
import org.jruby.util.cli.Options;
import org.jruby.util.collections.NonBlockingHashMapLong;
import static org.jruby.java.invokers.RubyToJavaInvoker.convertArguments;
import static org.jruby.runtime.Visibility.*;
@JRubyModule(name = "Java")
public class Java implements Library {
public static final boolean NEW_STYLE_EXTENSION = Options.JI_NEWSTYLEEXTENSION.load();
public static final boolean OBJECT_PROXY_CACHE = Options.JI_OBJECTPROXYCACHE.load();
@Override
public void load(Ruby runtime, boolean wrap) {
final RubyModule Java = createJavaModule(runtime);
JavaPackage.createJavaPackageClass(runtime, Java);
org.jruby.javasupport.ext.Kernel.define(runtime);
org.jruby.javasupport.ext.JavaLang.define(runtime);
org.jruby.javasupport.ext.JavaLangReflect.define(runtime);
org.jruby.javasupport.ext.JavaUtil.define(runtime);
org.jruby.javasupport.ext.JavaUtilRegex.define(runtime);
org.jruby.javasupport.ext.JavaIo.define(runtime);
org.jruby.javasupport.ext.JavaNet.define(runtime);
org.jruby.javasupport.ext.JavaMath.define(runtime);
org.jruby.javasupport.ext.JavaTime.define(runtime);
runtime.getLoadService().load("jruby/java.rb", false);
final RubyClass ArrayJavaProxy = runtime.getClass("ArrayJavaProxy");
ArrayJavaProxy.setSuperClass(runtime.getJavaSupport().getObjectJavaClass().getProxyClass());
ArrayJavaProxy.includeModule(runtime.getEnumerable());
RubyClassPathVariable.createClassPathVariable(runtime);
runtime.setJavaProxyClassFactory(JavaProxyClassFactory.createFactory());
final Map systemProperties = new SystemPropertiesMap();
RubyClass proxyClass = (RubyClass) getProxyClass(runtime, SystemPropertiesMap.class);
runtime.getObject().setConstantQuiet("ENV_JAVA", new MapJavaProxy(runtime, proxyClass, systemProperties));
}
@SuppressWarnings("deprecation")
public static RubyModule createJavaModule(final Ruby runtime) {
final ThreadContext context = runtime.getCurrentContext();
final RubyModule Java = runtime.defineModule("Java");
Java.defineAnnotatedMethods(Java.class);
final RubyClass _JavaObject = JavaObject.createJavaObjectClass(runtime, Java);
JavaArray.createJavaArrayClass(runtime, Java, _JavaObject);
JavaClass.createJavaClassClass(runtime, Java, _JavaObject);
JavaMethod.createJavaMethodClass(runtime, Java);
JavaConstructor.createJavaConstructorClass(runtime, Java);
JavaField.createJavaFieldClass(runtime, Java);
JavaProxyMethods.createJavaProxyMethods(context);
JavaProxy.createJavaProxy(context);
ArrayJavaProxyCreator.createArrayJavaProxyCreator(context);
ConcreteJavaProxy.createConcreteJavaProxy(context);
InterfaceJavaProxy.createInterfaceJavaProxy(context);
ArrayJavaProxy.createArrayJavaProxy(context);
MapJavaProxy.createMapJavaProxy(runtime);
JavaProxyClass.createJavaProxyClasses(runtime, Java);
JavaInterfaceTemplate.createJavaInterfaceTemplateModule(context);
runtime.defineModule("JavaUtilities").defineAnnotatedMethods(JavaUtilities.class);
JavaArrayUtilities.createJavaArrayUtilitiesModule(runtime);
runtime.getArray().defineAnnotatedMethods(ArrayJavaAddons.class);
runtime.getKernel().defineAnnotatedMethods(KernelJavaAddons.class);
runtime.getString().defineAnnotatedMethods(StringJavaAddons.class);
runtime.getIO().defineAnnotatedMethods(IOJavaAddons.class);
runtime.getClassClass().defineAnnotatedMethods(ClassJavaAddons.class);
if ( runtime.getObject().isConstantDefined("StringIO") ) {
((RubyClass) runtime.getObject().getConstant("StringIO")).defineAnnotatedMethods(IOJavaAddons.AnyIO.class);
}
addNameClassMappings(runtime, runtime.getJavaSupport().getNameClassMap());
runtime.getJavaSupport().setObjectJavaClass( JavaClass.get(runtime, Object.class) );
return Java;
}
public static class OldStyleExtensionInherited {
@Deprecated
public static IRubyObject inherited(IRubyObject self, IRubyObject subclass) {
return inherited(self.getRuntime().getCurrentContext(), self, subclass);
}
@JRubyMethod
public static IRubyObject inherited(ThreadContext context, IRubyObject self, IRubyObject subclass) {
return invokeProxyClassInherited(context, self, subclass);
}
};
public static class NewStyleExtensionInherited {
@Deprecated
public static IRubyObject inherited(IRubyObject self, IRubyObject subclass) {
return inherited(self.getRuntime().getCurrentContext(), self, subclass);
}
@JRubyMethod
public static IRubyObject inherited(ThreadContext context, IRubyObject self, IRubyObject subclass) {
if ( ! ( subclass instanceof RubyClass ) ) {
throw context.runtime.newTypeError(subclass, context.runtime.getClassClass());
}
JavaInterfaceTemplate.addRealImplClassNew((RubyClass) subclass);
return context.nil;
}
};
private static void addNameClassMappings(final Ruby runtime, final Map<String, JavaClass> nameClassMap) {
JavaClass booleanClass = JavaClass.get(runtime, Boolean.class);
nameClassMap.put("boolean", JavaClass.get(runtime, Boolean.TYPE));
nameClassMap.put("Boolean", booleanClass);
nameClassMap.put("java.lang.Boolean", booleanClass);
JavaClass byteClass = JavaClass.get(runtime, Byte.class);
nameClassMap.put("byte", JavaClass.get(runtime, Byte.TYPE));
nameClassMap.put("Byte", byteClass);
nameClassMap.put("java.lang.Byte", byteClass);
JavaClass shortClass = JavaClass.get(runtime, Short.class);
nameClassMap.put("short", JavaClass.get(runtime, Short.TYPE));
nameClassMap.put("Short", shortClass);
nameClassMap.put("java.lang.Short", shortClass);
JavaClass charClass = JavaClass.get(runtime, Character.class);
nameClassMap.put("char", JavaClass.get(runtime, Character.TYPE));
nameClassMap.put("Character", charClass);
nameClassMap.put("Char", charClass);
nameClassMap.put("java.lang.Character", charClass);
JavaClass intClass = JavaClass.get(runtime, Integer.class);
nameClassMap.put("int", JavaClass.get(runtime, Integer.TYPE));
nameClassMap.put("Integer", intClass);
nameClassMap.put("Int", intClass);
nameClassMap.put("java.lang.Integer", intClass);
JavaClass longClass = JavaClass.get(runtime, Long.class);
nameClassMap.put("long", JavaClass.get(runtime, Long.TYPE));
nameClassMap.put("Long", longClass);
nameClassMap.put("java.lang.Long", longClass);
JavaClass floatClass = JavaClass.get(runtime, Float.class);
nameClassMap.put("float", JavaClass.get(runtime, Float.TYPE));
nameClassMap.put("Float", floatClass);
nameClassMap.put("java.lang.Float", floatClass);
JavaClass doubleClass = JavaClass.get(runtime, Double.class);
nameClassMap.put("double", JavaClass.get(runtime, Double.TYPE));
nameClassMap.put("Double", doubleClass);
nameClassMap.put("java.lang.Double", doubleClass);
JavaClass bigintClass = JavaClass.get(runtime, BigInteger.class);
nameClassMap.put("big_int", bigintClass);
nameClassMap.put("big_integer", bigintClass);
nameClassMap.put("BigInteger", bigintClass);
nameClassMap.put("java.math.BigInteger", bigintClass);
JavaClass bigdecimalClass = JavaClass.get(runtime, BigDecimal.class);
nameClassMap.put("big_decimal", bigdecimalClass);
nameClassMap.put("BigDecimal", bigdecimalClass);
nameClassMap.put("java.math.BigDecimal", bigdecimalClass);
JavaClass objectClass = JavaClass.get(runtime, Object.class);
nameClassMap.put("object", objectClass);
nameClassMap.put("Object", objectClass);
nameClassMap.put("java.lang.Object", objectClass);
JavaClass stringClass = JavaClass.get(runtime, String.class);
nameClassMap.put("string", stringClass);
nameClassMap.put("String", stringClass);
nameClassMap.put("java.lang.String", stringClass);
nameClassMap.put("void", JavaClass.get(runtime, Void.TYPE));
nameClassMap.put("Void", JavaClass.get(runtime, Void.class));
}
public static IRubyObject create_proxy_class(
IRubyObject self,
IRubyObject name,
IRubyObject javaClass,
IRubyObject module) {
final Ruby runtime = self.getRuntime();
if ( ! ( module instanceof RubyModule ) ) {
throw runtime.newTypeError(module, runtime.getModule());
}
final RubyModule proxyClass = get_proxy_class(self, javaClass);
final String constName = name.asJavaString();
IRubyObject existing = ((RubyModule) module).getConstantNoConstMissing(constName);
if ( existing != null && existing != RubyBasicObject.UNDEF && existing != proxyClass ) {
runtime.getWarnings().warn("replacing " + existing + " with " + proxyClass + " in constant '" + constName + " on class/module " + module);
}
((RubyModule) module).setConstantQuiet(name.asJavaString(), proxyClass);
return proxyClass;
}
public static IRubyObject get_java_class(final IRubyObject self, final IRubyObject name) {
try {
return JavaClass.for_name(self, name);
}
catch (Exception e) {
self.getRuntime().getJavaSupport().handleNativeException(e, null);
return self.getRuntime().getNil();
}
}
public static IRubyObject getInstance(Ruby runtime, Object rawJavaObject) {
return getInstance(runtime, rawJavaObject, false);
}
public static IRubyObject getInstance(Ruby runtime, Object rawJavaObject, boolean forceCache) {
if (rawJavaObject != null) {
RubyClass proxyClass = (RubyClass) getProxyClass(runtime, rawJavaObject.getClass());
if (OBJECT_PROXY_CACHE || forceCache || proxyClass.getCacheProxy()) {
return runtime.getJavaSupport().getObjectProxyCache().getOrCreate(rawJavaObject, proxyClass);
}
return allocateProxy(rawJavaObject, proxyClass);
}
return runtime.getNil();
}
public static RubyModule getInterfaceModule(final Ruby runtime, final JavaClass javaClass) {
return getInterfaceModule(runtime, javaClass.javaClass());
}
public static RubyModule getInterfaceModule(final Ruby runtime, final Class javaClass) {
return Java.getProxyClass(runtime, javaClass);
}
public static RubyModule get_interface_module(final Ruby runtime, IRubyObject javaClassObject) {
JavaClass javaClass; String javaName;
if ( javaClassObject instanceof RubyString ) {
javaClass = JavaClass.forNameVerbose(runtime, javaClassObject.asJavaString());
}
else if ( javaClassObject instanceof JavaClass ) {
javaClass = (JavaClass) javaClassObject;
}
else if ( (javaName = unwrapJavaString(javaClassObject)) != null ) {
javaClass = JavaClass.forNameVerbose(runtime, javaName);
}
else {
throw runtime.newArgumentError("expected JavaClass, got " + javaClassObject);
}
return getInterfaceModule(runtime, javaClass);
}
public static RubyModule get_proxy_class(final IRubyObject self, final IRubyObject java_class) {
final Ruby runtime = self.getRuntime();
final JavaClass javaClass; String javaName;
if ( java_class instanceof RubyString ) {
javaClass = JavaClass.for_name(self, java_class);
}
else if ( java_class instanceof JavaClass ) {
javaClass = (JavaClass) java_class;
}
else if ( (javaName = unwrapJavaString(java_class)) != null ) {
javaClass = JavaClass.for_name(self, javaName);
}
else {
throw runtime.newTypeError(java_class, runtime.getJavaSupport().getJavaClassClass());
}
return getProxyClass(runtime, javaClass);
}
private static String unwrapJavaString(IRubyObject arg) {
if (arg instanceof JavaProxy) {
Object str = ((JavaProxy) arg).getObject();
return str instanceof String ? (String) str : null;
}
return null;
}
public static RubyClass getProxyClassForObject(Ruby runtime, Object object) {
return (RubyClass) getProxyClass(runtime, object.getClass());
}
public static RubyModule getProxyClass(Ruby runtime, JavaClass javaClass) {
return getProxyClass(runtime, javaClass.javaClass());
}
@SuppressWarnings("deprecation")
public static RubyModule getProxyClass(final Ruby runtime, final Class<?> clazz) {
RubyModule proxy = runtime.getJavaSupport().getUnfinishedProxy(clazz);
if (proxy != null) return proxy;
return runtime.getJavaSupport().getProxyClassFromCache(clazz);
}
@SuppressWarnings("deprecation")
static RubyModule createProxyClassForClass(final Ruby runtime, final Class<?> clazz) {
final JavaSupport javaSupport = runtime.getJavaSupport();
RubyModule proxy;
RubyClass superClass = null;
if (clazz.isInterface()) {
proxy = (RubyModule) runtime.getJavaSupport().getJavaInterfaceTemplate().dup();
} else {
if (clazz.isArray()) {
superClass = javaSupport.getArrayProxyClass();
} else if (clazz.isPrimitive()) {
superClass = javaSupport.getConcreteProxyClass();
} else if (clazz == Object.class) {
superClass = javaSupport.getConcreteProxyClass();
} else {
superClass = (RubyClass) getProxyClass(runtime, clazz.getSuperclass());
}
proxy = RubyClass.newClass(runtime, superClass);
}
javaSupport.beginProxy(clazz, proxy);
try {
if (clazz.isInterface()) {
generateInterfaceProxy(runtime, clazz, proxy);
} else {
generateClassProxy(runtime, clazz, (RubyClass) proxy, superClass);
}
} finally {
javaSupport.endProxy(clazz);
}
return proxy;
}
private static void generateInterfaceProxy(final Ruby runtime, final Class javaClass, final RubyModule proxy) {
assert javaClass.isInterface();
final Class<?>[] extended = javaClass.getInterfaces();
for (int i = extended.length; --i >= 0; ) {
RubyModule extModule = getInterfaceModule(runtime, extended[i]);
proxy.includeModule(extModule);
}
Initializer.setupProxyModule(runtime, javaClass, proxy);
addToJavaPackageModule(proxy);
}
private static void generateClassProxy(Ruby runtime, Class<?> clazz, RubyClass proxy, RubyClass superClass) {
if ( clazz.isArray() ) {
createProxyClass(runtime, proxy, clazz, superClass, true);
if ( clazz.getComponentType() == byte.class ) {
proxy.defineAnnotatedMethods( ByteArrayProxyMethods.class );
}
}
else if ( clazz.isPrimitive() ) {
createProxyClass(runtime, proxy, clazz, superClass, true);
}
else if ( clazz == Object.class ) {
createProxyClass(runtime, proxy, clazz, superClass, true);
if (NEW_STYLE_EXTENSION) {
proxy.getMetaClass().defineAnnotatedMethods(NewStyleExtensionInherited.class);
} else {
proxy.getMetaClass().defineAnnotatedMethods(OldStyleExtensionInherited.class);
}
addToJavaPackageModule(proxy);
}
else {
createProxyClass(runtime, proxy, clazz, superClass, false);
final Class<?>[] interfaces = clazz.getInterfaces();
for ( int i = interfaces.length; --i >= 0; ) {
proxy.includeModule(getInterfaceModule(runtime, interfaces[i]));
}
if ( Modifier.isPublic(clazz.getModifiers()) ) {
addToJavaPackageModule(proxy);
}
}
if ( Modifier.isFinal(clazz.getModifiers()) ) {
final String clazzName = clazz.getCanonicalName();
proxy.getMetaClass().addMethod("inherited", new org.jruby.internal.runtime.methods.JavaMethod(proxy, PUBLIC, "inherited") {
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
throw context.runtime.newTypeError("can not extend final Java class: " + clazzName);
}
});
}
}
private static RubyClass createProxyClass(final Ruby runtime,
final RubyClass proxyClass, final Class<?> javaClass,
final RubyClass superClass, boolean invokeInherited) {
proxyClass.makeMetaClass( superClass.getMetaClass() );
if ( Map.class.isAssignableFrom( javaClass ) ) {
proxyClass.setAllocator( runtime.getJavaSupport().getMapJavaProxyClass().getAllocator() );
proxyClass.defineAnnotatedMethods( MapJavaProxy.class );
proxyClass.includeModule( runtime.getEnumerable() );
}
else {
proxyClass.setAllocator( superClass.getAllocator() );
}
proxyClass.defineAnnotatedMethods( JavaProxy.ClassMethods.class );
if ( invokeInherited ) proxyClass.inherit(superClass);
Initializer.setupProxyClass(runtime, javaClass, proxyClass);
return proxyClass;
}
public static class ByteArrayProxyMethods {
@JRubyMethod
public static IRubyObject to_s(ThreadContext context, IRubyObject self) {
final Encoding ascii8bit = context.runtime.getEncodingService().getAscii8bitEncoding();
ByteList bytes = new ByteList((byte[]) ((ArrayJavaProxy) self).getObject(), ascii8bit);
return RubyString.newStringLight(context.runtime, bytes);
}
}
@Deprecated
public static IRubyObject concrete_proxy_inherited(final IRubyObject clazz, final IRubyObject subclazz) {
return invokeProxyClassInherited(clazz.getRuntime().getCurrentContext(), clazz, subclazz);
}
private static IRubyObject invokeProxyClassInherited(final ThreadContext context,
final IRubyObject clazz, final IRubyObject subclazz) {
final JavaSupport javaSupport = context.runtime.getJavaSupport();
RubyClass javaProxyClass = javaSupport.getJavaProxyClass().getMetaClass();
Helpers.invokeAs(context, javaProxyClass, clazz, "inherited", subclazz, Block.NULL_BLOCK);
if ( ! ( subclazz instanceof RubyClass ) ) {
throw context.runtime.newTypeError(subclazz, context.runtime.getClassClass());
}
setupJavaSubclass(context, (RubyClass) subclazz);
return context.nil;
}
private static void setupJavaSubclass(final ThreadContext context, final RubyClass subclass) {
subclass.setInstanceVariable("@java_proxy_class", context.nil);
subclass.setCacheProxy(true);
final RubyClass subclassSingleton = subclass.getSingletonClass();
subclassSingleton.addReadWriteAttribute(context, "java_proxy_class");
subclassSingleton.addMethod("java_interfaces", new JavaMethodZero(subclassSingleton, PUBLIC, "java_interfaces") {
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
IRubyObject javaInterfaces = self.getInstanceVariables().getInstanceVariable("@java_interfaces");
if (javaInterfaces != null) return javaInterfaces.dup();
return context.nil;
}
});
subclass.addMethod("__jcreate!", new JCreateMethod(subclassSingleton));
}
public static class JCreateMethod extends JavaMethodN implements CallableSelector.CallableCache<JavaProxyConstructor> {
private final NonBlockingHashMapLong<JavaProxyConstructor> cache = new NonBlockingHashMapLong<>(8);
JCreateMethod(RubyModule cls) {
super(cls, PUBLIC, "__jcreate!");
}
private static JavaProxyClass getProxyClass(final IRubyObject self) {
final RubyClass metaClass = self.getMetaClass();
IRubyObject proxyClass = metaClass.getInstanceVariable("@java_proxy_class");
if (proxyClass == null || proxyClass.isNil()) {
proxyClass = JavaProxyClass.getProxyClass(self.getRuntime(), metaClass);
metaClass.setInstanceVariable("@java_proxy_class", proxyClass);
}
return (JavaProxyClass) proxyClass;
}
@Override
public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
return call(context, self, clazz, name, IRubyObject.NULL_ARRAY);
}
@Override
public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) {
final JavaProxyConstructor[] constructors = getProxyClass(self).getConstructors();
final JavaProxyConstructor matching;
switch (constructors.length) {
case 1: matching = matchConstructor0ArityOne(context, constructors, arg0); break;
default: matching = matchConstructorArityOne(context, constructors, arg0);
}
JavaObject newObject = matching.newInstance(self, arg0);
return JavaUtilities.set_java_object(self, self, newObject);
}
@Override
public final IRubyObject call(final ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args) {
final int arity = args.length;
final JavaProxyConstructor[] constructors = getProxyClass(self).getConstructors();
final JavaProxyConstructor matching;
switch (constructors.length) {
case 1: matching = matchConstructor0(context, constructors, arity, args); break;
default: matching = matchConstructor(context, constructors, arity, args);
}
JavaObject newObject = matching.newInstance(self, args);
return JavaUtilities.set_java_object(self, self, newObject);
}
private JavaProxyConstructor matchConstructor0ArityOne(final ThreadContext context,
final JavaProxyConstructor[] constructors, final IRubyObject arg0) {
JavaProxyConstructor forArity = checkCallableForArity(1, constructors, 0);
if ( forArity == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
final JavaProxyConstructor matching = CallableSelector.matchingCallableArityOne(
context.runtime, this, new JavaProxyConstructor[] { forArity }, arg0
);
if ( matching == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
return matching;
}
private JavaProxyConstructor matchConstructor0(final ThreadContext context,
final JavaProxyConstructor[] constructors, final int arity, final IRubyObject[] args) {
JavaProxyConstructor forArity = checkCallableForArity(arity, constructors, 0);
if ( forArity == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
final JavaProxyConstructor matching = CallableSelector.matchingCallableArityN(
context.runtime, this, new JavaProxyConstructor[] { forArity }, args
);
if ( matching == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
return matching;
}
private JavaProxyConstructor matchConstructorArityOne(final ThreadContext context,
final JavaProxyConstructor[] constructors, final IRubyObject arg0) {
ArrayList<JavaProxyConstructor> forArity = findCallablesForArity(1, constructors);
if ( forArity.size() == 0 ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
final JavaProxyConstructor matching = CallableSelector.matchingCallableArityOne(
context.runtime, this, forArity.toArray(new JavaProxyConstructor[forArity.size()]), arg0
);
if ( matching == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
return matching;
}
private JavaProxyConstructor matchConstructor(final ThreadContext context,
final JavaProxyConstructor[] constructors, final int arity, final IRubyObject... args) {
ArrayList<JavaProxyConstructor> forArity = findCallablesForArity(arity, constructors);
if ( forArity.size() == 0 ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
final JavaProxyConstructor matching = CallableSelector.matchingCallableArityN(
context.runtime, this, forArity.toArray(new JavaProxyConstructor[forArity.size()]), args
);
if ( matching == null ) {
throw context.runtime.newArgumentError("wrong number of arguments for constructor");
}
return matching;
}
public final JavaProxyConstructor getSignature(int signatureCode) {
return cache.get(signatureCode);
}
public final void putSignature(int signatureCode, JavaProxyConstructor callable) {
cache.put(signatureCode, callable);
}
}
static <T extends ParameterTypes> ArrayList<T> findCallablesForArity(final int arity, final T[] callables) {
final ArrayList<T> forArity = new ArrayList<>(callables.length);
for ( int i = 0; i < callables.length; i++ ) {
final T found = checkCallableForArity(arity, callables, i);
if ( found != null ) forArity.add(found);
}
return forArity;
}
private static <T extends ParameterTypes> T checkCallableForArity(final int arity, final T[] callables, final int index) {
final T callable = callables[index];
final int callableArity = callable.getArity();
if ( callableArity == arity ) return callable;
if ( callable.isVarArgs() && callableArity - 1 <= arity ) {
return callable;
}
return null;
}
private static void addToJavaPackageModule(RubyModule proxyClass) {
final Ruby runtime = proxyClass.getRuntime();
final Class<?> clazz = (Class<?>)proxyClass.dataGetStruct();
final String fullName;
if ( ( fullName = clazz.getName() ) == null ) return;
final RubyModule parentModule; final String className;
if ( fullName.indexOf('$') != -1 ) {
return;
}
else {
final int endPackage = fullName.lastIndexOf('.');
String packageString = endPackage < 0 ? "" : fullName.substring(0, endPackage);
parentModule = getJavaPackageModule(runtime, packageString);
className = parentModule == null ? fullName : fullName.substring(endPackage + 1);
}
if ( parentModule != null &&
( IdUtil.isConstant(className) || parentModule instanceof JavaPackage ) ) {
if (parentModule.getConstantAt(className) == null) {
parentModule.setConstant(className, proxyClass);
}
}
}
public static RubyModule getJavaPackageModule(final Ruby runtime, final Package pkg) {
return getJavaPackageModule(runtime, pkg == null ? "" : pkg.getName());
}
public static RubyModule getJavaPackageModule(final Ruby runtime, final String packageString) {
final String packageName; final int length;
if ( ( length = packageString.length() ) == 0 ) {
packageName = "Default";
}
else {
StringBuilder name = new StringBuilder(length);
for (int start = 0, offset; start < length; start = offset + 1) {
offset = packageString.indexOf('.', start);
if ( offset == -1 ) offset = length;
name.append( Character.toUpperCase(packageString.charAt(start)) )
.append( packageString.substring(start + 1, offset) );
}
packageName = name.toString();
}
final RubyModule javaModule = runtime.getJavaSupport().getJavaModule();
final IRubyObject packageModule = javaModule.getConstantAt(packageName);
if ( packageModule == null ) {
return createPackageModule(runtime, javaModule, packageName, packageString);
}
if ( packageModule instanceof RubyModule ) {
return (RubyModule) packageModule;
}
return null;
}
private static RubyModule createPackageModule(final Ruby runtime,
final RubyModule parentModule, final String name, final String packageString) {
final RubyModule packageModule = JavaPackage.newPackage(runtime, packageString, parentModule);
synchronized (parentModule) {
final IRubyObject packageAlreadySet = parentModule.fetchConstant(name);
if ( packageAlreadySet != null ) {
return (RubyModule) packageAlreadySet;
}
parentModule.setConstant(name.intern(), packageModule);
}
return packageModule;
}
private static final Pattern CAMEL_CASE_PACKAGE_SPLITTER = Pattern.compile("([a-z0-9_]+)([A-Z])");
private static RubyModule getPackageModule(final Ruby runtime, final String name) {
final RubyModule javaModule = runtime.getJavaSupport().getJavaModule();
final IRubyObject packageModule = javaModule.getConstantAt(name);
if ( packageModule instanceof RubyModule ) return (RubyModule) packageModule;
final String packageName;
if ( "Default".equals(name) ) packageName = "";
else {
Matcher match = CAMEL_CASE_PACKAGE_SPLITTER.matcher(name);
packageName = match.replaceAll("$1.$2").toLowerCase();
}
return createPackageModule(runtime, javaModule, name, packageName);
}
public static RubyModule get_package_module(final IRubyObject self, final IRubyObject name) {
return getPackageModule(self.getRuntime(), name.asJavaString());
}
public static IRubyObject get_package_module_dot_format(final IRubyObject self,
final IRubyObject dottedName) {
final Ruby runtime = self.getRuntime();
RubyModule module = getJavaPackageModule(runtime, dottedName.asJavaString());
return module == null ? runtime.getNil() : module;
}
static RubyModule getProxyOrPackageUnderPackage(final ThreadContext context,
final RubyModule parentPackage, final String name, final boolean cacheMethod) {
final Ruby runtime = context.runtime;
if ( name.length() == 0 ) {
throw runtime.newArgumentError("empty class or package name");
}
final String fullName = JavaPackage.buildPackageName(parentPackage, name).toString();
final RubyModule result;
if ( ! Character.isUpperCase( name.charAt(0) ) ) {
checkJavaReservedNames(runtime, name, false);
RubyModule proxyClass = getProxyClassOrNull(runtime, fullName);
if ( proxyClass != null ) result = proxyClass;
else {
final RubyModule packageModule = getJavaPackageModule(runtime, fullName);
if ( packageModule == null ) return null;
result = packageModule;
}
}
else {
try {
final RubyModule javaClass = getProxyClassOrNull(runtime, fullName);
if ( javaClass != null ) result = javaClass;
else {
if ( allowUppercasePackageNames(runtime) ) {
result = getJavaPackageModule(runtime, fullName);
if ( result == null ) {
throw runtime.newNameError("missing class (or package) name (`" + fullName + "')", fullName);
}
}
else {
throw runtime.newNameError("missing class name (`" + fullName + "')", fullName);
}
}
}
catch (RuntimeException e) {
if ( e instanceof RaiseException ) throw e;
if (runtime.isDebug()) e.printStackTrace();
throw runtime.newNameError("missing class or uppercase package name (`" + fullName + "'), caused by " + e.getMessage(), fullName);
}
}
if ( cacheMethod ) bindJavaPackageOrClassMethod(parentPackage, name, result);
return result;
}
private static boolean allowUppercasePackageNames(final Ruby runtime) {
return runtime.getInstanceConfig().getAllowUppercasePackageNames();
}
private static void checkJavaReservedNames(final Ruby runtime, final String name,
final boolean allowPrimitives) {
if ( ! allowPrimitives && JavaClass.isPrimitiveName(name) ) {
throw runtime.newArgumentError("illegal package name component: " + name);
}
}
private static RubyModule getProxyClassOrNull(final Ruby runtime, final String className) {
return getProxyClassOrNull(runtime, className, true);
}
private static RubyModule getProxyClassOrNull(final Ruby runtime, final String className,
final boolean initJavaClass) {
final Class<?> clazz;
try {
synchronized (Java.class) {
clazz = runtime.getJavaSupport().loadJavaClass(className);
}
}
catch (ExceptionInInitializerError ex) {
throw runtime.newNameError("cannot initialize Java class " + className + ' ' + '(' + ex + ')', className, ex, false);
}
catch (UnsupportedClassVersionError ex) {
String type = ex.getClass().getName();
String msg = ex.getLocalizedMessage();
if ( msg != null ) {
final String unMajorMinorVersion = "unsupported major.minor version";
int idx = msg.indexOf(unMajorMinorVersion);
if (idx > 0) {
idx += unMajorMinorVersion.length();
idx = mapMajorMinorClassVersionToJavaVersion(msg, idx);
if ( idx > 0 ) msg = "needs Java " + idx + " (" + type + ": " + msg + ')';
else msg = '(' + type + ": " + msg + ')';
}
}
else msg = '(' + type + ')';
throw runtime.newNameError("cannot link Java class " + className + ' ' + msg, className, ex, false);
}
catch (NoClassDefFoundError | ClassNotFoundException ncdfe) {
return null;
}
catch (LinkageError ex) {
throw runtime.newNameError("cannot link Java class " + className + ' ' + '(' + ex + ')', className, ex, false);
}
catch (SecurityException ex) {
throw runtime.newSecurityError(ex.getLocalizedMessage());
}
if ( initJavaClass ) {
return getProxyClass(runtime, JavaClass.get(runtime, clazz));
}
return getProxyClass(runtime, clazz);
}
private static int mapMajorMinorClassVersionToJavaVersion(String msg, final int offset) {
int end;
if ( ( end = msg.indexOf('.', offset) ) == -1 ) end = msg.length();
msg = msg.substring(offset, end).trim();
try {
return Integer.parseInt(msg) - 50 + 6;
}
catch (RuntimeException ignore) { return 0; }
}
public static IRubyObject get_proxy_or_package_under_package(final ThreadContext context,
final IRubyObject self, final IRubyObject parentPackage, final IRubyObject name) {
final Ruby runtime = context.runtime;
if ( ! ( parentPackage instanceof RubyModule ) ) {
throw runtime.newTypeError(parentPackage, runtime.getModule());
}
final RubyModule result = getProxyOrPackageUnderPackage(context, (RubyModule) parentPackage, name.asJavaString(), true);
return result != null ? result : context.nil;
}
private static RubyModule getTopLevelProxyOrPackage(final Ruby runtime,
final String name, final boolean cacheMethod) {
if ( name.length() == 0 ) {
throw runtime.newArgumentError("empty class or package name");
}
final RubyModule result;
if ( Character.isLowerCase( name.charAt(0) ) ) {
RubyModule proxyClass = getProxyClassOrNull(runtime, name);
if ( proxyClass != null ) result = proxyClass;
else {
checkJavaReservedNames(runtime, name, true);
final RubyModule packageModule = getJavaPackageModule(runtime, name);
if ( packageModule == null ) return null;
result = packageModule;
}
}
else {
RubyModule javaClass = getProxyClassOrNull(runtime, name);
if ( javaClass != null ) result = javaClass;
else {
result = getPackageModule(runtime, name);
}
}
if ( cacheMethod ) bindJavaPackageOrClassMethod(runtime, name, result);
return result;
}
private static boolean bindJavaPackageOrClassMethod(final Ruby runtime, final String name,
final RubyModule packageOrClass) {
final RubyModule javaPackage = runtime.getJavaSupport().getJavaModule();
return bindJavaPackageOrClassMethod(javaPackage, name, packageOrClass);
}
private static boolean bindJavaPackageOrClassMethod(final RubyModule parentPackage,
final String name, final RubyModule packageOrClass) {
if ( parentPackage.getMetaClass().isMethodBound(name, false) ) {
return false;
}
final RubyClass singleton = parentPackage.getSingletonClass();
singleton.addMethod(name.intern(), new JavaAccessor(singleton, packageOrClass, parentPackage, name));
return true;
}
private static class JavaAccessor extends org.jruby.internal.runtime.methods.JavaMethod {
private final RubyModule packageOrClass;
private final RubyModule parentPackage;
JavaAccessor(final RubyClass singleton, final RubyModule packageOrClass, final RubyModule parentPackage, final String name) {
super(singleton, PUBLIC, name);
this.parentPackage = parentPackage; this.packageOrClass = packageOrClass;
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
if ( args.length != 0 ) {
throw JavaPackage.packageMethodArgumentMismatch(context.runtime, parentPackage, name, args.length);
}
return call(context, self, clazz, name);
}
@Override
public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
return this.packageOrClass;
}
@Override
public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
return this.packageOrClass;
}
@Override
public Arity getArity() { return Arity.noArguments(); }
}
static final class ProcToInterface extends org.jruby.internal.runtime.methods.DynamicMethod {
ProcToInterface(final RubyClass singletonClass) {
super(singletonClass, PUBLIC, "call");
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
final IRubyObject[] newArgs;
switch( args.length ) {
case 1 : newArgs = IRubyObject.NULL_ARRAY; break;
case 2 : newArgs = new IRubyObject[] { args[1] }; break;
case 3 : newArgs = new IRubyObject[] { args[1], args[2] }; break;
default : newArgs = new IRubyObject[ args.length - 1 ];
System.arraycopy(args, 1, newArgs, 0, newArgs.length);
}
return callProc(context, self, newArgs);
}
private static IRubyObject callProc(ThreadContext context, IRubyObject self, IRubyObject[] procArgs) {
if ( ! ( self instanceof RubyProc ) ) {
throw context.runtime.newTypeError("interface impl method_missing for block used with non-Proc object");
}
return ((RubyProc) self).call(context, procArgs);
}
@Override
public DynamicMethod dup() {
return this;
}
final ConcreteMethod getConcreteMethod(String name) { return new ConcreteMethod(name); }
final class ConcreteMethod extends org.jruby.internal.runtime.methods.JavaMethod {
ConcreteMethod(String name) {
super(ProcToInterface.this.implementationClass, Visibility.PUBLIC, name);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, Block block) {
return ProcToInterface.this.callProc(context, self, IRubyObject.NULL_ARRAY);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, Block block) {
return ProcToInterface.this.callProc(context, self, new IRubyObject[]{arg0});
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) {
return ProcToInterface.this.callProc(context, self, new IRubyObject[]{arg0, arg1});
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
return ProcToInterface.this.callProc(context, self, new IRubyObject[]{arg0, arg1, arg2});
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String name, IRubyObject[] args, Block block) {
return ProcToInterface.this.callProc(context, self, args);
}
}
}
private static RubyModule getProxyUnderClass(final ThreadContext context,
final RubyModule enclosingClass, final String name) {
final Ruby runtime = context.runtime;
if ( name.length() == 0 ) throw runtime.newArgumentError("empty class name");
Class<?> enclosing = JavaClass.getJavaClass(context, enclosingClass);
if (enclosing == null) return null;
final String fullName = enclosing.getName() + '$' + name;
final RubyModule result = getProxyClassOrNull(runtime, fullName);
return result;
}
public static IRubyObject get_inner_class(final ThreadContext context,
final RubyModule self, final IRubyObject name) {
final String constName = name.asJavaString();
final RubyModule innerClass = getProxyUnderClass(context, self, constName);
if ( innerClass == null ) {
return Helpers.invokeSuper(context, self, name, Block.NULL_BLOCK);
}
return cacheConstant(self, constName, innerClass, true);
}
@JRubyMethod(meta = true)
public static IRubyObject const_missing(final ThreadContext context,
final IRubyObject self, final IRubyObject name) {
final Ruby runtime = context.runtime;
final String constName = name.asJavaString();
final RubyModule packageOrClass = getTopLevelProxyOrPackage(runtime, constName, false);
if ( packageOrClass == null ) return context.nil;
return cacheConstant((RubyModule) self, constName, packageOrClass, false);
}
private static RubyModule cacheConstant(final RubyModule owner,
final String constName, final RubyModule packageOrClass, final boolean hidden) {
if ( packageOrClass != null ) {
synchronized (owner) {
final IRubyObject alreadySet = owner.fetchConstant(constName);
if ( alreadySet != null ) return (RubyModule) alreadySet;
owner.setConstant(constName, packageOrClass, hidden);
}
return packageOrClass;
}
return null;
}
@JRubyMethod(name = "method_missing", meta = true, required = 1)
public static IRubyObject method_missing(ThreadContext context, final IRubyObject self,
final IRubyObject name) {
final RubyModule result = getTopLevelProxyOrPackage(context.runtime, name.asJavaString(), true);
if ( result != null ) return result;
return context.nil;
}
@JRubyMethod(name = "method_missing", meta = true, rest = true)
public static IRubyObject method_missing(ThreadContext context, final IRubyObject self,
final IRubyObject[] args) {
final IRubyObject name = args[0];
if ( args.length > 1 ) {
final int count = args.length - 1;
throw context.runtime.newArgumentError("Java does not have a method `"+ name +"' with " + count + " arguments");
}
return method_missing(context, self, name);
}
public static IRubyObject get_top_level_proxy_or_package(final ThreadContext context,
final IRubyObject self, final IRubyObject name) {
final RubyModule result = getTopLevelProxyOrPackage(context.runtime, name.asJavaString(), true);
return result != null ? result : context.nil;
}
public static IRubyObject wrap(Ruby runtime, IRubyObject java_object) {
return getInstance(runtime, ((JavaObject) java_object).getValue());
}
@Deprecated
@JRubyMethod(module = true, visibility = PRIVATE)
public static IRubyObject java_to_ruby(IRubyObject recv, IRubyObject object, Block unusedBlock) {
try {
return JavaUtil.java_to_ruby(recv.getRuntime(), object);
} catch (RuntimeException e) {
recv.getRuntime().getJavaSupport().handleNativeException(e, null);
return recv.getRuntime().getNil();
}
}
@Deprecated
@JRubyMethod(module = true, visibility = PRIVATE)
public static IRubyObject ruby_to_java(final IRubyObject recv, IRubyObject object, Block unusedBlock) {
return JavaUtil.ruby_to_java(recv, object, unusedBlock);
}
@Deprecated
@JRubyMethod(module = true, visibility = PRIVATE)
public static IRubyObject java_to_primitive(IRubyObject recv, IRubyObject object, Block unusedBlock) {
return JavaUtil.java_to_primitive(recv, object, unusedBlock);
}
@JRubyMethod(required = 2, module = true, visibility = PRIVATE)
public static IRubyObject new_proxy_instance2(IRubyObject recv, final IRubyObject wrapper, IRubyObject ifcs, Block block) {
IRubyObject[] javaClasses = ((RubyArray)ifcs).toJavaArray();
Class[] interfaces = new Class[javaClasses.length];
for (int i = 0; i < javaClasses.length; i++) {
if (!(javaClasses[i] instanceof JavaClass) || !((JavaClass) javaClasses[i]).interface_p().isTrue()) {
throw recv.getRuntime().newArgumentError("Java interface expected. got: " + javaClasses[i]);
}
interfaces[i] = ((JavaClass) javaClasses[i]).javaClass();
}
return newInterfaceImpl(wrapper, interfaces);
}
public static JavaObject newInterfaceImpl(final IRubyObject wrapper, Class[] interfaces) {
final Ruby runtime = wrapper.getRuntime();
final int length = interfaces.length;
switch ( length ) {
case 1 :
interfaces = new Class[] { interfaces[0], RubyObjectHolderProxy.class };
case 2 :
interfaces = new Class[] { interfaces[0], interfaces[1], RubyObjectHolderProxy.class };
default :
interfaces = ArraySupport.newCopy(interfaces, length + 1);
interfaces[length] = RubyObjectHolderProxy.class;
}
final RubyClass wrapperClass = wrapper.getMetaClass();
final boolean isProc = wrapperClass.isSingleton() && wrapperClass.getRealClass() == runtime.getProc();
final JRubyClassLoader jrubyClassLoader = runtime.getJRubyClassLoader();
if ( RubyInstanceConfig.INTERFACES_USE_PROXY ) {
return JavaObject.wrap(runtime, newProxyInterfaceImpl(wrapper, interfaces, jrubyClassLoader));
}
final ClassDefiningClassLoader classLoader;
int interfacesHashCode = interfacesHashCode(interfaces);
if ( isProc ) {
interfacesHashCode = 31 * interfacesHashCode + runtime.getProc().hashCode();
classLoader = jrubyClassLoader;
}
else {
interfacesHashCode = 31 * interfacesHashCode + wrapperClass.getRealClass().hashCode();
classLoader = new OneShotClassLoader(jrubyClassLoader);
}
final String implClassName = "org.jruby.gen.InterfaceImpl" + Math.abs(interfacesHashCode);
Class<?> proxyImplClass;
try {
proxyImplClass = Class.forName(implClassName, true, jrubyClassLoader);
}
catch (ClassNotFoundException ex) {
proxyImplClass = RealClassGenerator.createOldStyleImplClass(interfaces, wrapperClass, runtime, implClassName, classLoader);
}
try {
Constructor<?> proxyConstructor = proxyImplClass.getConstructor(IRubyObject.class);
return JavaObject.wrap(runtime, proxyConstructor.newInstance(wrapper));
}
catch (InvocationTargetException e) {
throw mapGeneratedProxyException(runtime, e);
}
catch (ReflectiveOperationException e) {
throw mapGeneratedProxyException(runtime, e);
}
}
private static Object newProxyInterfaceImpl(final IRubyObject wrapper, final Class[] interfaces, final ClassLoader loader) {
return Proxy.newProxyInstance(loader, interfaces, new InterfaceProxyHandler(wrapper, interfaces));
}
private static final class InterfaceProxyHandler implements InvocationHandler {
final IRubyObject wrapper;
private final String[] ifaceNames;
InterfaceProxyHandler(final IRubyObject wrapper, final Class[] interfaces) {
this.wrapper = wrapper;
this.ifaceNames = new String[interfaces.length];
for ( int i = 0; i < interfaces.length; i++ ) {
ifaceNames[i] = interfaces[i].getName();
}
Arrays.sort(ifaceNames);
}
public Object invoke(Object proxy, Method method, Object[] nargs) throws Throwable {
final String methodName = method.getName();
final int length = nargs == null ? 0 : nargs.length;
switch ( methodName ) {
case "toString" :
if ( length == 0 && ! wrapper.respondsTo("toString") ) {
return proxyToString(proxy);
}
break;
case "hashCode" :
if ( length == 0 && ! wrapper.respondsTo("hashCode") ) {
return proxyHashCode(proxy);
}
break;
case "equals" :
if ( length == 1 && ! wrapper.respondsTo("equals") ) {
Class[] parameterTypes = getParameterTypes(method);
if ( parameterTypes[0] == Object.class ) return proxyEquals(proxy, nargs[0]);
}
break;
case "__ruby_object" :
if ( length == 0 ) return wrapper;
break;
}
final Ruby runtime = wrapper.getRuntime();
final ThreadContext context = runtime.getCurrentContext();
switch ( length ) {
case 0 :
return Helpers.invoke(context, wrapper, methodName).toJava(method.getReturnType());
case 1 :
IRubyObject arg = JavaUtil.convertJavaToUsableRubyObject(runtime, nargs[0]);
return Helpers.invoke(context, wrapper, methodName, arg).toJava(method.getReturnType());
default :
IRubyObject[] args = JavaUtil.convertJavaArrayToRuby(runtime, nargs);
return Helpers.invoke(context, wrapper, methodName, args).toJava(method.getReturnType());
}
}
final String proxyToString(final Object proxy) {
return proxy.getClass().getName() + '{' + this + '}';
}
final boolean proxyEquals(final Object proxy, final Object otherProxy) {
if ( proxy == otherProxy ) return true;
if ( otherProxy == null ) return false;
if ( Proxy.isProxyClass(otherProxy.getClass()) ) {
InvocationHandler other = Proxy.getInvocationHandler(otherProxy);
if ( other instanceof InterfaceProxyHandler ) {
InterfaceProxyHandler that = (InterfaceProxyHandler) other;
if ( this.wrapper != that.wrapper ) return false;
return Arrays.equals(this.ifaceNames, that.ifaceNames);
}
}
return false;
}
final int proxyHashCode(final Object proxy) {
int hash = 11 * this.wrapper.hashCode();
for ( String iface : this.ifaceNames ) {
hash = 31 * hash + iface.hashCode();
}
return hash;
}
private Map<Method, Class[]> parameterTypeCache;
private Class[] getParameterTypes(final Method method) {
Map<Method, Class[]> parameterTypeCache = this.parameterTypeCache;
if (parameterTypeCache == null) {
parameterTypeCache = new ConcurrentHashMap<Method, Class[]>(4);
this.parameterTypeCache = parameterTypeCache;
}
Class[] parameterTypes = parameterTypeCache.get(method);
if (parameterTypes == null) {
parameterTypes = method.getParameterTypes();
parameterTypeCache.put(method, parameterTypes);
}
return parameterTypes;
}
}
@SuppressWarnings("unchecked")
public static Class generateRealClass(final RubyClass clazz) {
final Ruby runtime = clazz.getRuntime();
final Class[] interfaces = getInterfacesFromRubyClass(clazz);
int interfacesHashCode = interfacesHashCode(interfaces);
interfacesHashCode = 31 * interfacesHashCode + clazz.hashCode();
String implClassName = Constants.GENERATED_PACKAGE;
if (clazz.getBaseName() == null) {
implClassName += "anon_class" + Math.abs(System.identityHashCode(clazz)) + '_' + Math.abs(interfacesHashCode);
} else {
implClassName += StringSupport.replaceAll(clazz.getName(), "::", "$$").toString() + '_' + Math.abs(interfacesHashCode);
}
Class<? extends IRubyObject> proxyImplClass;
try {
proxyImplClass = (Class<? extends IRubyObject>) Class.forName(implClassName, true, runtime.getJRubyClassLoader());
}
catch (ClassNotFoundException ex) {
Class<? extends IRubyObject> superClass = clazz.getSuperClass().getRealClass().getReifiedClass();
if ( superClass == null ) superClass = RubyObject.class;
proxyImplClass = RealClassGenerator.createRealImplClass(superClass, interfaces, clazz, runtime, implClassName);
if ( NEW_STYLE_EXTENSION &&
! ( RubyBasicObject.class.isAssignableFrom(proxyImplClass) || clazz.getMethods().containsKey("initialize") ) ) {
clazz.addMethod("initialize", new DummyInitialize(clazz));
}
}
clazz.setReifiedClass(proxyImplClass);
clazz.setRubyClassAllocator(proxyImplClass);
return proxyImplClass;
}
private static final class DummyInitialize extends JavaMethodZero {
DummyInitialize(final RubyClass clazz) { super(clazz, PRIVATE, "initialize"); }
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
return context.nil;
}
}
public static Constructor getRealClassConstructor(final Ruby runtime, Class<?> proxyImplClass) {
try {
return proxyImplClass.getConstructor(Ruby.class, RubyClass.class);
}
catch (NoSuchMethodException e) {
throw mapGeneratedProxyException(runtime, e);
}
}
public static IRubyObject constructProxy(Ruby runtime, Constructor proxyConstructor, RubyClass clazz) {
try {
return (IRubyObject) proxyConstructor.newInstance(runtime, clazz);
}
catch (InvocationTargetException e) {
throw mapGeneratedProxyException(runtime, e);
}
catch (ReflectiveOperationException e) {
throw mapGeneratedProxyException(runtime, e);
}
}
private static RaiseException mapGeneratedProxyException(final Ruby runtime, final ReflectiveOperationException e) {
RaiseException ex = runtime.newTypeError("Exception instantiating generated interface impl:\n" + e);
ex.initCause(e);
return ex;
}
private static RaiseException mapGeneratedProxyException(final Ruby runtime, final InvocationTargetException e) {
RaiseException ex = runtime.newTypeError("Exception instantiating generated interface impl:\n" + e.getTargetException());
ex.initCause(e);
return ex;
}
public static IRubyObject allocateProxy(Object javaObject, RubyClass clazz) {
final Ruby runtime = clazz.getRuntime();
if ( clazz.getSuperClass() == runtime.getJavaSupport().getArrayProxyClass() ) {
return new ArrayJavaProxy(runtime, clazz, javaObject, JavaUtil.getJavaConverter(javaObject.getClass().getComponentType()));
}
final IRubyObject proxy = clazz.allocate();
if ( proxy instanceof JavaProxy ) {
((JavaProxy) proxy).setObject(javaObject);
}
else {
JavaObject wrappedObject = JavaObject.wrap(runtime, javaObject);
proxy.dataWrapStruct(wrappedObject);
}
return proxy;
}
public static IRubyObject wrapJavaObject(Ruby runtime, Object object) {
return allocateProxy(object, getProxyClassForObject(runtime, object));
}
@SuppressWarnings("unchecked")
public static Class[] getInterfacesFromRubyClass(RubyClass klass) {
Set<Class> interfaces = new HashSet<Class>();
while (klass != null) {
IRubyObject maybeInterfaces = klass.getInstanceVariables().getInstanceVariable("@java_interfaces");
if (maybeInterfaces instanceof RubyArray) {
final RubyArray moreInterfaces = (RubyArray) maybeInterfaces;
if ( ! moreInterfaces.isFrozen() ) moreInterfaces.setFrozen(true);
interfaces.addAll(moreInterfaces);
}
klass = klass.getSuperClass();
}
return interfaces.toArray(new Class[interfaces.size()]);
}
private static int interfacesHashCode(Class[] a) {
if (a == null) {
return 0;
}
int result = 1;
for (Class element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
public static Method getFunctionalInterfaceMethod(final Class<?> iface) {
assert iface.isInterface();
Method single = null;
for ( final Method method : iface.getMethods() ) {
final int mod = method.getModifiers();
if ( Modifier.isStatic(mod) ) continue;
if ( Modifier.isAbstract(mod) ) {
try {
Object.class.getMethod(method.getName(), method.getParameterTypes());
continue;
}
catch (NoSuchMethodException e) { }
catch (SecurityException e) {
}
}
else continue;
if ( single == null ) single = method;
else return null;
}
return single;
}
public static <T extends AccessibleObject & Member> boolean trySetAccessible(T member) {
return Modules.trySetAccessible(member, Java.class);
}
}