diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java index cd4ff4531fd..e873665786d 100644 --- a/core/src/main/java/org/jruby/RubyClass.java +++ b/core/src/main/java/org/jruby/RubyClass.java @@ -144,23 +144,32 @@ public void setAllocator(ObjectAllocator allocator) { * Set a reflective allocator that calls a no-arg constructor on the given * class. * - * @param cls The class on which to call the default constructor to allocate + * @param clazz The class on which to call the default constructor to allocate */ @SuppressWarnings("unchecked") - public void setClassAllocator(final Class cls) { + public void setClassAllocator(final Class clazz) { + final Constructor constructor; + try { + constructor = clazz.getConstructor(); + } catch (NoSuchMethodException nsme) { + throw new RuntimeException(nsme); + } + this.allocator = (runtime, klazz) -> { try { - RubyBasicObject object = (RubyBasicObject)cls.getConstructor().newInstance(); + RubyBasicObject object = (RubyBasicObject) constructor.newInstance(); object.setMetaClass(klazz); return object; - } catch (InstantiationException | InvocationTargetException ie) { - throw runtime.newTypeError("could not allocate " + cls + " with default constructor:\n" + ie); - } catch (IllegalAccessException | NoSuchMethodException iae) { - throw runtime.newSecurityError("could not allocate " + cls + " due to inaccessible default constructor:\n" + iae); + } catch (InvocationTargetException e) { + throw newTypeError(runtime, "could not allocate " + clazz + " with default constructor:\n" + e.getTargetException(), e); + } catch (InstantiationException e) { + throw newTypeError(runtime, "could not allocate " + clazz + " with default constructor:\n" + e, e); + } catch (IllegalAccessException e) { + throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible default constructor:\n" + e); } }; - this.reifiedClass = (Class) cls; + this.reifiedClass = (Class) clazz; } /** @@ -171,25 +180,26 @@ public void setClassAllocator(final Class cls) { */ @SuppressWarnings("unchecked") public void setRubyClassAllocator(final Class clazz) { + final Constructor constructor; try { - final Constructor constructor = clazz.getConstructor(Ruby.class, RubyClass.class); - - this.allocator = (runtime, klazz) -> { - try { - return constructor.newInstance(runtime, klazz); - } catch (InvocationTargetException ite) { - throw runtime.newTypeError("could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + ite); - } catch (InstantiationException ie) { - throw runtime.newTypeError("could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + ie); - } catch (IllegalAccessException iae) { - throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) constructor:\n" + iae); - } - }; - - this.reifiedClass = (Class) clazz; + constructor = clazz.getConstructor(Ruby.class, RubyClass.class); } catch (NoSuchMethodException nsme) { throw new RuntimeException(nsme); } + + this.allocator = (runtime, klazz) -> { + try { + return constructor.newInstance(runtime, klazz); + } catch (InvocationTargetException e) { + throw newTypeError(runtime, "could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + e.getTargetException(), e); + } catch (InstantiationException e) { + throw newTypeError(runtime, "could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + e, e); + } catch (IllegalAccessException e) { + throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) constructor:\n" + e); + } + }; + + this.reifiedClass = (Class) clazz; } /** @@ -203,23 +213,30 @@ public void setRubyClassAllocator(final Class clazz) { *

Note: Used with new concrete extension.

*/ public void setRubyStaticAllocator(final Class clazz) { + final Method method; try { - final Method method = clazz.getDeclaredMethod("__allocate__", Ruby.class, RubyClass.class); - - this.allocator = (runtime, klazz) -> { - try { - return (IRubyObject) method.invoke(null, runtime, klazz); - } catch (InvocationTargetException ite) { - throw runtime.newTypeError("could not allocate " + clazz + " with (Ruby, RubyClass) constructor:\n" + ite); - } catch (IllegalAccessException iae) { - throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) constructor:\n" + iae); - } - }; - - this.reifiedClass = (Class) clazz; + method = clazz.getDeclaredMethod("__allocate__", Ruby.class, RubyClass.class); } catch (NoSuchMethodException nsme) { throw new RuntimeException(nsme); } + + this.allocator = (runtime, klazz) -> { + try { + return (IRubyObject) method.invoke(null, runtime, klazz); + } catch (InvocationTargetException e) { + throw newTypeError(runtime, "could not allocate " + clazz + " with (Ruby, RubyClass) method:\n" + e.getTargetException(), e); + } catch (IllegalAccessException e) { + throw runtime.newSecurityError("could not allocate " + clazz + " due to inaccessible (Ruby, RubyClass) method:\n" + e); + } + }; + + this.reifiedClass = (Class) clazz; + } + + private static RaiseException newTypeError(final Ruby runtime, final String msg, final Exception e) { + RaiseException error = runtime.newTypeError(msg); + error.initCause(e); + return error; } @JRubyMethod(name = "allocate") @@ -936,22 +953,14 @@ public IRubyObject newInstance(ThreadContext context, IRubyObject[] args, Block * */ @Override - public IRubyObject initialize(ThreadContext context, Block block) { - return initialize19(context, block); - } - - public IRubyObject initialize(ThreadContext context, IRubyObject superObject, Block block) { - return initialize19(context, superObject, block); - } - @JRubyMethod(name = "initialize", visibility = PRIVATE) - public IRubyObject initialize19(ThreadContext context, Block block) { + public IRubyObject initialize(ThreadContext context, Block block) { checkNotInitialized(); return initializeCommon(context, runtime.getObject(), block); } @JRubyMethod(name = "initialize", visibility = PRIVATE) - public IRubyObject initialize19(ThreadContext context, IRubyObject superObject, Block block) { + public IRubyObject initialize(ThreadContext context, IRubyObject superObject, Block block) { checkNotInitialized(); checkInheritable(superObject); return initializeCommon(context, (RubyClass) superObject, block); @@ -2899,6 +2908,16 @@ public VariableAccessorField getObjectGroupAccessorField() { return variableTableManager.getObjectGroupAccessorField(); } + @Deprecated + public IRubyObject initialize19(ThreadContext context, Block block) { + return initialize(context, block); + } + + @Deprecated + public IRubyObject initialize19(ThreadContext context, IRubyObject superObject, Block block) { + return initialize(context, superObject, block); + } + @Deprecated public IRubyObject invokeFrom(ThreadContext context, CallType callType, IRubyObject caller, IRubyObject self, String name, Block block) { diff --git a/core/src/main/java/org/jruby/RubyDir.java b/core/src/main/java/org/jruby/RubyDir.java index 475908429a0..46155ab5dc5 100644 --- a/core/src/main/java/org/jruby/RubyDir.java +++ b/core/src/main/java/org/jruby/RubyDir.java @@ -85,8 +85,6 @@ public class RubyDir extends RubyObject implements Closeable { private boolean isOpen = true; private Encoding encoding; - private static final Pattern PROTOCOL_PATTERN = Pattern.compile("^(uri|jar|file|classpath):([^:]*:)?//?.*"); - public RubyDir(Ruby runtime, RubyClass type) { super(runtime, type); } @@ -103,13 +101,13 @@ public static RubyClass createDirClass(Ruby runtime) { return dirClass; } - private final void checkDir() { + private void checkDir() { checkDirIgnoreClosed(); if (!isOpen) throw getRuntime().newIOError("closed directory"); } - private final void checkDirIgnoreClosed() { + private void checkDirIgnoreClosed() { testFrozen("Dir"); // update snapshot (if changed) : if (snapshot == null || dir.exists() && dir.lastModified() > lastModified) { diff --git a/core/src/main/java/org/jruby/java/invokers/ConstructorInvoker.java b/core/src/main/java/org/jruby/java/invokers/ConstructorInvoker.java index 404391f611c..7517c9674db 100644 --- a/core/src/main/java/org/jruby/java/invokers/ConstructorInvoker.java +++ b/core/src/main/java/org/jruby/java/invokers/ConstructorInvoker.java @@ -213,7 +213,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz return call(context, self, clazz, name, arg0, arg1, arg2); } - private void setAndCacheProxyObject(ThreadContext context, RubyModule clazz, JavaProxy proxy, Object object) { + private static void setAndCacheProxyObject(ThreadContext context, RubyModule clazz, JavaProxy proxy, Object object) { proxy.setObject(object); if (Java.OBJECT_PROXY_CACHE || clazz.getCacheProxy()) { diff --git a/core/src/main/java/org/jruby/java/invokers/RubyToJavaInvoker.java b/core/src/main/java/org/jruby/java/invokers/RubyToJavaInvoker.java index fe55b38eadd..75ffc575774 100644 --- a/core/src/main/java/org/jruby/java/invokers/RubyToJavaInvoker.java +++ b/core/src/main/java/org/jruby/java/invokers/RubyToJavaInvoker.java @@ -161,7 +161,7 @@ void initialize() { } if (varArgs != null /* && varargsMethods.size() > 0 */) { - varargsCallables = (T[]) varArgs.toArray(createCallableArray(varArgs.size())); + varargsCallables = varArgs.toArray(createCallableArray(varArgs.size())); } // NOTE: tested (4, false); with opt_for_space: false but does not // seem to give the promised ~10% improvement in map's speed ... @@ -341,7 +341,7 @@ private static Object convertVarArgumentsOnly(final Class varArrayType, } static JavaProxy castJavaProxy(final IRubyObject self) { - assert self instanceof JavaProxy : "Java methods can only be invoked on Java objects"; + assert self instanceof JavaProxy : "Java methods can only be invoked on Java objects; got: " + self; return (JavaProxy) self; } diff --git a/core/src/main/java/org/jruby/java/proxies/JavaProxy.java b/core/src/main/java/org/jruby/java/proxies/JavaProxy.java index 78daa90c74a..3709ae35b14 100644 --- a/core/src/main/java/org/jruby/java/proxies/JavaProxy.java +++ b/core/src/main/java/org/jruby/java/proxies/JavaProxy.java @@ -6,10 +6,12 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; @@ -29,10 +31,12 @@ import org.jruby.anno.JRubyMethod; import org.jruby.common.IRubyWarnings; import org.jruby.exceptions.RaiseException; +import org.jruby.java.invokers.ConstructorInvoker; import org.jruby.java.invokers.InstanceFieldGetter; import org.jruby.java.invokers.InstanceFieldSetter; import org.jruby.java.invokers.InstanceMethodInvoker; import org.jruby.java.invokers.MethodInvoker; +import org.jruby.java.invokers.RubyToJavaInvoker; import org.jruby.java.invokers.StaticFieldGetter; import org.jruby.java.invokers.StaticFieldSetter; import org.jruby.java.invokers.StaticMethodInvoker; @@ -655,7 +659,7 @@ public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRu checkArgSizeMismatch(runtime, 1, argTypesAry); - Class argTypeClass = (Class) argTypesAry.eltInternal(0).toJava(Class.class); + Class argTypeClass = argTypesAry.eltInternal(0).toJava(Class.class); JavaMethod method = new JavaMethod(runtime, getMethodFromClass(context, recv, name, argTypeClass)); return method.invokeStaticDirect(context, arg0.toJava(argTypeClass)); @@ -696,29 +700,37 @@ public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, I } @JRubyMethod(meta = true, visibility = Visibility.PRIVATE) - public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, IRubyObject newName, IRubyObject rubyName, IRubyObject argTypes) { + public static IRubyObject java_alias(ThreadContext context, IRubyObject klass, IRubyObject newName, IRubyObject rubyName, IRubyObject argTypes) { final Ruby runtime = context.runtime; - if ( ! ( clazz instanceof RubyModule ) ) { - throw runtime.newTypeError(clazz, runtime.getModule()); + if ( ! ( klass instanceof RubyModule ) ) { + throw runtime.newTypeError(klass, runtime.getModule()); } - final RubyModule proxyClass = (RubyModule) clazz; + final RubyModule proxyClass = (RubyModule) klass; String name = rubyName.asJavaString(); String newNameStr = newName.asJavaString(); - RubyArray argTypesAry = argTypes.convertToArray(); - Class[] argTypesClasses = (Class[]) argTypesAry.toArray(new Class[argTypesAry.size()]); - - final Method method = getMethodFromClass(context, clazz, name, argTypesClasses); - final MethodInvoker invoker; - - if ( Modifier.isStatic( method.getModifiers() ) ) { - invoker = new StaticMethodInvoker(proxyClass.getMetaClass(), () -> arrayOf(method), newNameStr); - // add alias to meta - proxyClass.getSingletonClass().addMethod(newNameStr, invoker); - } - else { - invoker = new InstanceMethodInvoker(proxyClass, () -> arrayOf(method), newNameStr); - proxyClass.addMethod(newNameStr, invoker); + Class[] argTypesClasses = (Class[]) argTypes.convertToArray().toArray(ClassUtils.EMPTY_CLASS_ARRAY); + + final Class clazz = JavaUtil.getJavaClass(proxyClass); + final RubyToJavaInvoker invoker; + switch (name) { + case "" : + final Constructor constructor = getConstructorFromClass(context, clazz, name, argTypesClasses); + invoker = new ConstructorInvoker(proxyClass, () -> arrayOf(constructor), newNameStr); + proxyClass.addMethod(newNameStr, invoker); + + break; + + default : + final Method method = getMethodFromClass(context, clazz, name, argTypesClasses); + if ( Modifier.isStatic( method.getModifiers() ) ) { + invoker = new StaticMethodInvoker(proxyClass.getMetaClass(), () -> arrayOf(method), newNameStr); + proxyClass.getSingletonClass().addMethod(newNameStr, invoker); // add alias to meta + } + else { + invoker = new InstanceMethodInvoker(proxyClass, () -> arrayOf(method), newNameStr); + proxyClass.addMethod(newNameStr, invoker); + } } return context.nil; @@ -731,7 +743,7 @@ private static AbstractRubyMethod getRubyMethod(ThreadContext context, IRubyObje } final RubyModule proxyClass = (RubyModule) clazz; - final Method method = getMethodFromClass(context, clazz, name, argTypesClasses); + final Method method = getMethodFromClass(context, JavaUtil.getJavaClass(proxyClass), name, argTypesClasses); final String prettyName = name + CodegenUtils.prettyParams(argTypesClasses); if ( Modifier.isStatic( method.getModifiers() ) ) { @@ -743,21 +755,36 @@ private static AbstractRubyMethod getRubyMethod(ThreadContext context, IRubyObje return RubyUnboundMethod.newUnboundMethod(proxyClass, prettyName, proxyClass, name, new CacheEntry(invoker, proxyClass, proxyClass.getGeneration())); } - private static Method getMethodFromClass(final ThreadContext context, final IRubyObject proxyClass, - final String name, final Class... argTypes) { - final Class clazz = JavaUtil.getJavaClass((RubyModule) proxyClass); + private static Method getMethodFromClass(final ThreadContext context, final IRubyObject klass, final String name, final Class... argTypes) { + return getMethodFromClass(context, JavaUtil.getJavaClass((RubyModule) klass), name, argTypes); + } + + private static Method getMethodFromClass(final ThreadContext context, final Class clazz, final String name, final Class... argTypes) { try { return clazz.getMethod(name, argTypes); + } catch (NoSuchMethodException e) { + throw newNameError(context.runtime, "Java method not found: " + format(clazz, name, argTypes), name, e); } - catch (NoSuchMethodException e) { - String prettyName = name + CodegenUtils.prettyParams(argTypes); - String errorName = clazz.getName() + '.' + prettyName; - RaiseException ex = context.runtime.newNameError("Java method not found: " + errorName, name); - ex.initCause(e); - throw ex; + } + + private static Constructor getConstructorFromClass(final ThreadContext context, final Class clazz, final String name, final Class... argTypes) { + try { + return clazz.getConstructor(argTypes); + } catch (NoSuchMethodException e) { + throw newNameError(context.runtime, "Java initializer not found: " + format(clazz, name, argTypes), name, e); } } + private static String format(final Class clazz, final String name, final Class... argTypes) { + return clazz.getName() + '.' + name + CodegenUtils.prettyParams(argTypes); // e.g. SomeClass.someMethod(java.lang.String) + } + + private static RaiseException newNameError(final Ruby runtime, final String msg, final String name, final ReflectiveOperationException cause) { + RaiseException ex = runtime.newNameError(msg, name); + ex.initCause(cause); + return ex; + } + } } diff --git a/spec/java_integration/fixtures/AnArrayList.java b/spec/java_integration/fixtures/AnArrayList.java new file mode 100644 index 00000000000..d7f1e4ded64 --- /dev/null +++ b/spec/java_integration/fixtures/AnArrayList.java @@ -0,0 +1,17 @@ +package java_integration.fixtures; + +public class AnArrayList extends java.util.ArrayList { + public static final java.util.Vector CREATED_INSTANCES = new java.util.Vector(); + + public AnArrayList() { + super(); + CREATED_INSTANCES.add(this); + } + + public AnArrayList(int c) { + super(c); + for (int i=0; i', [Java::int] # AnArrayList(int) + + def initialize(num) + init(num) if num + @@last_instance = self + end + + @@last_instance = nil + def self.last_instance; @@last_instance end + end + end + + before(:each) { klass::CREATED_INSTANCES.clear } + + let(:klass) { Java::java_integration.fixtures::AnArrayList } + + it 'is supported' do + a_list = klass.new(3) + expect( a_list ).to be klass::last_instance + expect( klass::CREATED_INSTANCES.size ).to be 1 + expect( klass::CREATED_INSTANCES[0].size ).to eql(3) + + a_list = klass.allocate + a_list.init(10) # useful to bypass :initialize + expect( klass::CREATED_INSTANCES.size ).to be 2 + expect( a_list ).to_not be klass::last_instance + end + + it 'raises on invalid arguments' do + expect { klass.new(:foo) }.to raise_error(TypeError) + end + + it 'raises on unexpected arguments' do + a_list = klass.new(nil) + expect { a_list.init(1, 2) }.to raise_error(ArgumentError) + end + + end + context 'interface' do before(:all) do