Skip to content

Commit

Permalink
re-implement the option to lazy bind Java:: constants (jruby#8368)
Browse files Browse the repository at this point in the history
This (partially) reverts (and re-implements) commit 31f1ed6.
  • Loading branch information
kares authored Oct 18, 2024
1 parent 4f5e92c commit 13309db
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,12 @@ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {

private static IRubyObject newInterfaceProxy(final IRubyObject self) {
final RubyClass current = self.getMetaClass();
final Ruby runtime = current.getRuntime();
// construct the new interface impl and set it into the object
Object impl = Java.newInterfaceImpl(self, Java.getInterfacesFromRubyClass(current));
IRubyObject implWrapper = Java.getInstance(self.getRuntime(), impl);
RubyClass proxyClass = (RubyClass) Java.getProxyClass(runtime, impl.getClass());
// we do not want the (InterfaceImpl) proxy-class in this case to be set on the Ruby side
IRubyObject implWrapper = Java.getInstanceInternal(runtime, impl, proxyClass, false);
JavaUtilities.set_java_object(self, self, implWrapper); // self.dataWrapStruct(newObject);
return implWrapper;
}
Expand Down
64 changes: 36 additions & 28 deletions core/src/main/java/org/jruby/javasupport/Java.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public static RubyModule setProxyClass(final Ruby runtime, final RubyModule targ
return proxyClass;
}

private static void setProxyClass(final Ruby runtime, final RubyModule target, final String constName, final RubyModule proxyClass, final boolean validateConstant) {
static void setProxyClass(final Ruby runtime, final RubyModule target, final String constName, final RubyModule proxyClass, final boolean validateConstant) {
if (constantNotSetOrDifferent(target, constName, proxyClass)) {
synchronized (target) { // synchronize to prevent "already initialized constant" warnings with multiple threads
if (constantNotSetOrDifferent(target, constName, proxyClass)) {
Expand Down Expand Up @@ -297,15 +297,19 @@ public static IRubyObject getInstance(Ruby runtime, Object rawJavaObject) {
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 getInstanceInternal(runtime, rawJavaObject, proxyClass, forceCache);
}
return runtime.getNil();
}

public static IRubyObject getInstanceInternal(Ruby runtime, Object rawJavaObject,
RubyClass proxyClass, boolean forceCache) {
if (OBJECT_PROXY_CACHE || forceCache || proxyClass.getCacheProxy()) {
return runtime.getJavaSupport().getObjectProxyCache().getOrCreate(rawJavaObject, proxyClass);
}
return allocateProxy(rawJavaObject, proxyClass);
}

@Deprecated
public static RubyModule getInterfaceModule(final Ruby runtime, final JavaClass javaClass) {
return getInterfaceModule(runtime, javaClass.javaClass());
Expand Down Expand Up @@ -429,16 +433,10 @@ 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) {
return getProxyClass(runtime, clazz, Options.JI_EAGER_CONSTANTS.load());
}

@SuppressWarnings("deprecation")
public static RubyModule getProxyClass(final Ruby runtime, final Class<?> clazz, boolean setConstant) {
RubyModule proxy = runtime.getJavaSupport().getUnfinishedProxy(clazz);
if (proxy != null) return proxy;
return runtime.getJavaSupport().getProxyClassFromCache(clazz, setConstant);
return runtime.getJavaSupport().getProxyClassFromCache(clazz);
}

// expected to handle Java proxy (Ruby) sub-classes as well
Expand Down Expand Up @@ -485,16 +483,17 @@ static RubyModule createProxyClassForClass(final Ruby runtime, final Class<?> cl
return proxy;
}

private static void generateInterfaceProxy(final Ruby runtime, final Class javaClass, final RubyModule proxy) {
assert javaClass.isInterface();
private static void generateInterfaceProxy(final Ruby runtime, final Class<?> clazz, final RubyModule proxy) {
assert clazz.isInterface();

// include any interfaces we extend
final Class<?>[] extended = javaClass.getInterfaces();
final Class<?>[] extended = clazz.getInterfaces();
for (int i = extended.length; --i >= 0; ) {
RubyModule extModule = getInterfaceModule(runtime, extended[i]);
proxy.includeModule(extModule);
}
Initializer.setupProxyModule(runtime, javaClass, proxy);
Initializer.setupProxyModule(runtime, clazz, proxy);
setProxyConstantInJavaPackage(proxy, clazz);
}

private static void generateClassProxy(Ruby runtime, Class<?> clazz, RubyClass proxy, RubyClass superClass) {
Expand All @@ -516,6 +515,7 @@ else if ( clazz == Object.class ) {
} else {
proxy.getMetaClass().defineAnnotatedMethods(OldStyleExtensionInherited.class);
}
setProxyConstantInJavaPackage(proxy, clazz, true);
}
else {
createProxyClass(runtime, proxy, clazz, superClass, false);
Expand All @@ -524,6 +524,7 @@ else if ( clazz == Object.class ) {
for ( int i = interfaces.length; --i >= 0; ) {
proxy.includeModule(getInterfaceModule(runtime, interfaces[i]));
}
setProxyConstantInJavaPackage(proxy, clazz);
}

// JRUBY-1000, fail early when attempting to subclass a final Java class;
Expand Down Expand Up @@ -839,13 +840,19 @@ private static <T extends ParameterTypes> T checkCallableForArity(final int arit
return null;
}

static void setProxyConstantInJavaPackage(final RubyModule proxyClass, final Class<?> clazz) {
setProxyConstantInJavaPackage(proxyClass, clazz, Options.JI_EAGER_CONSTANTS.load());
}

// package scheme 2: separate module for each full package name, constructed
// from the camel-cased package segments: Java::JavaLang::Object,
static void addToJavaPackageModule(Ruby runtime, Class<?> clazz, RubyModule proxyClass) {
final String fullName;
if ( ( fullName = clazz.getName() ) == null ) return;
private static void setProxyConstantInJavaPackage(final RubyModule proxyClass, final Class<?> clazz, final boolean eagerSet) {
assert clazz == proxyClass.dataGetStruct() :
"not a Java proxy wrapper: " + proxyClass.dataGetStruct() + " expected: " + clazz;

if (!eagerSet || !Modifier.isPublic(clazz.getModifiers())) return;

final RubyModule parentModule; final String className;
final String fullName = clazz.getName();

if ( fullName.indexOf('$') != -1 ) {
/*
Expand All @@ -855,12 +862,13 @@ static void addToJavaPackageModule(Ruby runtime, Class<?> clazz, RubyModule prox
*/
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);
}

final Ruby runtime = proxyClass.getRuntime();

final int endPackage = fullName.lastIndexOf('.');
String packageString = endPackage < 0 ? "" : fullName.substring(0, endPackage);
final RubyModule parentModule = getJavaPackageModule(runtime, packageString);
final String className = parentModule == null ? fullName : fullName.substring(endPackage + 1);

if ( parentModule != null && // TODO a Java Ruby class should not validate (as well)
( IdUtil.isConstant(className) || parentModule instanceof JavaPackage ) ) {
Expand Down Expand Up @@ -1088,7 +1096,7 @@ private static RubyModule getProxyClassOrNull(final Ruby runtime, final String c
} catch (ClassNotFoundException ex) { // used to catch NoClassDefFoundError for whatever reason
return null;
}
return getProxyClass(runtime, clazz, true);
return getProxyClass(runtime, clazz);
}

private static int mapMajorMinorClassVersionToJavaVersion(String msg, final int offset) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/javasupport/JavaPackage.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private RubyModule relativeJavaClassOrPackage(final ThreadContext context,

RubyModule relativeJavaProxyClass(final Ruby runtime, final IRubyObject name) {
final String fullName = packageRelativeName( name.toString() ).toString();
return Java.getProxyClass(runtime, Java.getJavaClass(runtime, fullName), true);
return Java.getProxyClass(runtime, Java.getJavaClass(runtime, fullName));
}

@JRubyMethod(name = "respond_to?")
Expand Down
13 changes: 2 additions & 11 deletions core/src/main/java/org/jruby/javasupport/JavaSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
import org.jruby.util.collections.ClassValueCalculator;

import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -370,16 +369,8 @@ final RubyModule getUnfinishedProxy(Class clazz) {
return null;
}

RubyModule getProxyClassFromCache(Class clazz, boolean setConstant) {
RubyModule proxy = proxyClassCache.get(clazz);

if (setConstant) {
if ( Modifier.isPublic(clazz.getModifiers()) ) {
Java.addToJavaPackageModule(runtime, clazz, proxy);
}
}

return proxy;
RubyModule getProxyClassFromCache(Class clazz) {
return proxyClassCache.get(clazz);
}

}
50 changes: 50 additions & 0 deletions core/src/test/java/org/jruby/javasupport/TestJava.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jruby.javasupport;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;

Expand Down Expand Up @@ -137,4 +140,51 @@ public void testOverrideNewOnConcreteJavaProxySubClassRegression() {
assertNotNull(runtime.evalScriptlet("FormatImpl.new")); // used to cause an infinite loop
assertTrue(runtime.evalScriptlet("FormatImpl.new").toJava(Object.class) instanceof SimpleDateFormat);
}

@Test
public void testDuckTypeInterfaceImplGeneratesNoWarning() {
final String script =
"class Consumer1\n" +
" def self.accept(arg); puts self end\n" +
"end\n" +
"class Consumer2 < Consumer1; end\n" +
"class Consumer3 < Consumer1; end\n" ;

final ByteArrayOutputStream output = new ByteArrayOutputStream();

RubyInstanceConfig config = new RubyInstanceConfig();
config.setOutput(new PrintStream(output));
config.setError(new PrintStream(output));

final Ruby runtime = Ruby.newInstance(config);
runtime.evalScriptlet(script);

runtime.evalScriptlet(
"coll = java.util.Collections.singleton(42)\n" +
"coll.forEach(Consumer1); coll.forEach(Consumer2); coll.forEach(Consumer3)"
);

assertEquals("Consumer1\nConsumer2\nConsumer3\n", output.toString()); // no "warning: already initialized constant ..."
}

@Test
public void testGetProxyClass() {
final ByteArrayOutputStream output = new ByteArrayOutputStream();

RubyInstanceConfig config = new RubyInstanceConfig();
config.setOutput(new PrintStream(output));
config.setError(new PrintStream(output)); // ignore warnings - we'll be replacing an internal Java proxy constant

final Ruby runtime = Ruby.newInstance(config);

final Object klass = Java.getProxyClass(runtime, java.lang.System.class); // Java::JavaLang::System

final RubyModule dummySystemProxy = RubyModule.newModule(runtime); // replace Java::JavaLang::System with smt different
Java.setProxyClass(runtime, runtime.getClassFromPath("Java::JavaLang"), "System", dummySystemProxy, true);

assertSame(klass, Java.getProxyClass(runtime, java.lang.System.class));

// asserting here that the Java.setProxyConstantInJavaPackage path only executes once and not repeatedly
assertSame(dummySystemProxy, runtime.getClassFromPath("Java::JavaLang::System"));
}
}

0 comments on commit 13309db

Please sign in to comment.