Skip to content

Commit c8ea8bf

Browse files
committed
Start prototyping user-definable Java<=>Ruby conversions.
1 parent 9ac895a commit c8ea8bf

File tree

9 files changed

+124
-6
lines changed

9 files changed

+124
-6
lines changed

core/src/main/java/org/jruby/RubyBasicObject.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,15 @@ public Class getJavaClass() {
668668
return getClass();
669669
}
670670

671+
/**
672+
* @see IRubyObject#canCoerceTo(Class)
673+
*/
674+
@Override
675+
public boolean canCoerceTo(Class target) {
676+
return target.equals(getJavaClass()) ||
677+
getRuntime().getJavaSupport().getRegisteredConverter(getMetaClass().getRealClass(), target) != null;
678+
}
679+
671680
/** rb_to_id
672681
*
673682
* Will try to convert this object to a String using the Ruby
@@ -835,6 +844,11 @@ public Object toJava(Class target) {
835844
}
836845
} else if (target.isAssignableFrom(getClass())) {
837846
return this;
847+
} else {
848+
JavaUtil.RubyToJava converter = getRuntime().getJavaSupport().getRegisteredConverter(getMetaClass().getRealClass(), target);
849+
if (converter != null) {
850+
return converter.convert(getRuntime().getCurrentContext(), this);
851+
}
838852
}
839853

840854
throw getRuntime().newTypeError("cannot convert instance of " + getClass() + " to " + target);

core/src/main/java/org/jruby/RubyNumeric.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,12 @@ public IRubyObject conjugate(ThreadContext context) {
12081208

12091209
@Override
12101210
public Object toJava(Class target) {
1211-
return JavaUtil.getNumericConverter(target).coerce(this, target);
1211+
JavaUtil.NumericConverter converter = JavaUtil.getNumericConverter(target);
1212+
if (converter == JavaUtil.NUMERIC_TO_OTHER) {
1213+
return super.toJava(target);
1214+
}
1215+
1216+
return converter.coerce(this, target);
12121217
}
12131218

12141219
public static class InvalidIntegerException extends NumberFormatException {

core/src/main/java/org/jruby/ir/operands/UndefinedValue.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,4 +399,9 @@ public String toString() {
399399
public void visit(IRVisitor visitor) {
400400
visitor.UndefinedValue(this);
401401
}
402+
403+
@Override
404+
public boolean canCoerceTo(Class target) {
405+
return false;
406+
}
402407
}

core/src/main/java/org/jruby/java/dispatch/CallableSelector.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,13 @@ private static boolean exactMatch(ParameterTypes paramTypes, IRubyObject... args
295295

296296
private static Matcher EXACT = new Matcher() {
297297
public boolean match(Class type, IRubyObject arg) {
298-
return type.equals(argClass(arg))
299-
|| (type.isPrimitive() && CodegenUtils.getBoxType(type) == argClass(arg));
298+
if (arg == null) {
299+
if (type.isPrimitive()) return false;
300+
return true;
301+
} else if (arg.canCoerceTo(type)) {
302+
return true;
303+
}
304+
return false;
300305
}
301306
};
302307

core/src/main/java/org/jruby/javasupport/JavaSupport.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,10 @@ public abstract class JavaSupport {
111111
public abstract ClassValue<Map<String, AssignedName>> getStaticAssignedNames();
112112

113113
public abstract ClassValue<Map<String, AssignedName>> getInstanceAssignedNames();
114+
115+
public abstract void registerConverter(RubyClass source, Class target, JavaUtil.RubyToJava converter);
116+
117+
public abstract void registerConverter(RubyClass source, Class target, IRubyObject converter);
118+
119+
public abstract JavaUtil.RubyToJava getRegisteredConverter(RubyClass source, Class target);
114120
}

core/src/main/java/org/jruby/javasupport/JavaSupportImpl.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@
3333
***** END LICENSE BLOCK *****/
3434
package org.jruby.javasupport;
3535

