Skip to content

Commit

Permalink
[ji] lazy loading of Java (proxy) class extensions (#5161)
Browse files Browse the repository at this point in the history
* [ji] lazy loading Java (proxy) class extensions as they're being used

* of course, need to track Java extension loading per runtime

* [ji] its for the best to have -Xji.load.lazy=false switch available

* [refactor] less confusing proxy class/module local variable name
  • Loading branch information
kares committed May 18, 2018
1 parent 3c0e10b commit 262bea3
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 128 deletions.
8 changes: 8 additions & 0 deletions core/src/main/java/org/jruby/Ruby.java
Expand Up @@ -4826,6 +4826,12 @@ public RubyClass getData() {
return dataClass;
}

/**
* @return Class -> extension initializer map
* @note Internal API, subject to change!
*/
public Map<Class, Consumer<RubyModule>> getJavaExtensionDefinitions() { return javaExtensionDefinitions; }

@Deprecated
private static final RecursiveFunctionEx<RecursiveFunction> LEGACY_RECURSE = new RecursiveFunctionEx<RecursiveFunction>() {
@Override
Expand Down Expand Up @@ -5296,6 +5302,8 @@ protected TypePopulator computeValue(Class<?> type) {

private volatile MRIRecursionGuard mriRecursionGuard;

private final Map<Class, Consumer<RubyModule>> javaExtensionDefinitions = new WeakHashMap<>(); // caller-syncs

// For strptime processing we cache the parsed format strings since most applications
// reuse the same formats over and over. This is also unbounded (for now) since all applications
// I know of use very few of them. Even if there are many the size of these lists are modest.
Expand Down
Expand Up @@ -48,6 +48,7 @@
import org.jruby.RubyModule;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.Unrescuable;
import org.jruby.javasupport.ext.JavaExtensions;
import org.jruby.runtime.Helpers;
import org.jruby.util.ArraySupport;
import org.jruby.util.Loader;
Expand Down Expand Up @@ -124,7 +125,9 @@ public JavaClass computeValue(Class<?> klass) {
*/
@Override
public synchronized RubyModule computeValue(Class<?> klass) {
return Java.createProxyClassForClass(runtime, klass);
RubyModule proxyKlass = Java.createProxyClassForClass(runtime, klass);
JavaExtensions.define(runtime, klass, proxyKlass); // (lazy) load extensions
return proxyKlass;
}
});

Expand Down
36 changes: 36 additions & 0 deletions core/src/main/java/org/jruby/javasupport/ext/JavaExtensions.java
@@ -0,0 +1,36 @@
package org.jruby.javasupport.ext;

import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.util.cli.Options;

import java.util.function.Consumer;

/**
* Lazy Java class extensions initialization.
*
* @note Internal API
* @author kares
*/
public class JavaExtensions {

private static final boolean LAZY = Options.JI_LOAD_LAZY.load();;

private JavaExtensions() { /* hidden */ }

static void put(final Ruby runtime, Class javaClass, Consumer<RubyModule> proxyClass) {
if (!LAZY) {
proxyClass.accept( org.jruby.javasupport.Java.getProxyClass(runtime, javaClass) );
return;
}
Object previous = runtime.getJavaExtensionDefinitions().put(javaClass, proxyClass);
assert previous == null;
}

public static void define(final Ruby runtime, final Class javaClass, final RubyModule proxyClass) {
runtime.getJavaExtensionDefinitions().getOrDefault(javaClass, NOOP).accept(proxyClass);
}

private static final Consumer<RubyModule> NOOP = (noop) -> { /* no extensions */ };

}
18 changes: 9 additions & 9 deletions core/src/main/java/org/jruby/javasupport/ext/JavaIo.java
Expand Up @@ -32,7 +32,6 @@
import org.jruby.RubyIO;
import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.javasupport.Java;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

Expand All @@ -47,16 +46,17 @@
public abstract class JavaIo {

public static void define(final Ruby runtime) {
RubyModule proxyClass;
JavaExtensions.put(runtime, java.io.InputStream.class, (proxyClass) -> {
proxyClass.addMethodInternal("to_io", new InputStreamToIO(proxyClass));
});

proxyClass = Java.getProxyClass(runtime, java.io.InputStream.class);
proxyClass.addMethodInternal("to_io", new InputStreamToIO(proxyClass));
JavaExtensions.put(runtime, java.io.OutputStream.class, (proxyClass) -> {
proxyClass.addMethodInternal("to_io", new OutputStreamToIO(proxyClass));
});

proxyClass = Java.getProxyClass(runtime, java.io.OutputStream.class);
proxyClass.addMethodInternal("to_io", new OutputStreamToIO(proxyClass));

proxyClass = Java.getProxyClass(runtime, java.nio.channels.Channel.class);
proxyClass.addMethodInternal("to_io", new ChannelToIO(proxyClass));
JavaExtensions.put(runtime, java.nio.channels.Channel.class, (proxyClass) -> {
proxyClass.addMethodInternal("to_io", new ChannelToIO(proxyClass));
});
}

private static final class InputStreamToIO extends JavaMethod.JavaMethodZeroOrOne {
Expand Down
113 changes: 54 additions & 59 deletions core/src/main/java/org/jruby/javasupport/ext/JavaLang.java
Expand Up @@ -59,35 +59,31 @@
public abstract class JavaLang {

public static void define(final Ruby runtime) {
Iterable.define(runtime);
Comparable.define(runtime);
Throwable.define(runtime);
Runnable.define(runtime);
Character.define(runtime);
Number.define(runtime);
Class.define(runtime);
ClassLoader.define(runtime);
JavaExtensions.put(runtime, java.lang.Iterable.class, (proxyClass) -> Iterable.define(runtime, proxyClass));
JavaExtensions.put(runtime, java.lang.Comparable.class, (proxyClass) -> Comparable.define(runtime, proxyClass));
JavaExtensions.put(runtime, java.lang.Throwable.class, (proxyClass) -> Throwable.define(runtime, (RubyClass) proxyClass));
JavaExtensions.put(runtime, java.lang.Runnable.class, (proxyClass) -> Runnable.define(runtime, proxyClass));
JavaExtensions.put(runtime, java.lang.Character.class, (proxyClass) -> Character.define(runtime, (RubyClass) proxyClass));
JavaExtensions.put(runtime, java.lang.Number.class, (proxyClass) -> Number.define(runtime, (RubyClass) proxyClass));
JavaExtensions.put(runtime, java.lang.Class.class, (proxyClass) -> Class.define(runtime, (RubyClass) proxyClass));
JavaExtensions.put(runtime, java.lang.ClassLoader.class, (proxyClass) -> ClassLoader.define(runtime, (RubyClass) proxyClass));
// Java::byte[].class_eval ...
final RubyModule byteArray = Java.getProxyClass(runtime, new byte[0].getClass());
byteArray.addMethod("ubyte_get", new UByteGet(byteArray));
byteArray.addMethod("ubyte_set", new UByteSet(byteArray));

final RubyModule String = Java.getProxyClass(runtime, java.lang.String.class);
String.defineAlias("to_str", "to_s");

final RubyModule Number = Java.getProxyClass(runtime, java.lang.Number.class);
Number.defineAlias("to_int", "longValue");
Number.defineAlias("to_f", "doubleValue");
JavaExtensions.put(runtime, new byte[0].getClass(), (byteArray) -> {
byteArray.addMethod("ubyte_get", new UByteGet(byteArray));
byteArray.addMethod("ubyte_set", new UByteSet(byteArray));
});
JavaExtensions.put(runtime, java.lang.String.class, (proxyClass) -> {
proxyClass.defineAlias("to_str", "to_s");
});
}

@JRubyModule(name = "Java::JavaLang::Iterable", include = "Enumerable")
public static class Iterable {

static RubyModule define(final Ruby runtime) {
final RubyModule Iterable = Java.getProxyClass(runtime, java.lang.Iterable.class);
Iterable.includeModule( runtime.getEnumerable() ); // include Enumerable
Iterable.defineAnnotatedMethods(Iterable.class);
return Iterable;
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.includeModule( runtime.getEnumerable() ); // include Enumerable
proxy.defineAnnotatedMethods(Iterable.class);
return proxy;
}

@JRubyMethod
Expand Down Expand Up @@ -181,11 +177,10 @@ public static IRubyObject count(final ThreadContext context, final IRubyObject s
@JRubyClass(name = "Java::JavaLang::Comparable", include = "Comparable")
public static class Comparable {

static RubyModule define(final Ruby runtime) {
final RubyModule Comparable = Java.getProxyClass(runtime, java.lang.Comparable.class);
Comparable.includeModule( runtime.getComparable() ); // include Comparable
Comparable.defineAnnotatedMethods(Comparable.class);
return Comparable;
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.includeModule( runtime.getComparable() ); // include Comparable
proxy.defineAnnotatedMethods(Comparable.class);
return proxy;
}

@JRubyMethod(name = "<=>")
Expand All @@ -210,10 +205,9 @@ public static IRubyObject cmp(final ThreadContext context, final IRubyObject sel
@JRubyClass(name = "Java::JavaLang::Throwable")
public static class Throwable {

static RubyModule define(final Ruby runtime) {
final RubyModule Throwable = Java.getProxyClass(runtime, java.lang.Throwable.class);
Throwable.defineAnnotatedMethods(Throwable.class);
return Throwable;
static RubyModule define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(Throwable.class);
return proxy;
}

@JRubyMethod // stackTrace => backtrace
Expand Down Expand Up @@ -279,10 +273,9 @@ private static boolean checkNativeException(IRubyObject self, IRubyObject other)
@JRubyModule(name = "Java::JavaLang::Runnable")
public static class Runnable {

static RubyModule define(final Ruby runtime) {
final RubyModule Runnable = Java.getProxyClass(runtime, java.lang.Runnable.class);
Runnable.defineAnnotatedMethods(Runnable.class);
return Runnable;
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.defineAnnotatedMethods(Runnable.class);
return proxy;
}

@JRubyMethod
Expand Down Expand Up @@ -328,10 +321,13 @@ protected final IRubyObject doYield(ThreadContext context, Block block, IRubyObj
@JRubyClass(name = "Java::JavaLang::Number")
public static class Number {

static RubyClass define(final Ruby runtime) {
final RubyModule Number = Java.getProxyClass(runtime, java.lang.Number.class);
Number.defineAnnotatedMethods(Number.class);
return (RubyClass) Number;
static RubyClass define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(Number.class);

proxy.defineAlias("to_int", "longValue");
proxy.defineAlias("to_f", "doubleValue");

return proxy;
}

@JRubyMethod(name = "to_f")
Expand Down Expand Up @@ -409,24 +405,25 @@ public static IRubyObject coerce(final ThreadContext context, final IRubyObject
@JRubyClass(name = "Java::JavaLang::Character")
public static class Character {

static RubyClass define(final Ruby runtime) {
final RubyModule Character = Java.getProxyClass(runtime, java.lang.Character.class);
Character.defineAnnotatedMethods(Character.class);
return (RubyClass) Character;
static RubyClass define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(Character.class);
return proxy;
}

@JRubyMethod(name = "java_identifier_start?", meta = true)
public static IRubyObject java_identifier_start_p(final IRubyObject self, final IRubyObject num) {
return self.getRuntime().newBoolean( java.lang.Character.isJavaIdentifierStart(to_char(num)) );
public static IRubyObject java_identifier_start_p(final ThreadContext context, final IRubyObject self,
final IRubyObject num) {
return context.runtime.newBoolean( java.lang.Character.isJavaIdentifierStart(int_char(num)) );
}

@JRubyMethod(name = "java_identifier_part?", meta = true)
public static IRubyObject java_identifier_part_p(final IRubyObject self, final IRubyObject num) {
return self.getRuntime().newBoolean( java.lang.Character.isJavaIdentifierPart(to_char(num)) );
public static IRubyObject java_identifier_part_p(final ThreadContext context, final IRubyObject self,
final IRubyObject num) {
return context.runtime.newBoolean( java.lang.Character.isJavaIdentifierPart(int_char(num)) );
}

private static char to_char(final IRubyObject num) {
return (java.lang.Character) num.toJava(java.lang.Character.TYPE);
private static int int_char(IRubyObject num) { // str.ord -> Fixnum
return num.toJava(java.lang.Character.TYPE);
}

@JRubyMethod(name = "to_i")
Expand All @@ -440,11 +437,10 @@ public static IRubyObject to_i(final ThreadContext context, final IRubyObject se
@JRubyClass(name = "Java::JavaLang::Class")
public static class Class {

static RubyClass define(final Ruby runtime) {
final RubyModule Class = Java.getProxyClass(runtime, java.lang.Class.class);
Class.includeModule( runtime.getComparable() ); // include Comparable
Class.defineAnnotatedMethods(Class.class);
return (RubyClass) Class;
static RubyClass define(final Ruby runtime, final RubyClass proxy) {
proxy.includeModule( runtime.getComparable() ); // include Comparable
proxy.defineAnnotatedMethods(Class.class);
return proxy;
}

@JRubyMethod(name = "ruby_class")
Expand Down Expand Up @@ -602,10 +598,9 @@ public static IRubyObject static_p(final IRubyObject self) {
@JRubyClass(name = "Java::JavaLang::ClassLoader")
public static class ClassLoader {

static RubyModule define(final Ruby runtime) {
final RubyModule ClassLoader = Java.getProxyClass(runtime, java.lang.ClassLoader.class);
ClassLoader.defineAnnotatedMethods(ClassLoader.class);
return ClassLoader;
static RubyModule define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(ClassLoader.class);
return proxy;
}

@JRubyMethod
Expand Down
28 changes: 12 additions & 16 deletions core/src/main/java/org/jruby/javasupport/ext/JavaLangReflect.java
Expand Up @@ -31,7 +31,6 @@
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.Java;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
Expand All @@ -50,18 +49,17 @@
public abstract class JavaLangReflect {

public static void define(final Ruby runtime) {
Constructor.define(runtime);
Field.define(runtime);
Method.define(runtime);
JavaExtensions.put(runtime, java.lang.reflect.Constructor.class, (proxyClass) -> Constructor.define(runtime, (RubyClass) proxyClass));
JavaExtensions.put(runtime, java.lang.reflect.Field.class, (proxyClass) -> Field.define(runtime, (RubyClass) proxyClass));
JavaExtensions.put(runtime, java.lang.reflect.Method.class, (proxyClass) -> Method.define(runtime, (RubyClass) proxyClass));
}

@JRubyClass(name = "Java::JavaLangReflect::Constructor")
public static class Constructor {

static RubyClass define(final Ruby runtime) {
final RubyModule Constructor = Java.getProxyClass(runtime, java.lang.reflect.Constructor.class);
Constructor.defineAnnotatedMethods(Constructor.class);
return (RubyClass) Constructor;
static RubyClass define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(Constructor.class);
return proxy;
}

@JRubyMethod
Expand Down Expand Up @@ -120,10 +118,9 @@ public static IRubyObject static_p(final IRubyObject self) {
@JRubyClass(name = "Java::JavaLangReflect::Method")
public static class Method {

static RubyClass define(final Ruby runtime) {
final RubyModule Method = Java.getProxyClass(runtime, java.lang.reflect.Method.class);
Method.defineAnnotatedMethods(Method.class);
return (RubyClass) Method;
static RubyClass define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(Method.class);
return proxy;
}

@JRubyMethod
Expand Down Expand Up @@ -201,10 +198,9 @@ public static IRubyObject static_p(final IRubyObject self) {
@JRubyClass(name = "Java::JavaLangReflect::Field")
public static class Field {

static RubyClass define(final Ruby runtime) {
final RubyModule Field = Java.getProxyClass(runtime, java.lang.reflect.Field.class);
Field.defineAnnotatedMethods(Field.class);
return (RubyClass) Field;
static RubyClass define(final Ruby runtime, final RubyClass proxy) {
proxy.defineAnnotatedMethods(Field.class);
return proxy;
}

@JRubyMethod // alias value_type name
Expand Down
8 changes: 3 additions & 5 deletions core/src/main/java/org/jruby/javasupport/ext/JavaNet.java
Expand Up @@ -32,7 +32,6 @@
import org.jruby.RubyIO;
import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.javasupport.Java;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
Expand All @@ -52,10 +51,9 @@
public abstract class JavaNet {

public static void define(final Ruby runtime) {
RubyModule proxyClass;

proxyClass = Java.getProxyClass(runtime, java.net.URL.class);
proxyClass.addMethodInternal("open", new URLOpenMethod(proxyClass));
JavaExtensions.put(runtime, java.net.URL.class, (proxyClass) -> {
proxyClass.addMethodInternal("open", new URLOpenMethod(proxyClass));
});
}

private static final class URLOpenMethod extends JavaMethod.JavaMethodZeroOrNBlock {
Expand Down

0 comments on commit 262bea3

Please sign in to comment.