Skip to content

Commit

Permalink
Merge branch 'jruby-1_7'
Browse files Browse the repository at this point in the history
* jruby-1_7:
  add declaring class methods for Java/Ruby on JavaConstructor
  make the java method spec pass on Ruby > 1.9 (method names are symbols)
  move and add some more Java 8 interface specs
  use (Java 6+) compiler API instead of `javac` (to make sure right is used)
  increase return type on internal method
  re-use some code snippets within instance and meta java_send impls
  move JavaProxyClassMethods from Java into JavaProxy instead
  spec TypeError raised with java_send when there's a type mismatch
  spec how Java array[].to_s behaves on non-byte[]
  move byte[] proxy to_s into a specialized class
  avoid e.printStackTrace + init native ex.cause and cleanup RubyDigest some
  make require 'digest/bubblebabble' work (MRI compatibility)
  Fix Digest bubblebabble incorrect output on empty string
  Remove several layers on copying in babblebubble.
  Add direct BubbleBabble power from OpenSSH
  manually filter out matching callables with non-matching arguments length

Conflicts:
	core/src/main/java/org/jruby/ext/digest/RubyDigest.java
	core/src/main/java/org/jruby/javasupport/Java.java
kares committed Aug 16, 2015
2 parents af7f58f + b2d759a commit 69f9741
Showing 11 changed files with 351 additions and 264 deletions.
134 changes: 74 additions & 60 deletions core/src/main/java/org/jruby/ext/digest/RubyDigest.java
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
* Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
* Copyright (C) 2008 Vladimir Sizikov <vsizikov@gmail.com>
* Copyright (C) 2009 Joseph LaFata <joe@quibb.org>
*
*
* 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"),
@@ -50,33 +50,41 @@
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;