36+
import org.jruby.java.proxies.JavaProxy;
3637
import org.jruby.javasupport.binding.AssignedName;
38+
import org.jruby.runtime.ThreadContext;
39+
import org.jruby.runtime.callsite.CachingCallSite;
40+
import org.jruby.runtime.callsite.FunctionalCachingCallSite;
41+
import org.jruby.runtime.opto.Invalidator;
42+
import org.jruby.runtime.opto.OptoFactory;
43+
import org.jruby.util.TypeConverter;
3744
import org.jruby.util.collections.MapBasedClassValue;
3845
import java.lang.reflect.Constructor;
3946
import java.lang.reflect.InvocationTargetException;
@@ -42,6 +49,7 @@
4249
import java.util.HashMap;
4350
import java.util.Map;
4451
import java.util.Set;
52+
import java.util.WeakHashMap;
4553

4654
import org.jruby.Ruby;
4755
import org.jruby.RubyClass;
@@ -75,6 +83,8 @@ public IRubyObject allocateProxy(Object javaObject, RubyClass clazz) {
7583
private final ClassValue<Map<String, AssignedName>> staticAssignedNames;
7684
private final ClassValue<Map<String, AssignedName>> instanceAssignedNames;
7785
private static final Constructor<? extends ClassValue> CLASS_VALUE_CONSTRUCTOR;
86+
private final Map<RubyClass, Map<Class, JavaUtil.RubyToJava>> registeredConverters;
87+
private final Invalidator registeredConverterInvalidator;
7888

7989
static {
8090
Constructor constructor = null;
@@ -167,6 +177,9 @@ public Map<String, AssignedName> computeValue(Class<?> cls) {
167177
catch (InvocationTargetException ex) {
168178
throw new RuntimeException(ex.getTargetException());
169179
}
180+
181+
registeredConverters = new WeakHashMap<>();
182+
registeredConverterInvalidator = OptoFactory.newConstantInvalidator();
170183
}
171184

172185
public Class loadJavaClass(String className) throws ClassNotFoundException {
@@ -372,6 +385,37 @@ public ClassValue<Map<String, AssignedName>> getInstanceAssignedNames() {
372385
return instanceAssignedNames;
373386
}
374387

388+
public synchronized void registerConverter(RubyClass source, Class target, JavaUtil.RubyToJava converter) {
389+
Map<Class, JavaUtil.RubyToJava> classToConverter = registeredConverters.get(source);
390+
if (classToConverter == null) {
391+
registeredConverters.put(source, classToConverter = new WeakHashMap<Class, JavaUtil.RubyToJava>());
392+
}
393+
classToConverter.put(target, converter);
394+
395+
registeredConverterInvalidator.invalidate();
396+
}
397+
398+
public synchronized JavaUtil.RubyToJava getRegisteredConverter(RubyClass source, Class target) {
399+
Map<Class, JavaUtil.RubyToJava> classToConverter = registeredConverters.get(source);
400+
if (classToConverter == null) return null;
401+
402+
return classToConverter.get(target);
403+
}
404+
405+
public void registerConverter(RubyClass source, Class target, final IRubyObject converter) {
406+
JavaUtil.RubyToJava rubyToJava = new JavaUtil.RubyToJava() {
407+
CachingCallSite site = new FunctionalCachingCallSite("call");
408+
@Override
409+
public Object convert(ThreadContext context, IRubyObject object) {
410+
Object result = site.call(runtime.getCurrentContext(), converter, converter, object);
411+
if (result instanceof JavaProxy) result = ((JavaProxy)result).getObject();
412+
return result;
413+
}
414+
};
415+
416+
registerConverter(source, target, rubyToJava);
417+
}
418+
375419
@Deprecated
376420
private volatile Map<Object, Object[]> javaObjectVariables;
377421

core/src/main/java/org/jruby/javasupport/JavaUtil.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,13 +475,21 @@ public static Set<String> getRubyNamesForJavaName(String javaName, List<Method>
475475

476476
return nameSet;
477477
}
478+
479+
public interface RubyToJava {
480+
public Object convert(ThreadContext context, IRubyObject object);
481+
}
478482

479483
public static abstract class JavaConverter {
480484
private final Class type;
481485
public JavaConverter(Class type) {this.type = type;}
482486
public abstract IRubyObject convert(Ruby runtime, Object object);
483-
public abstract IRubyObject get(Ruby runtime, Object array, int i);
484-
public abstract void set(Ruby runtime, Object array, int i, IRubyObject value);
487+
public IRubyObject get(Ruby runtime, Object array, int i) {
488+
return convert(runtime, Array.get(array, i));
489+
}
490+
public void set(Ruby runtime, Object array, int i, IRubyObject value) {
491+
Array.set(array, i, value.toJava(type));
492+
}
485493
public String toString() {return type.getName() + " converter";}
486494
}
487495

@@ -899,7 +907,7 @@ public Object coerce(RubyNumeric numeric, Class target) {
899907
}
900908
}
901909
};
902-
private static final NumericConverter NUMERIC_TO_OTHER = new NumericConverter() {
910+
public static final NumericConverter NUMERIC_TO_OTHER = new NumericConverter() {
903911
public Object coerce(RubyNumeric numeric, Class target) {
904912
if (target.isAssignableFrom(numeric.getClass())) {
905913
// just return as-is, since we can't do any coercion

core/src/main/java/org/jruby/javasupport/JavaUtilities.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.jruby.javasupport;
22

3+
import org.jruby.RubyClass;
4+
import org.jruby.RubyProc;
35
import org.jruby.anno.JRubyMethod;
46
import org.jruby.anno.JRubyModule;
7+
import org.jruby.runtime.Block;
58
import org.jruby.runtime.ThreadContext;
69
import org.jruby.runtime.Visibility;
710
import org.jruby.runtime.builtin.IRubyObject;
@@ -53,4 +56,27 @@ public static IRubyObject get_top_level_proxy_or_package(ThreadContext context,
5356
public static IRubyObject get_proxy_or_package_under_package(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
5457
return Java.get_proxy_or_package_under_package(context, recv, arg0, arg1);
5558
}
59+
60+
@JRubyMethod(module = true, visibility = Visibility.PRIVATE)
61+
public static IRubyObject register_converter(ThreadContext context, IRubyObject recv, IRubyObject source, IRubyObject target, IRubyObject converter, Block block) {
62+
if (block.isGiven()) {
63+
context.runtime.getWarnings().warn("block passed to `reigster_converter' ignored");
64+
}
65+
66+
if (!(source instanceof RubyClass)) {
67+
throw context.runtime.newTypeError(source, context.runtime.getClassClass());
68+
}
69+
70+
Object maybeTarget = target.dataGetStruct();
71+
if (!(maybeTarget instanceof Class)) context.runtime.newTypeError(target, Java.getProxyClass(context.runtime, Class.class));
72+
73+
context.runtime.getJavaSupport().registerConverter((RubyClass)source, (Class)maybeTarget, converter);
74+
75+
return context.nil;
76+
}
77+
78+
@JRubyMethod(module = true, visibility = Visibility.PRIVATE)
79+
public static IRubyObject register_converter(ThreadContext context, IRubyObject recv, IRubyObject source, IRubyObject target, Block block) {
80+
return register_converter(context, recv, source, target, RubyProc.newProc(context.runtime, block, Block.Type.LAMBDA), Block.NULL_BLOCK);
81+
}
5682
}

core/src/main/java/org/jruby/runtime/builtin/IRubyObject.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ public interface IRubyObject {
186186
* @return Class
187187
*/
188188
Class getJavaClass();
189+
190+
/**
191+
* Return true if this object can coerce to the given target Java class, false otherwise.
192+
*/
193+
boolean canCoerceTo(Class target);
189194

190195
/**
191196
* Convert the object into a symbol name if possible.

0 commit comments

Comments
 (0)