/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
*/
@JRubyModule(name="Digest")
public class RubyDigest {
private static Provider provider = null;
private static final Map<String, MessageDigest> CLONEABLE_DIGESTS = new HashMap<String, MessageDigest>();

private static final Map<String, MessageDigest> CLONEABLE_DIGESTS = new HashMap<String, MessageDigest>(8, 1);
static {
// standard digests from JCA specification; if we can retrieve and clone, save them
for (String name : new String[] {"MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512"})
try {
MessageDigest digest = MessageDigest.getInstance(name);
digest.clone();
CLONEABLE_DIGESTS.put(name, digest);
} catch (Exception e) {
e.printStackTrace();
// ignore; go to next iteration
for (String name : new String[] {"MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512"}) {
try {
MessageDigest digest = MessageDigest.getInstance(name);
digest.clone();
CLONEABLE_DIGESTS.put(name, digest);
}
catch (Exception e) {
logger().debug(name + " not clonable", e);
}
}
}

private static Logger logger() { return LoggerFactory.getLogger("RubyDigest"); }

private static Provider provider = null;

public static void createDigest(Ruby runtime) {
// We're not setting the provider or anything, but it seems that BouncyCastle does some internal things in its
// provider's constructor which require it to be executed in a secure context.
@@ -104,27 +112,29 @@ public Object run() {
cDigestBase.defineAnnotatedMethods(DigestBase.class);
}

private static MessageDigest createMessageDigest(Ruby runtime, String providerName) throws NoSuchAlgorithmException {
MessageDigest cloneable = CLONEABLE_DIGESTS.get(providerName);
private static MessageDigest createMessageDigest(final String name) throws NoSuchAlgorithmException {
MessageDigest cloneable = CLONEABLE_DIGESTS.get(name);
if (cloneable != null) {
try {
return (MessageDigest)cloneable.clone();
} catch (CloneNotSupportedException cnse) {
return (MessageDigest) cloneable.clone();
}
catch (CloneNotSupportedException e) {
// should never happen, since we tested it in static init
}
}

// fall back on JCA mechanisms for getting a digest
if(provider != null) {
if (provider != null) {
try {
return MessageDigest.getInstance(providerName, provider);
} catch(NoSuchAlgorithmException e) {
return MessageDigest.getInstance(name, provider);
}
catch (NoSuchAlgorithmException e) {
// bouncy castle doesn't support algorithm
}
}

// fall back to default JCA providers
return MessageDigest.getInstance(providerName);
return MessageDigest.getInstance(name);
}

private final static byte[] digits = {
@@ -146,17 +156,17 @@ private static ByteList toHex(byte[] val) {
return byteList;
}

private static IRubyObject toHexString(Ruby runtime, byte[] val) {
private static RubyString toHexString(Ruby runtime, byte[] val) {
return RubyString.newStringNoCopy(runtime, new ByteList(ByteList.plain(toHex(val)), USASCIIEncoding.INSTANCE));
}

@JRubyMethod(name = "hexencode", required = 1, meta = true)
public static IRubyObject s_hexencode(IRubyObject recv, IRubyObject arg) {
return toHexString(recv.getRuntime(), arg.convertToString().getBytes());
public static RubyString hexencode(IRubyObject self, IRubyObject arg) {
return toHexString(self.getRuntime(), arg.convertToString().getBytes());
}

@JRubyMethod(name = "bubblebabble", required = 1, meta = true)
public static IRubyObject bubblebabble(IRubyObject recv, IRubyObject arg) {
public static RubyString bubblebabble(IRubyObject recv, IRubyObject arg) {
final ByteList bytes = arg.convertToString().getByteList();
return RubyString.newString(recv.getRuntime(), BubbleBabble.bubblebabble(bytes.unsafeBytes(), bytes.begin(), bytes.length()));
}
@@ -196,47 +206,49 @@ public static class SHA512 {}

public static void createDigestMD5(Ruby runtime) {
runtime.getLoadService().require("digest");
RubyModule mDigest = runtime.getModule("Digest");
RubyClass cDigestBase = mDigest.getClass("Base");
RubyClass cDigest_MD5 = mDigest.defineClassUnder("MD5",cDigestBase,cDigestBase.getAllocator());
cDigest_MD5.setInternalVariable("metadata", new Metadata("MD5", 64));
RubyModule Digest = runtime.getModule("Digest");
RubyClass Base = Digest.getClass("Base");
RubyClass MD5 = Digest.defineClassUnder("MD5", Base, Base.getAllocator());
MD5.setInternalVariable("metadata", new Metadata("MD5", 64));
}

public static void createDigestRMD160(Ruby runtime) {
runtime.getLoadService().require("digest");
if(provider == null) {
throw runtime.newLoadError("RMD160 not supported without BouncyCastle");
}
RubyModule mDigest = runtime.getModule("Digest");
RubyClass cDigestBase = mDigest.getClass("Base");
RubyClass cDigest_RMD160 = mDigest.defineClassUnder("RMD160",cDigestBase,cDigestBase.getAllocator());
cDigest_RMD160.setInternalVariable("metadata", new Metadata("RIPEMD160", 64));
RubyModule Digest = runtime.getModule("Digest");
RubyClass Base = Digest.getClass("Base");
RubyClass RMD160 = Digest.defineClassUnder("RMD160", Base, Base.getAllocator());
RMD160.setInternalVariable("metadata", new Metadata("RIPEMD160", 64));
}

public static void createDigestSHA1(Ruby runtime) {
runtime.getLoadService().require("digest");
RubyModule mDigest = runtime.getModule("Digest");
RubyClass cDigestBase = mDigest.getClass("Base");
RubyClass cDigest_SHA1 = mDigest.defineClassUnder("SHA1",cDigestBase,cDigestBase.getAllocator());
cDigest_SHA1.setInternalVariable("metadata", new Metadata("SHA1", 64));
RubyModule Digest = runtime.getModule("Digest");
RubyClass Base = Digest.getClass("Base");
RubyClass SHA1 = Digest.defineClassUnder("SHA1", Base, Base.getAllocator());
SHA1.setInternalVariable("metadata", new Metadata("SHA1", 64));
}

public static void createDigestSHA2(Ruby runtime) {
runtime.getLoadService().require("digest");
try {
createMessageDigest(runtime, "SHA-256");
} catch(NoSuchAlgorithmException e) {
throw runtime.newLoadError("SHA2 not supported");
}
RubyModule mDigest = runtime.getModule("Digest");
RubyClass cDigestBase = mDigest.getClass("Base");
RubyClass cDigest_SHA2_256 = mDigest.defineClassUnder("SHA256",cDigestBase,cDigestBase.getAllocator());
Metadata sha256Metadata = new Metadata("SHA-256", 64);
cDigest_SHA2_256.setInternalVariable("metadata", sha256Metadata);
RubyClass cDigest_SHA2_384 = mDigest.defineClassUnder("SHA384",cDigestBase,cDigestBase.getAllocator());
cDigest_SHA2_384.setInternalVariable("metadata", new Metadata("SHA-384", 128));
RubyClass cDigest_SHA2_512 = mDigest.defineClassUnder("SHA512",cDigestBase,cDigestBase.getAllocator());
cDigest_SHA2_512.setInternalVariable("metadata", new Metadata("SHA-512", 128));
createMessageDigest("SHA-256");
}
catch (NoSuchAlgorithmException e) {
RaiseException ex = runtime.newLoadError("SHA2 not supported");
ex.initCause(e);
throw ex;
}
final RubyModule Digest = runtime.getModule("Digest");
final RubyClass Base = Digest.getClass("Base");
RubyClass SHA256 = Digest.defineClassUnder("SHA256", Base, Base.getAllocator());
SHA256.setInternalVariable("metadata", new Metadata("SHA-256", 64));
RubyClass SHA384 = Digest.defineClassUnder("SHA384", Base, Base.getAllocator());
SHA384.setInternalVariable("metadata", new Metadata("SHA-384", 128));
RubyClass SHA512 = Digest.defineClassUnder("SHA512", Base, Base.getAllocator());
SHA512.setInternalVariable("metadata", new Metadata("SHA-512", 128));
}

@JRubyModule(name = "Digest::Instance")
@@ -303,7 +315,7 @@ public static IRubyObject newObject(ThreadContext context, IRubyObject self) {

@JRubyMethod(optional = 1)
public static IRubyObject digest(ThreadContext context, IRubyObject self, IRubyObject[] args) {
IRubyObject value = null;
final IRubyObject value;
if (args != null && args.length > 0) {
self.callMethod(context, "reset");
self.callMethod(context, "update", args[0]);
@@ -363,7 +375,7 @@ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
public DigestClass(Ruby runtime, RubyClass type) {
super(runtime, type);
}

@JRubyMethod(name = "digest", required = 1, rest = true, meta = true)
public static IRubyObject s_digest(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
Ruby runtime = recv.getRuntime();
@@ -408,7 +420,7 @@ public DigestBase(Ruby runtime, RubyClass type) {
if(metadata == null) {
throw runtime.newNotImplementedError("the " + type + "() function is unimplemented on this machine");
}

try {
setAlgorithm(metadata);
} catch(NoSuchAlgorithmException e) {
@@ -431,15 +443,16 @@ private Metadata getMetadata(RubyModule type) {
@JRubyMethod(required = 1, visibility = Visibility.PRIVATE)
@Override
public IRubyObject initialize_copy(IRubyObject obj) {
if(this == obj) {
return this;
}
((RubyObject)obj).checkFrozen();
if (this == obj) return this;

DigestBase from = (DigestBase) obj;
from.checkFrozen();

String name = ((DigestBase)obj).algo.getAlgorithm();
try {
algo = (MessageDigest)((DigestBase)obj).algo.clone();
} catch(CloneNotSupportedException e) {
this.algo = (MessageDigest) from.algo.clone();
}
catch (CloneNotSupportedException e) {
String name = from.algo.getAlgorithm();
throw getRuntime().newTypeError("Could not initialize copy of digest (" + name + ")");
}
return this;
@@ -458,7 +471,7 @@ public IRubyObject finish() {
algo.reset();
return digest;
}

@JRubyMethod()
public IRubyObject digest_length() {
return RubyFixnum.newFixnum(getRuntime(), algo.getDigestLength());
@@ -486,8 +499,9 @@ public IRubyObject bubblebabble(ThreadContext context) {
}

private void setAlgorithm(Metadata metadata) throws NoSuchAlgorithmException {
this.algo = createMessageDigest(getRuntime(), metadata.getName());
this.algo = createMessageDigest(metadata.getName());
this.blockLength = metadata.getBlockLength();
}

}
}// RubyDigest
15 changes: 14 additions & 1 deletion core/src/main/java/org/jruby/java/dispatch/CallableSelector.java
Original file line number Diff line number Diff line change
@@ -185,6 +185,7 @@ private static <T extends ParameterTypes> T findMatchingCallableForArgs(final Ru
final T candidate = candidates.get(c);
final Class<?>[] cTypes = candidate.getParameterTypes();

// TODO still need to handle var-args better Class<?> lastType;
for ( int i = 0; i < msTypes.length; i++ ) {
final Class<?> msType = msTypes[i], cType = cTypes[i];
if ( msType != cType && msType.isAssignableFrom(cType) ) {
@@ -375,7 +376,19 @@ private static <T extends ParameterTypes> List<T> findCallableCandidates(final T
incoming = retained.toArray( new ParameterTypes[retained.size()] );
}

return retained;
// final step rule out argument length mismatch :
int j = 0; for ( int i = 0; i < retained.size(); i++ ) {
T callable = retained.get(i);
if ( callable.isVarArgs() ) {
if ( callable.getArity() > args.length - 1 ) continue;
}
else {
if ( callable.getArity() != args.length ) continue;
}
retained.set(j++, callable);
}

return j < retained.size() ? retained.subList(0, j) : retained;
}

private static int calcExactnessScore(final ParameterTypes callable, final IRubyObject[] args) {
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ public static RubyModule createJavaInterfaceTemplateModule(ThreadContext context
singleton.addReadAttribute(context, "java_class");
singleton.defineAnnotatedMethods(JavaInterfaceTemplate.class);

JavaInterfaceTemplate.defineAnnotatedMethods(Java.JavaProxyClassMethods.class);
JavaInterfaceTemplate.defineAnnotatedMethods(JavaProxy.ClassMethods.class);

return JavaInterfaceTemplate;
}
153 changes: 147 additions & 6 deletions core/src/main/java/org/jruby/java/proxies/JavaProxy.java
Original file line number Diff line number Diff line change
@@ -3,10 +3,8 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -15,6 +13,7 @@
import java.util.Iterator;
import java.util.Map;

import org.jruby.AbstractRubyMethod;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
@@ -23,6 +22,7 @@
import org.jruby.RubyMethod;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.RubyUnboundMethod;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings;
import org.jruby.java.invokers.InstanceFieldGetter;
@@ -40,15 +40,15 @@
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.CodegenUtils;
import org.jruby.util.JRubyObjectInputStream;

public class JavaProxy extends RubyObject {
private static final boolean DEBUG = false;

private JavaObject javaObject;
private transient JavaObject javaObject;
Object object;

public JavaProxy(Ruby runtime, RubyClass klazz) {
@@ -80,8 +80,6 @@ public static RubyClass createJavaProxy(ThreadContext context) {

@Override
public Object dataGetStruct() {
// for investigating and eliminating code that causes JavaObject to live
if (DEBUG) Thread.dumpStack();
return getJavaObject();
}

@@ -499,4 +497,147 @@ private void confirmCachedProxy(String message) {

private static final String NONPERSISTENT_IVAR_MESSAGE = "instance vars on non-persistent Java type {0} (http://wiki.jruby.org/Persistence)";
private static final String NONPERSISTENT_SINGLETON_MESSAGE = "singleton on non-persistent Java type {0} (http://wiki.jruby.org/Persistence)";

public static class ClassMethods {

@JRubyMethod(meta = true)
public static IRubyObject java_method(ThreadContext context, IRubyObject proxyClass, IRubyObject rubyName) {
String name = rubyName.asJavaString();

return getRubyMethod(context, proxyClass, name);
}

@JRubyMethod(meta = true)
public static IRubyObject java_method(ThreadContext context, IRubyObject proxyClass, IRubyObject rubyName, IRubyObject argTypes) {
String name = rubyName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
Class[] argTypesClasses = (Class[])argTypesAry.toArray(new Class[argTypesAry.size()]);

return getRubyMethod(context, proxyClass, name, argTypesClasses);
}

@JRubyMethod(meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject rubyName) {
String name = rubyName.asJavaString();
final Ruby runtime = context.runtime;

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(context, recv, name));
return method.invokeStaticDirect(context);
}

@JRubyMethod(meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject rubyName, IRubyObject argTypes) {
String name = rubyName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
final Ruby runtime = context.runtime;

checkArgSizeMismatch(runtime, 0, argTypesAry);

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(context, recv, name));
return method.invokeStaticDirect(context);
}

@JRubyMethod(meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject rubyName, IRubyObject argTypes, IRubyObject arg0) {
String name = rubyName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
final Ruby runtime = context.runtime;

checkArgSizeMismatch(runtime, 1, argTypesAry);

Class argTypeClass = (Class) argTypesAry.eltInternal(0).toJava(Class.class);

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(context, recv, name, argTypeClass));
return method.invokeStaticDirect(context, arg0.toJava(argTypeClass));
}

@JRubyMethod(required = 4, rest = true, meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
final Ruby runtime = context.runtime;

String name = args[0].asJavaString();
RubyArray argTypesAry = args[1].convertToArray();
final int argsLen = args.length - 2;

checkArgSizeMismatch(runtime, argsLen, argTypesAry);

Class[] argTypesClasses = (Class[]) argTypesAry.toArray(new Class[argsLen]);

Object[] javaArgs = new Object[argsLen];
for ( int i = 0; i < argsLen; i++ ) {
javaArgs[i] = args[i + 2].toJava( argTypesClasses[i] );
}

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(context, recv, name, argTypesClasses));
return method.invokeStaticDirect(context, javaArgs);
}

@JRubyMethod(meta = true, visibility = Visibility.PRIVATE)
public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, IRubyObject newName, IRubyObject rubyName) {
return java_alias(context, clazz, newName, rubyName, context.runtime.newEmptyArray());
}

@JRubyMethod(meta = true, visibility = Visibility.PRIVATE)
public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, IRubyObject newName, IRubyObject rubyName, IRubyObject argTypes) {
final Ruby runtime = context.runtime;
if ( ! ( clazz instanceof RubyClass ) ) {
throw runtime.newTypeError(clazz, runtime.getModule());
}
final RubyClass proxyClass = (RubyClass) clazz;

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(), method);
// add alias to meta
proxyClass.getSingletonClass().addMethod(newNameStr, invoker);
}
else {
invoker = new InstanceMethodInvoker(proxyClass, method);
proxyClass.addMethod(newNameStr, invoker);
}

return context.nil;
}

private static AbstractRubyMethod getRubyMethod(ThreadContext context, IRubyObject clazz, String name, Class... argTypesClasses) {
final Ruby runtime = context.runtime;
if ( ! ( clazz instanceof RubyModule ) ) {
throw runtime.newTypeError(clazz, runtime.getModule());
}
final RubyModule proxyClass = (RubyModule) clazz;

final Method method = getMethodFromClass(context, clazz, name, argTypesClasses);
final String prettyName = name + CodegenUtils.prettyParams(argTypesClasses);

if ( Modifier.isStatic( method.getModifiers() ) ) {
MethodInvoker invoker = new StaticMethodInvoker(proxyClass, method);
return RubyMethod.newMethod(proxyClass, prettyName, proxyClass, name, invoker, clazz);
}

MethodInvoker invoker = new InstanceMethodInvoker(proxyClass, method);
return RubyUnboundMethod.newUnboundMethod(proxyClass, prettyName, proxyClass, name, invoker);
}

private static Method getMethodFromClass(final ThreadContext context, final IRubyObject proxyClass,
final String name, final Class... argTypes) {
final Class<?> clazz = JavaClass.getJavaClass(context, (RubyModule) proxyClass);
try {
return clazz.getMethod(name, argTypes);
}
catch (NoSuchMethodException nsme) {
String prettyName = name + CodegenUtils.prettyParams(argTypes);
String errorName = clazz.getName() + '.' + prettyName;
throw context.runtime.newNameError("Java method not found: " + errorName, name);
}
}

}

}
172 changes: 13 additions & 159 deletions core/src/main/java/org/jruby/javasupport/Java.java
Original file line number Diff line number Diff line change
@@ -128,7 +128,7 @@ public void load(Ruby runtime, boolean wrap) {

// load Ruby parts of the 'java' library
runtime.getLoadService().load("jruby/java.rb", false);

// rewite ArrayJavaProxy superclass to point at Object, so it inherits Object behaviors
final RubyClass ArrayJavaProxy = runtime.getClass("ArrayJavaProxy");
ArrayJavaProxy.setSuperClass(runtime.getJavaSupport().getObjectJavaClass().getProxyClass());
@@ -500,18 +500,8 @@ private static void generateClassProxy(Ruby runtime, Class<?> clazz, RubyClass p
if (clazz.isArray()) {
createProxyClass(runtime, proxy, clazz, true);

// FIXME: Organizationally this might be nicer in a specialized class
if ( clazz.getComponentType() == byte.class ) {
final Encoding ascii8bit = runtime.getEncodingService().getAscii8bitEncoding();

// All bytes can be considered raw strings and forced to particular codings if not 8bitascii
proxy.addMethod("to_s", new JavaMethodZero(proxy, PUBLIC) {
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
ByteList bytes = new ByteList((byte[]) ((ArrayJavaProxy) self).getObject(), ascii8bit);
return RubyString.newStringLight(context.runtime, bytes);
}
});
proxy.defineAnnotatedMethods( ByteArrayProxyMethods.class ); // to_s
}
}
else if ( clazz.isPrimitive() ) {
@@ -553,7 +543,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}

private static RubyClass createProxyClass(final Ruby runtime,
final RubyClass proxyClass, final Class<?> javaClass, boolean invokeInherited) {
final RubyClass proxyClass, final Class<?> javaClass, boolean invokeInherited) {

final RubyClass superClass = proxyClass.getSuperClass();

@@ -567,7 +557,7 @@ private static RubyClass createProxyClass(final Ruby runtime,
else {
proxyClass.setAllocator( superClass.getAllocator() );
}
proxyClass.defineAnnotatedMethods( JavaProxyClassMethods.class );
proxyClass.defineAnnotatedMethods( JavaProxy.ClassMethods.class );

if ( invokeInherited ) proxyClass.inherit(superClass);

@@ -576,156 +566,20 @@ private static RubyClass createProxyClass(final Ruby runtime,
return proxyClass;
}

public static class JavaProxyClassMethods {
@JRubyMethod(meta = true)
public static IRubyObject java_method(ThreadContext context, IRubyObject proxyClass, IRubyObject rubyName) {
String name = rubyName.asJavaString();

return getRubyMethod(context, proxyClass, name);
}

@JRubyMethod(meta = true)
public static IRubyObject java_method(ThreadContext context, IRubyObject proxyClass, IRubyObject rubyName, IRubyObject argTypes) {
String name = rubyName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
Class[] argTypesClasses = (Class[])argTypesAry.toArray(new Class[argTypesAry.size()]);

return getRubyMethod(context, proxyClass, name, argTypesClasses);
}

@JRubyMethod(meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject rubyName) {
String name = rubyName.asJavaString();
final Ruby runtime = context.runtime;

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(runtime, recv, name));
return method.invokeStaticDirect(context);
}

@JRubyMethod(meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject rubyName, IRubyObject argTypes) {
String name = rubyName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
final Ruby runtime = context.runtime;

if (argTypesAry.size() != 0) {
Class[] argTypesClasses = (Class[]) argTypesAry.toArray(new Class[argTypesAry.size()]);
throw JavaMethod.newArgSizeMismatchError(runtime, argTypesClasses);
}

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(runtime, recv, name));
return method.invokeStaticDirect(context);
}

@JRubyMethod(meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject rubyName, IRubyObject argTypes, IRubyObject arg0) {
String name = rubyName.asJavaString();
RubyArray argTypesAry = argTypes.convertToArray();
final Ruby runtime = context.runtime;

if (argTypesAry.size() != 1) {
throw JavaMethod.newArgSizeMismatchError(runtime, (Class) argTypesAry.eltInternal(0).toJava(Class.class));
}

Class argTypeClass = (Class) argTypesAry.eltInternal(0).toJava(Class.class);

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(runtime, recv, name, argTypeClass));
return method.invokeStaticDirect(context, arg0.toJava(argTypeClass));
}

@JRubyMethod(required = 1, rest = true, meta = true)
public static IRubyObject java_send(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
final Ruby runtime = context.runtime;

String name = args[0].asJavaString();
RubyArray argTypesAry = args[1].convertToArray();
int argsLen = args.length - 2;

if (argTypesAry.size() != argsLen) {
throw JavaMethod.newArgSizeMismatchError(runtime, (Class[]) argTypesAry.toArray(new Class[argTypesAry.size()]));
}

Class[] argTypesClasses = (Class[]) argTypesAry.toArray(new Class[argsLen]);

Object[] argsAry = new Object[argsLen];
for (int i = 0; i < argsLen; i++) {
argsAry[i] = args[i + 2].toJava(argTypesClasses[i]);
}

JavaMethod method = new JavaMethod(runtime, getMethodFromClass(runtime, recv, name, argTypesClasses));
return method.invokeStaticDirect(context, argsAry);
}

@JRubyMethod(meta = true, visibility = PRIVATE)
public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, IRubyObject newName, IRubyObject rubyName) {
return java_alias(context, clazz, newName, rubyName, context.runtime.newEmptyArray());
}

@JRubyMethod(meta = true, visibility = PRIVATE)
public static IRubyObject java_alias(ThreadContext context, IRubyObject clazz, IRubyObject newName, IRubyObject rubyName, IRubyObject argTypes) {
final Ruby runtime = context.runtime;
if ( ! ( clazz instanceof RubyClass ) ) {
throw runtime.newTypeError(clazz, runtime.getModule());
}
final RubyClass proxyClass = (RubyClass) clazz;

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(), method);
// add alias to meta
proxyClass.getSingletonClass().addMethod(newNameStr, invoker);
}
else {
invoker = new InstanceMethodInvoker(proxyClass, method);
proxyClass.addMethod(newNameStr, invoker);
}

return context.nil;
}
}

private static IRubyObject getRubyMethod(ThreadContext context, IRubyObject clazz, String name, Class... argTypesClasses) {
final Ruby runtime = context.runtime;
if ( ! ( clazz instanceof RubyModule ) ) {
throw runtime.newTypeError(clazz, runtime.getModule());
}
final RubyModule proxyClass = (RubyModule) clazz;

final Method method = getMethodFromClass(context, clazz, name, argTypesClasses);
final String prettyName = name + CodegenUtils.prettyParams(argTypesClasses);
@Deprecated
public static class JavaProxyClassMethods extends JavaProxy.ClassMethods {}

if ( Modifier.isStatic( method.getModifiers() ) ) {
MethodInvoker invoker = new StaticMethodInvoker(proxyClass, method);
return RubyMethod.newMethod(proxyClass, prettyName, proxyClass, name, invoker, clazz);
}
public static class ByteArrayProxyMethods {

MethodInvoker invoker = new InstanceMethodInvoker(proxyClass, method);
return RubyUnboundMethod.newUnboundMethod(proxyClass, prettyName, proxyClass, name, invoker);
}
@JRubyMethod
public static IRubyObject to_s(ThreadContext context, IRubyObject self) {
final Encoding ascii8bit = context.runtime.getEncodingService().getAscii8bitEncoding();

private static Method getMethodFromClass(final ThreadContext context, final IRubyObject proxyClass,
final String name, final Class... argTypes) {
final Class<?> clazz = JavaClass.getJavaClass(context, (RubyModule) proxyClass);
try {
return clazz.getMethod(name, argTypes);
}
catch (NoSuchMethodException nsme) {
String prettyName = name + CodegenUtils.prettyParams(argTypes);
String errorName = clazz.getName() + '.' + prettyName;
throw context.runtime.newNameError("Java method not found: " + errorName, name);
// All bytes can be considered raw strings and forced to particular codings if not 8bitascii
ByteList bytes = new ByteList((byte[]) ((ArrayJavaProxy) self).getObject(), ascii8bit);
return RubyString.newStringLight(context.runtime, bytes);
}
}

public static Method getMethodFromClass(final Ruby runtime, final IRubyObject proxyClass,
final String name, final Class... argTypes) {
return getMethodFromClass(runtime.getCurrentContext(), proxyClass, name, argTypes);
}

public static IRubyObject concrete_proxy_inherited(final IRubyObject clazz, final IRubyObject subclazz) {
9 changes: 9 additions & 0 deletions core/src/main/java/org/jruby/javasupport/JavaConstructor.java
Original file line number Diff line number Diff line change
@@ -187,6 +187,10 @@ public String toGenericString() {
return constructor.toGenericString();
}

public Class<?> getDeclaringClass() {
return constructor.getDeclaringClass();
}

public AccessibleObject accessibleObject() {
return constructor;
}
@@ -201,6 +205,11 @@ public IRubyObject return_type() {
return getRuntime().getNil();
}

@JRubyMethod
public IRubyObject declaring_class() {
return JavaClass.get(getRuntime(), getDeclaringClass());
}

@JRubyMethod(rest = true)
public final IRubyObject new_instance(final IRubyObject[] args) {
return new_instance( convertArguments(args) );
1 change: 1 addition & 0 deletions lib/ruby/stdlib/digest/digest/bubblebabble.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'digest' # bubblebabble gets in with Digest ext loading
79 changes: 79 additions & 0 deletions spec/java_integration/interfaces/java8_methods_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require File.dirname(__FILE__) + "/../spec_helper"

describe "an interface (Java 8+)" do

before :all do
require 'tmpdir'; @tmpdir = Dir.mktmpdir
files = []

src = <<-JAVA
public interface Java8Interface {
static String message() { return "hello"; }
static String message(CharSequence name) { return "hello " + name; }
abstract String bar() ;
default CharSequence foo(Object prefix) { return prefix + "foo" + ' ' + bar(); }
}
JAVA
files << (file = "#{@tmpdir}/Java8Interface.java"); File.open(file, 'w') { |f| f.print(src) }

src = <<-JAVA
public class Java8Implemtor implements Java8Interface {
public String bar() { return getClass().getSimpleName(); }
}
JAVA
files << (file = "#{@tmpdir}/Java8Implemtor.java"); File.open(file, 'w') { |f| f.print(src) }

fail "#{files.inspect} compilation failed (see above javac output)!" unless javac_compile files

$CLASSPATH << @tmpdir
end

after :all do
FileUtils.rm_rf @tmpdir
end

it "binds static methods on the proxy module" do
expect(Java::Java8Interface.message).to eq("hello")
end

it "exposes static methods via java_send" do
expect(Java::Java8Interface.java_send(:message)).to eq("hello")
end

it "exposes static methods via java_send with type" do
expect(Java::Java8Interface.java_send(:message, [java.lang.CharSequence], 'world')).to eq("hello world")
end

it "exposes static methods via java_method" do
expect(Java::Java8Interface.java_method(:message).call).to eq("hello")
end

it "exposes instance method via java_method" do
method = Java::Java8Interface.java_method(:foo, [ java.lang.Object ])
expect(method.name).to eq(:"foo(java.lang.Object)") # default
method = Java::Java8Interface.java_method(:bar)
expect(method.name).to eq(:"bar()") # abstract
end if RUBY_VERSION > '1.9'

it "(default) java_method is callable" do
method = Java::Java8Interface.java_method(:foo, [ java.lang.Object ])
expect( method.bind(Java::Java8Implemtor.new).call '' ).to eql 'foo Java8Implemtor'
end

it "java_send works on impl (default method)" do
impl = Java::Java8Implemtor.new
expect(impl.java_send(:bar)).to eq("Java8Implemtor")
expect(impl.java_send(:foo, [ java.lang.Object ], 11)).to eq("11foo Java8Implemtor")
end

def javac_compile(files)
compiler = javax.tools.ToolProvider.getSystemJavaCompiler
fmanager = compiler.getStandardFileManager(nil, nil, nil)
diagnostics = nil
units = fmanager.getJavaFileObjectsFromStrings( files.map { |fname| fname.to_s } )
compilation_task = compiler.getTask(nil, fmanager, diagnostics, nil, nil, units)
compilation_task.call # returns boolean
end

end if ENV_JAVA['java.specification.version'] >= '1.8'
37 changes: 0 additions & 37 deletions spec/java_integration/interfaces/static_methods_spec.rb

This file was deleted.

8 changes: 8 additions & 0 deletions spec/java_integration/methods/java_send_spec.rb
Original file line number Diff line number Diff line change
@@ -32,6 +32,14 @@
str.to_s.should == '1234567890 str1 str2'
str.java_send :append, [Java::char[], Java::int, Java::int], " str3".to_java.to_char_array, 0, 4
str.to_s.should == '1234567890 str1 str2 str'

buf = java::lang::StringBuffer.new(' buf ')
begin
str.java_send :append, [Java::JavaLang::String], buf
rescue TypeError
else; fail end
str.java_send :append, [java.lang.CharSequence.java_class], buf
str.to_s.should == '1234567890 str1 str2 str buf '
end

it "works with package-level classes" do
5 changes: 5 additions & 0 deletions spec/java_integration/types/array_spec.rb
Original file line number Diff line number Diff line change
@@ -231,6 +231,11 @@
arr = [100, 101, 102].to_java :char
arr.inspect.should =~ /^char\[d, e, f\]@[0-9a-f]+$/
end

it "uses toString on to_s" do
arr = [100, 101, 102].to_java :char
arr.to_s.should =~ /\[C@[0-9a-f]+$/
end
end

describe "double" do

0 comments on commit 69f9741

Please sign in to comment.