diff --git a/core/src/main/java/org/jruby/java/proxies/ArrayJavaProxy.java b/core/src/main/java/org/jruby/java/proxies/ArrayJavaProxy.java index c3610ebc0ca..6ee22f16e3c 100644 --- a/core/src/main/java/org/jruby/java/proxies/ArrayJavaProxy.java +++ b/core/src/main/java/org/jruby/java/proxies/ArrayJavaProxy.java @@ -491,7 +491,12 @@ public IRubyObject component_type(ThreadContext context) { @JRubyMethod public RubyString inspect(ThreadContext context) { - return RubyString.newString(context.runtime, arrayToString()); + return inspect(context.runtime, null); // -> toStringImpl + } + + @Override + CharSequence toStringImpl(final Ruby runtime, final IRubyObject opts) { + return arrayToString(); } @Override diff --git a/core/src/main/java/org/jruby/java/proxies/ConcreteJavaProxy.java b/core/src/main/java/org/jruby/java/proxies/ConcreteJavaProxy.java index 5b07f1a525b..e87e51466fc 100644 --- a/core/src/main/java/org/jruby/java/proxies/ConcreteJavaProxy.java +++ b/core/src/main/java/org/jruby/java/proxies/ConcreteJavaProxy.java @@ -1,8 +1,8 @@ package org.jruby.java.proxies; -import org.jruby.Ruby; -import org.jruby.RubyClass; -import org.jruby.RubyModule; +import org.jruby.*; +import org.jruby.anno.JRubyMethod; +import org.jruby.ast.util.ArgsUtil; import org.jruby.internal.runtime.methods.DynamicMethod; import org.jruby.javasupport.Java; import org.jruby.runtime.Block; @@ -12,6 +12,16 @@ import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.callsite.CacheEntry; +import org.jruby.runtime.callsite.CachingCallSite; +import org.jruby.runtime.callsite.FunctionalCachingCallSite; +import org.jruby.util.CodegenUtils; +import org.jruby.util.StringSupport; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; public class ConcreteJavaProxy extends JavaProxy { @@ -235,4 +245,123 @@ else if ( type.isAssignableFrom(clazz) ) { throw getRuntime().newTypeError("failed to coerce " + clazz.getName() + " to " + type.getName()); } + + @JRubyMethod + public IRubyObject inspect(ThreadContext context) { + return inspect(context.runtime, null); // -> toStringImpl + } + + @Override + CharSequence toStringImpl(final Ruby runtime, final IRubyObject opts) { + final ThreadContext context = runtime.getCurrentContext(); + DynamicMethod toString = toStringIfNotFromObject(context); + if (toString == null) return hashyInspect().toString(); + IRubyObject str = toString.call(context, this, getMetaClass(), "toString"); + return str == context.nil ? "null" : str.convertToString(); // we don't return a nil (unlike to_s) + } + + private transient CachingCallSite toStringSite; + + private DynamicMethod toStringIfNotFromObject(final ThreadContext context) { + if (toStringSite == null) toStringSite = new FunctionalCachingCallSite("toString"); + + CacheEntry cache = toStringSite.retrieveCache(getMetaClass()); + + return (cache.method.getImplementationClass() != context.runtime.getJavaSupport().getObjectClass()) ? cache.method : null; + } + + private StringBuilder reflectiveToString(final ThreadContext context, final RubyHash opts) { + String[] excludedFields = null; + if (opts != null) { // NOTE: support nested excludes?!? + IRubyObject[] args = ArgsUtil.extractKeywordArgs(context, opts, "exclude"); + if (args[0] instanceof RubyArray) { + excludedFields = (String[]) ((RubyArray) args[0]).toArray(StringSupport.EMPTY_STRING_ARRAY); + } + } + + final StringBuilder buffer = new StringBuilder(192); + + final Object obj = getObject(); + Class clazz = obj.getClass(); + + // TODO use meta-class name? + buffer.append("#<").append(getMetaClass().getRealClass().getName()) + .append(":0x").append(Integer.toHexString(inspectHashCode())); + + final Ruby runtime = context.runtime; + + if (runtime.isInspecting(obj)) return buffer.append(" ...>"); + + try { + runtime.registerInspecting(obj); + + char sep = appendFields(context, buffer, obj, clazz, '\0', excludedFields); + + while (clazz.getSuperclass() != null) { + clazz = clazz.getSuperclass(); + sep = appendFields(context, buffer, obj, clazz, sep, excludedFields); + } + + return buffer.append('>'); + } + finally { + runtime.unregisterInspecting(obj); + } + } + + private static char appendFields(final ThreadContext context, final StringBuilder buffer, + Object obj, Class clazz, char sep, final String[] excludedFields) { + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + + for (final Field field : fields) { + if (accept(field, excludedFields)) { + if (sep == '\0') sep = ','; + else buffer.append(sep); + buffer.append(' '); + + try { + buffer.append(field.getName()).append('=').append(toStringValue(context, field.get(obj))); + } + catch (IllegalAccessException ex) { throw new AssertionError(ex); } // we've set accessible + } + } + return sep; + } + + private static CharSequence toStringValue(final ThreadContext context, final Object value) { + if (value == null) return "null"; + if (value instanceof CharSequence) return (CharSequence) value; // String + final Class klass = value.getClass(); + // take short-cuts for known Java toString impls : + if (klass.isPrimitive() || klass.isEnum()) return value.toString(); + if (klass == Integer.class || klass == Long.class || + klass == Short.class || klass == Byte.class || + klass == Double.class || klass == Float.class || + klass == Boolean.class || klass == Character.class) return value.toString(); + //if (klass.isSynthetic()) return value.toString(); // TODO skip - unwrap class!?! + if (value instanceof IRubyObject) { + if (value instanceof JavaProxy) { + return ((JavaProxy) value).toStringImpl(context.runtime, null); + } + return ((IRubyObject) value).inspect().convertToString(); + } + return ((JavaProxy) Java.wrapJavaObject(context.runtime, value)).toStringImpl(context.runtime, null); + } + + private static final char INNER_CLASS_SEPARATOR_CHAR = '$'; + + private static boolean accept(final Field field, final String[] excludedFields) { + final int mod = field.getModifiers(); + if (Modifier.isTransient(mod) || Modifier.isStatic(mod)) return false; + + final String name = field.getName(); + if (name.indexOf(INNER_CLASS_SEPARATOR_CHAR) != -1) return false; + if (excludedFields != null && Arrays.binarySearch(excludedFields, name) >= 0) { + return false; + } + + return true; + } + } 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 81dc4b2d1e7..2a784b4ca5d 100644 --- a/core/src/main/java/org/jruby/java/proxies/JavaProxy.java +++ b/core/src/main/java/org/jruby/java/proxies/JavaProxy.java @@ -13,15 +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; -import org.jruby.RubyHash; -import org.jruby.RubyMethod; -import org.jruby.RubyModule; -import org.jruby.RubyObject; -import org.jruby.RubyUnboundMethod; +import org.jruby.*; import org.jruby.anno.JRubyMethod; import org.jruby.common.IRubyWarnings; import org.jruby.java.invokers.InstanceFieldGetter; @@ -435,6 +427,21 @@ protected int inspectHashCode() { return System.identityHashCode(object); } + @Override + public IRubyObject inspect() { + return inspect(getRuntime(), null); + } + + final RubyString inspect(final Ruby runtime, final IRubyObject opts) { + return RubyString.newString(runtime, toStringImpl(runtime, opts)); + } + + CharSequence toStringImpl(final Ruby runtime, final IRubyObject opts) { + return String.valueOf(object); + } + + // toString() dispatches the shared RubyObject to_s site, maybe setup a custom for (Concrete)JavaProxy? + private Method getMethod(ThreadContext context, String name, Class... argTypes) { try { return getObject().getClass().getMethod(name, argTypes); diff --git a/core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java b/core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java index 062e5b9c14d..e672803e279 100644 --- a/core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java +++ b/core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java @@ -426,14 +426,38 @@ public IRubyObject set_default_proc(IRubyObject proc) { return getOrCreateRubyHashMap().set_default_proc(proc); } - /** rb_hash_inspect + /** + * hash_inspect provided for compatibility if in a need to revert to pre 9.2 behaviour * + * MapJavaProxy.class_eval { public alias_method :inspect, :hash_inspect } + * + * @since 9.2 */ - @JRubyMethod(name = "inspect") - public IRubyObject inspect(ThreadContext context) { + @Deprecated // ... since 9.2 inspect on Java proxies means -> toString + @JRubyMethod(name = "hash_inspect", visibility = Visibility.PRIVATE) + public IRubyObject hash_inspect(ThreadContext context) { return getOrCreateRubyHashMap().inspect(context); } + @Deprecated // only for binary compatibility + public IRubyObject inspect(ThreadContext context) { return super.inspect(context); } + + /** + * hash_inspect provided for compatibility if in a need to revert to pre 9.2 behaviour + * + * MapJavaProxy.class_eval { public alias_method :inspect, :hash_inspect } + * + * @since 9.2 + */ + @Deprecated // ... since 9.2 to_s on Java proxies is aliased toString + @JRubyMethod(name = "hash_to_s", visibility = Visibility.PRIVATE) + public IRubyObject hash_to_s(ThreadContext context) { + return getOrCreateRubyHashMap().to_s(context); + } + + @Deprecated // only for binary compatibility + public IRubyObject to_s(ThreadContext context) { return super.to_s(); } + /** rb_hash_size * */ @@ -467,14 +491,6 @@ public RubyProc to_proc(ThreadContext context) { return (RubyProc) newProc; } - /** rb_hash_to_s - * - */ - @JRubyMethod(name = "to_s") - public IRubyObject to_s(ThreadContext context) { - return getOrCreateRubyHashMap().to_s(context); - } - /** rb_hash_rehash * */ diff --git a/core/src/main/java/org/jruby/javasupport/Java.java b/core/src/main/java/org/jruby/javasupport/Java.java index 5d3e972a411..674bcfa9199 100644 --- a/core/src/main/java/org/jruby/javasupport/Java.java +++ b/core/src/main/java/org/jruby/javasupport/Java.java @@ -91,7 +91,6 @@ import org.jruby.util.cli.Options; import org.jruby.util.collections.NonBlockingHashMapLong; -import static org.jruby.java.invokers.RubyToJavaInvoker.convertArguments; import static org.jruby.runtime.Visibility.*; @JRubyModule(name = "Java") @@ -119,14 +118,13 @@ public void load(Ruby runtime, boolean wrap) { // rewire ArrayJavaProxy superclass to point at Object, so it inherits Object behaviors final RubyClass ArrayJavaProxy = runtime.getClass("ArrayJavaProxy"); - ArrayJavaProxy.setSuperClass(runtime.getJavaSupport().getObjectJavaClass().getProxyClass()); + ArrayJavaProxy.setSuperClass(runtime.getJavaSupport().getObjectClass()); ArrayJavaProxy.includeModule(runtime.getEnumerable()); RubyClassPathVariable.createClassPathVariable(runtime); runtime.setJavaProxyClassFactory(JavaProxyClassFactory.createFactory()); - // modify ENV_JAVA to be a read/write version final Map systemProperties = new SystemPropertiesMap(); RubyClass proxyClass = (RubyClass) getProxyClass(runtime, SystemPropertiesMap.class); runtime.getObject().setConstantQuiet("ENV_JAVA", new MapJavaProxy(runtime, proxyClass, systemProperties)); @@ -184,9 +182,6 @@ public static RubyModule createJavaModule(final Ruby runtime) { // add all name-to-class mappings addNameClassMappings(runtime, runtime.getJavaSupport().getNameClassMap()); - // add some base Java classes everyone will need - runtime.getJavaSupport().setObjectJavaClass( JavaClass.get(runtime, Object.class) ); - return Java; } @@ -1409,21 +1404,16 @@ public Object invoke(Object proxy, Method method, Object[] nargs) throws Throwab final Ruby runtime = wrapper.getRuntime(); final ThreadContext context = runtime.getCurrentContext(); - //try { - switch ( length ) { - case 0 : - return Helpers.invoke(context, wrapper, methodName).toJava(method.getReturnType()); - case 1 : - IRubyObject arg = JavaUtil.convertJavaToUsableRubyObject(runtime, nargs[0]); - return Helpers.invoke(context, wrapper, methodName, arg).toJava(method.getReturnType()); - default : - IRubyObject[] args = JavaUtil.convertJavaArrayToRuby(runtime, nargs); - return Helpers.invoke(context, wrapper, methodName, args).toJava(method.getReturnType()); - } - //} - //catch (RuntimeException e) { - // e.printStackTrace(); throw e; - //} + switch ( length ) { + case 0 : + return Helpers.invoke(context, wrapper, methodName).toJava(method.getReturnType()); + case 1 : + IRubyObject arg = JavaUtil.convertJavaToUsableRubyObject(runtime, nargs[0]); + return Helpers.invoke(context, wrapper, methodName, arg).toJava(method.getReturnType()); + default : + IRubyObject[] args = JavaUtil.convertJavaArrayToRuby(runtime, nargs); + return Helpers.invoke(context, wrapper, methodName, args).toJava(method.getReturnType()); + } } final String proxyToString(final Object proxy) { diff --git a/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java b/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java index 11060f743a1..9dfc2fcb324 100644 --- a/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java +++ b/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java @@ -80,7 +80,7 @@ public static IRubyObject op_equal(IRubyObject recv, IRubyObject rhs) { return ((JavaObject)recv.dataGetStruct()).op_equal(rhs); } - @JRubyMethod + @Deprecated // NOTE: handled by JavaProxy hierarchy (with java.lang.Object#to_s) public static IRubyObject to_s(IRubyObject recv) { if (recv instanceof JavaProxy) { return JavaObject.to_s(recv.getRuntime(), ((JavaProxy) recv).getObject()); @@ -90,7 +90,7 @@ public static IRubyObject to_s(IRubyObject recv) { return ((RubyBasicObject) recv).to_s(); } - @JRubyMethod + @Deprecated // NOTE: handled by JavaProxy public static IRubyObject inspect(IRubyObject recv) { if (recv instanceof RubyBasicObject) { return ((RubyBasicObject) recv).hashyInspect(); diff --git a/core/src/main/java/org/jruby/javasupport/JavaSupport.java b/core/src/main/java/org/jruby/javasupport/JavaSupport.java index fc4e13a4181..cf7af3aaa5a 100644 --- a/core/src/main/java/org/jruby/javasupport/JavaSupport.java +++ b/core/src/main/java/org/jruby/javasupport/JavaSupport.java @@ -77,8 +77,10 @@ public abstract class JavaSupport { public abstract RubyClass getJavaObjectClass(); + @Deprecated // no longer used public abstract JavaClass getObjectJavaClass(); + @Deprecated // no longer set public abstract void setObjectJavaClass(JavaClass objectJavaClass); public abstract RubyClass getJavaArrayClass(); @@ -92,6 +94,11 @@ public abstract class JavaSupport { @Deprecated public abstract RubyModule getPackageModuleTemplate(); + /** + * @return Java::JavaLang::Object (proxy) class + */ + public abstract RubyClass getObjectClass(); + public abstract RubyClass getJavaProxyClass(); public abstract RubyClass getArrayJavaProxyCreatorClass(); diff --git a/core/src/main/java/org/jruby/javasupport/JavaSupportImpl.java b/core/src/main/java/org/jruby/javasupport/JavaSupportImpl.java index 41b6f93301f..0bdc7cbc706 100644 --- a/core/src/main/java/org/jruby/javasupport/JavaSupportImpl.java +++ b/core/src/main/java/org/jruby/javasupport/JavaSupportImpl.java @@ -88,8 +88,8 @@ private static final class UnfinishedProxy extends ReentrantLock { private RubyModule javaModule; private RubyModule javaUtilitiesModule; private RubyModule javaArrayUtilitiesModule; + private RubyClass objectClass; private RubyClass javaObjectClass; - private JavaClass objectJavaClass; private RubyClass javaClassClass; private RubyClass javaPackageClass; private RubyClass javaArrayClass; @@ -265,12 +265,23 @@ public RubyClass getJavaProxyConstructorClass() { return javaProxyConstructorClass = getJavaModule().getClass("JavaProxyConstructor"); } + @Override + public RubyClass getObjectClass() { + RubyClass clazz; + if ((clazz = objectClass) != null) return clazz; + return objectClass = (RubyClass) getProxyClassFromCache(java.lang.Object.class); + } + + private transient JavaClass objectJavaClass; + public JavaClass getObjectJavaClass() { - return objectJavaClass; + JavaClass clazz; + if ((clazz = objectJavaClass) != null) return clazz; + return objectJavaClass = getJavaClassFromCache(java.lang.Object.class); } public void setObjectJavaClass(JavaClass objectJavaClass) { - this.objectJavaClass = objectJavaClass; + assert false; /* no longer used */ } public RubyClass getJavaArrayClass() { diff --git a/core/src/main/java/org/jruby/javasupport/binding/Initializer.java b/core/src/main/java/org/jruby/javasupport/binding/Initializer.java index 27b77dfd0bc..a781059d5a3 100644 --- a/core/src/main/java/org/jruby/javasupport/binding/Initializer.java +++ b/core/src/main/java/org/jruby/javasupport/binding/Initializer.java @@ -486,14 +486,12 @@ static Map> getMethods(final Class javaClass) { // we scan all superclasses, but avoid adding superclass methods with // same name+signature as subclass methods (see JRUBY-3130) for ( Class klass = javaClass; klass != null; klass = klass.getSuperclass() ) { - // only add class's methods if it's public or we can set accessible - // (see JRUBY-4799) + // only add class's methods if it's public or we can set accessible (see JRUBY-4799) if (Modifier.isPublic(klass.getModifiers()) || JavaUtil.CAN_SET_ACCESSIBLE) { // for each class, scan declared methods for new signatures try { - // add methods, including static if this is the actual class, - // and replacing child methods with equivalent parent methods - addNewMethods(nameMethods, DECLARED_METHODS.get(klass), klass == javaClass, true); + // add methods, including static if this is the actual class + addNewMethods(nameMethods, DECLARED_METHODS.get(klass), klass == javaClass, false); } catch (SecurityException e) { /* ignored */ } } diff --git a/core/src/main/java/org/jruby/javasupport/ext/JavaLang.java b/core/src/main/java/org/jruby/javasupport/ext/JavaLang.java index f2066cb0027..575cd678f5e 100644 --- a/core/src/main/java/org/jruby/javasupport/ext/JavaLang.java +++ b/core/src/main/java/org/jruby/javasupport/ext/JavaLang.java @@ -34,7 +34,6 @@ import org.jruby.anno.JRubyModule; import org.jruby.ext.bigdecimal.RubyBigDecimal; import org.jruby.internal.runtime.methods.JavaMethod; -import org.jruby.javasupport.Java; import org.jruby.javasupport.JavaClass; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; @@ -45,6 +44,7 @@ import java.lang.reflect.Modifier; +import static org.jruby.javasupport.Java.getProxyClass; import static org.jruby.javasupport.JavaUtil.convertJavaToUsableRubyObject; import static org.jruby.javasupport.JavaUtil.isJavaObject; import static org.jruby.javasupport.JavaUtil.unwrapIfJavaObject; @@ -59,6 +59,9 @@ public abstract class JavaLang { public static void define(final Ruby runtime) { + // NOTE: used immediately as JavaLang boots from loading Java support : + Object.define(runtime, (RubyClass) getProxyClass(runtime, java.lang.Object.class)); + // JavaExtensions.put(runtime, java.lang.Object.class, (proxyClass) -> Object.define(runtime, (RubyClass) proxyClass)); 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)); @@ -77,6 +80,22 @@ public static void define(final Ruby runtime) { }); } + @JRubyClass(name = "Java::JavaLang::Object") + public static class Object { + + static RubyClass define(final Ruby runtime, final RubyClass proxy) { + proxy.defineAnnotatedMethods(Object.class); + return proxy; + } + + @JRubyMethod(name = "to_s") + public static IRubyObject to_s(final ThreadContext context, final IRubyObject self) { + final String str = self.toJava(java.lang.Object.class).toString(); + return str == null ? context.nil : context.runtime.newString(str); + } + + } + @JRubyModule(name = "Java::JavaLang::Iterable", include = "Enumerable") public static class Iterable { @@ -95,7 +114,7 @@ public static IRubyObject each(final ThreadContext context, final IRubyObject se java.lang.Iterable iterable = unwrapIfJavaObject(self); java.util.Iterator iterator = iterable.iterator(); while ( iterator.hasNext() ) { - final Object value = iterator.next(); + final java.lang.Object value = iterator.next(); block.yield(context, convertJavaToUsableRubyObject(runtime, value)); } return self; @@ -112,7 +131,7 @@ public static IRubyObject each_with_index(final ThreadContext context, final IRu final boolean arity2 = block.getSignature().arity() == Arity.TWO_ARGUMENTS; int i = 0; while ( iterator.hasNext() ) { final RubyInteger index = RubyFixnum.newFixnum(runtime, i++); - final Object value = iterator.next(); + final java.lang.Object value = iterator.next(); final IRubyObject rValue = convertJavaToUsableRubyObject(runtime, value); if ( arity2 ) { block.yieldSpecific(context, rValue, index); @@ -130,7 +149,7 @@ public static IRubyObject to_a(final ThreadContext context, final IRubyObject se java.lang.Iterable iterable = unwrapIfJavaObject(self); java.util.Iterator iterator = iterable.iterator(); while ( iterator.hasNext() ) { - final Object value = iterator.next(); + final java.lang.Object value = iterator.next(); ary.append( convertJavaToUsableRubyObject(runtime, value) ); } return ary; diff --git a/spec/java_integration/fixtures/JavaFields.java b/spec/java_integration/fixtures/JavaFields.java index 51c2cf104c0..18dacf2d229 100644 --- a/spec/java_integration/fixtures/JavaFields.java +++ b/spec/java_integration/fixtures/JavaFields.java @@ -3,19 +3,18 @@ import java.math.BigInteger; public class JavaFields { - public static String stringStaticField = "foo"; + public static String stringStaticField = "000"; public static byte byteStaticField = (byte)1; public static short shortStaticField = (short)2; - public static char charStaticField = (char)2; + public static char charStaticField = (char)'3'; public static int intStaticField = 4; - public static long longStaticField = 8; - public static float floatStaticField = 4.5f; - public static double doubleStaticField = 8.5; + public static long longStaticField = 5; + public static float floatStaticField = 6.0f; + public static double doubleStaticField = 7.2d; public static boolean trueStaticField = true; public static boolean falseStaticField = false; public static Object nullStaticField = null; - public static BigInteger bigIntegerStaticField = - new BigInteger("1234567890123456789012345678901234567890"); + public static BigInteger bigIntegerStaticField = new BigInteger("111111111111111111110"); public static Byte byteObjStaticField = Byte.valueOf(byteStaticField); public static Short shortObjStaticField = Short.valueOf(shortStaticField); @@ -30,19 +29,18 @@ public class JavaFields { public static String $LEADING = "leading"; public static Boolean TRAILING$ = Boolean.TRUE; - public String stringField = "foo"; + public String stringField = stringStaticField; public byte byteField = (byte)1; public short shortField = (short)2; - public char charField = (char)2; + public char charField = (char)'T'; public int intField = 4; - public long longField = 8; - public float floatField = 4.5f; - public double doubleField = 8.5; + public long longField = 5; + public float floatField = floatStaticField; + public double doubleField = doubleStaticField; public boolean trueField = true; public boolean falseField = false; - public Object nullField = null; - public BigInteger bigIntegerField = - new BigInteger("1234567890123456789012345678901234567890"); + public final Object nullField = null; + public BigInteger bigIntegerField = new BigInteger("111111111111111111111"); public Byte byteObjField = Byte.valueOf(byteField); public Short shortObjField = Short.valueOf(shortField); @@ -53,4 +51,8 @@ public class JavaFields { public Double doubleObjField = Double.valueOf(doubleField); public Boolean trueObjField = Boolean.TRUE; public Boolean falseObjField = Boolean.FALSE; + + Object field1 = this; + Object[] aryField2 = new Object[] { this }; + } diff --git a/spec/java_integration/fixtures/JavaFieldsExt.java b/spec/java_integration/fixtures/JavaFieldsExt.java new file mode 100644 index 00000000000..9b4a7e10989 --- /dev/null +++ b/spec/java_integration/fixtures/JavaFieldsExt.java @@ -0,0 +1,23 @@ +package java_integration.fixtures; + +import java.math.BigInteger; + +public class JavaFieldsExt extends JavaFields { + + public JavaFieldsExt(Object field1) { + this.field1 = field1; + field2List.add(field1); + } + + private JavaFieldsExt() { + this(null); + this.field1 = this; + } + + private final JavaFields field1Ext = new JavaFieldsExt(); + + transient java.util.List field2List = new java.util.ArrayList<>(); + + public java.util.List getField2List() { return field2List; } + +} \ No newline at end of file diff --git a/spec/java_integration/fixtures/types/DateLike.java b/spec/java_integration/fixtures/types/DateLike.java new file mode 100644 index 00000000000..08ba86ec384 --- /dev/null +++ b/spec/java_integration/fixtures/types/DateLike.java @@ -0,0 +1,11 @@ +package java_integration.fixtures.types; + +public class DateLike extends java.util.Date { + + public DateLike() { super(); } + + public Object inspect() { + return new StringBuilder("inspect:").append(System.identityHashCode(this)); + } + +} diff --git a/spec/java_integration/types/coercion_spec.rb b/spec/java_integration/types/coercion_spec.rb index a024217ed6a..46862dca8ef 100644 --- a/spec/java_integration/types/coercion_spec.rb +++ b/spec/java_integration/types/coercion_spec.rb @@ -40,7 +40,7 @@ expect(CoreTypeMethods.getVoid).to eq(nil) - expect(CoreTypeMethods.getBigInteger).to eq(1234567890123456789012345678901234567890) + expect(CoreTypeMethods.getBigInteger).to eq(1234567890_1234567890_1234567890_1234567890) end it "should be coerced from Ruby types when passing parameters" do @@ -222,23 +222,23 @@ it "coerce to Ruby types when retrieved" do # static expect(JavaFields.stringStaticField).to be_kind_of(String) - expect(JavaFields.stringStaticField).to eq("foo"); + expect(JavaFields.stringStaticField).to eq('000'); expect(JavaFields.byteStaticField).to be_kind_of(Integer) expect(JavaFields.byteStaticField).to eq(1) expect(JavaFields.shortStaticField).to be_kind_of(Integer) expect(JavaFields.shortStaticField).to eq(2) expect(JavaFields.charStaticField).to be_kind_of(Integer) - expect(JavaFields.charStaticField).to eq(2) + expect(JavaFields.charStaticField).to eq('3'.ord) expect(JavaFields.intStaticField).to be_kind_of(Integer) expect(JavaFields.intStaticField).to eq(4) expect(JavaFields.longStaticField).to be_kind_of(Integer) - expect(JavaFields.longStaticField).to eq(8) + expect(JavaFields.longStaticField).to eq(5) expect(JavaFields.floatStaticField).to be_kind_of(Float) - expect(JavaFields.floatStaticField).to eq(4.5) + expect(JavaFields.floatStaticField).to eq(6.0) expect(JavaFields.doubleStaticField).to be_kind_of(Float) - expect(JavaFields.doubleStaticField).to eq(8.5) + expect(JavaFields.doubleStaticField).to eq(7.2) expect(JavaFields.trueStaticField).to be_kind_of(TrueClass) expect(JavaFields.trueStaticField).to eq(true) @@ -249,30 +249,28 @@ expect(JavaFields.nullStaticField).to eq(nil) expect(JavaFields.bigIntegerStaticField).to be_kind_of(Bignum) - expect(JavaFields.bigIntegerStaticField).to eq( - 1234567890123456789012345678901234567890 - ) + expect(JavaFields.bigIntegerStaticField).to eq(111_111_111_111_111_111_110) # instance jf = JavaFields.new expect(jf.stringField).to be_kind_of(String) - expect(jf.stringField).to eq("foo"); + expect(jf.stringField).to eq("000"); expect(jf.byteField).to be_kind_of(Integer) expect(jf.byteField).to eq(1) expect(jf.shortField).to be_kind_of(Integer) expect(jf.shortField).to eq(2) expect(jf.charField).to be_kind_of(Integer) - expect(jf.charField).to eq(2) + expect(jf.charField).to eq(84) expect(jf.intField).to be_kind_of(Integer) expect(jf.intField).to eq(4) expect(jf.longField).to be_kind_of(Integer) - expect(jf.longField).to eq(8) + expect(jf.longField).to eq(5) expect(jf.floatField).to be_kind_of(Float) - expect(jf.floatField).to eq(4.5) + expect(jf.floatField).to eq(6.0) expect(jf.doubleField).to be_kind_of(Float) - expect(jf.doubleField).to eq(8.5) + expect(jf.doubleField).to eq(7.2) expect(jf.trueField).to be_kind_of(TrueClass) expect(jf.trueField).to eq(true) @@ -283,9 +281,7 @@ expect(jf.nullField).to eq(nil) expect(jf.bigIntegerField).to be_kind_of(Bignum) - expect(jf.bigIntegerField).to eq( - 1234567890123456789012345678901234567890 - ) + expect(jf.bigIntegerField).to eq(111_111_111_111_111_111_111) end end @@ -297,16 +293,16 @@ expect(JavaFields.shortObjStaticField).to be_kind_of(Integer) expect(JavaFields.shortObjStaticField).to eq(2) expect(JavaFields.charObjStaticField).to be_kind_of(Integer) - expect(JavaFields.charObjStaticField).to eq(2) + expect(JavaFields.charObjStaticField).to eq('3'.ord) expect(JavaFields.intObjStaticField).to be_kind_of(Integer) expect(JavaFields.intObjStaticField).to eq(4) expect(JavaFields.longObjStaticField).to be_kind_of(Integer) - expect(JavaFields.longObjStaticField).to eq(8) + expect(JavaFields.longObjStaticField).to eq(5) expect(JavaFields.floatObjStaticField).to be_kind_of(Float) - expect(JavaFields.floatObjStaticField).to eq(4.5) + expect(JavaFields.floatObjStaticField).to eq(6.0) expect(JavaFields.doubleObjStaticField).to be_kind_of(Float) - expect(JavaFields.doubleObjStaticField).to eq(8.5) + expect(JavaFields.doubleObjStaticField).to eq(7.2) expect(JavaFields.trueObjStaticField).to be_kind_of(TrueClass) expect(JavaFields.trueObjStaticField).to eq(true) @@ -320,16 +316,16 @@ expect(jf.shortObjField).to be_kind_of(Integer) expect(jf.shortObjField).to eq(2) expect(jf.charObjField).to be_kind_of(Integer) - expect(jf.charObjField).to eq(2) + expect(jf.charObjField).to eq('T'.ord) expect(jf.intObjField).to be_kind_of(Integer) expect(jf.intObjField).to eq(4) expect(jf.longObjField).to be_kind_of(Integer) - expect(jf.longObjField).to eq(8) + expect(jf.longObjField).to eq(5) expect(jf.floatObjField).to be_kind_of(Float) - expect(jf.floatObjField).to eq(4.5) + expect(jf.floatObjField).to eq(6.0) expect(jf.doubleObjField).to be_kind_of(Float) - expect(jf.doubleObjField).to eq(8.5) + expect(jf.doubleObjField).to eq(7.2) expect(jf.trueObjField).to be_kind_of(TrueClass) expect(jf.trueObjField).to eq(true) diff --git a/spec/java_integration/types/inspect_spec.rb b/spec/java_integration/types/inspect_spec.rb index 7e8d7af2174..947923dec7d 100644 --- a/spec/java_integration/types/inspect_spec.rb +++ b/spec/java_integration/types/inspect_spec.rb @@ -1,8 +1,43 @@ require File.dirname(__FILE__) + "/../spec_helper" -describe "A Java object's builtin inspect method" do - it "produces the \"hashy\" inspect output" do +describe "inspect method" do + + it "produces \"hashy\" inspect output for java.lang.Object" do o = java.lang.Object.new expect(o.inspect).to match(/\#/) end + + it 'returns toString when overriden in Java types' do + expect(java.lang.Short.new(1).inspect).to eql '1' + + expect(java.util.concurrent.TimeUnit::DAYS.inspect).to eql 'DAYS' + + date = java.sql.Date.new(0) + expect(date.inspect).to eql '1970-01-01' + expect(date.toLocalDate.inspect).to eql date.to_s + time = java.sql.Timestamp.new(0) + expect(time.toInstant.inspect).to eql '1970-01-01T00:00:00Z' + + expect(java.math.BigInteger.new('1').inspect).to eql '1' + expect(java.math.BigDecimal.new('3.6').to_s).to eql '3.6' + + expect(java.lang.String.new('str').inspect).to eql 'str' + + expect(java.util.ArrayList.new([1, '2']).inspect).to eql '[1, 2]' + end + + class SubDate < java.util.Date; end + + it 'inherits (Java) inspect' do + date = SubDate.new(0) + expect(date.inspect).to include '1970' + end + + it 'overrides custom (Java) inspect' do + date = Java::java_integration.fixtures.types.DateLike.new + inspect = date.inspect + expect(inspect).to be_a java.lang.StringBuilder + expect(inspect.to_s).to match /inspect:.*/ + end + end \ No newline at end of file diff --git a/spec/java_integration/types/map_spec.rb b/spec/java_integration/types/map_spec.rb index eba5497acc1..7295c592cbd 100644 --- a/spec/java_integration/types/map_spec.rb +++ b/spec/java_integration/types/map_spec.rb @@ -77,17 +77,18 @@ expect( m.empty? ).to be true end - it "supports Hash-like operations" do + it 'supports Hash-like operations' do h = java.util.HashMap.new test_ok(h.kind_of? java.util.Map) h.put(1, 2); h.put(3, 4); h.put(5, 6) - test_equal({1=>2, 3=>4, 5=>6}, eval(h.inspect)) + # test_equal({1=>2, 3=>4, 5=>6}, eval(h.inspect)) test_equal(4, h[3]) test_equal(nil, h[10]) h[7]=8 test_ok({3=>4, 1=>2, 7=>8, 5=>6} == h) - test_equal(0, h.clear.size) + h.clear + test_equal(0, h.size) test_equal(Java::JavaUtil::HashMap, h.class) h.put("a", 100); h.put("b", 200); h.put("c", 300) @@ -128,7 +129,8 @@ test_equal(["a", "b", "c"], a1) test_equal([100, 200, 300], a2) - test_ok(h.clear.empty?) + h.clear + test_ok(h.empty?) # Java 8 adds a replace method to Map that takes a key and value h.ruby_replace({1=>100}) @@ -148,7 +150,7 @@ test_ok(!h.key?(0)) test_equal([1, 2, 3], h.keys) test_ok(!h.value?(0.1)) -# java.util.Map has values method. Java's values() is used. + # java.util.Map has values method. Java's values() is used. test_equal("[100, 200, 300]", h.values.to_a.inspect) test_equal(3, h.length) h.delete(1) @@ -167,18 +169,20 @@ test_equal({"a"=>100, "b"=>254, "c"=>300}, h1.ruby_merge(h2)) end test_equal({"a"=>100, "b"=>454, "c"=>300}, h1.ruby_merge(h2) { |k, o, n| o+n }) - test_equal("{\"a\"=>100, \"b\"=>200}", h1.inspect) + test_equal('{a=100, b=200}', h1.inspect) # does Map#toString since 9.2 + # used to do dispatch as Hash#inspect before 9.2 + # test_equal("{\"a\"=>100, \"b\"=>200}", h1.inspect) h1.merge!(h2) { |k, o, n| o } - test_equal("{\"a\"=>100, \"b\"=>200, \"c\"=>300}", h1.inspect) + test_equal(h1.toString, h1.inspect) test_equal(Java::JavaUtil::LinkedHashMap, h1.class) h.clear h.put(1, 100); h.put(2, 200); h.put(3, 300) test_equal({1=>100, 2=>200}, h.reject { |k, v| k > 2 }) - test_equal("{1=>100, 2=>200, 3=>300}", h.inspect) + test_equal("{1=100, 2=200, 3=300}", h.inspect) test_equal({1=>100, 2=>200}, h.reject! { |k, v| k > 2 }) - test_equal("{1=>100, 2=>200}", h.inspect) + test_equal("{1=100, 2=200}", h.inspect) # Java 8 adds a replace method to Map that takes a key and value test_equal({"c"=>300, "d"=>400, "e"=>500}, h.ruby_replace({"c"=>300, "d"=>400, "e"=>500})) @@ -202,7 +206,8 @@ test_equal(Java::JavaUtil::LinkedHashMap, h.class) test_equal(Hash, rh.class) - test_equal("{\"a\"=>20, \"d\"=>10, \"c\"=>30, \"b\"=>0, \"e\"=>20}", h.to_s) + test_equal('{a=20, d=10, c=30, b=0, e=20}', h.to_s) + test_equal("{\"a\"=>20, \"d\"=>10, \"c\"=>30, \"b\"=>0, \"e\"=>20}", h.send(:hash_to_s)) test_ok(h.all? { |k, v| k.length == 1 }) test_ok(!h.all? { |k, v| v > 100 }) @@ -215,7 +220,7 @@ h2 = {"b"=>254, "c"=>300} test_equal({"a"=>100, "b"=>200, "c"=>300}, h.update(h2) { |k, o, n| o }) - test_equal("{\"a\"=>100, \"b\"=>200, \"c\"=>300}", h.inspect) + # test_equal("{a=100, b=200, c=300}", h.to_s) test_equal(Java::JavaUtil::LinkedHashMap, h.class) test_equal([100, 200], h.values_at("a", "b")) test_equal([100, 200, nil], h.values_at("a", "b", "z")) @@ -237,14 +242,6 @@ test_equal(1, get_hash_key(h, 2)) test_equal(nil, get_hash_key(h, 10)) -# java.util.HashMap can't have a block as an arg for its constructor -#h = Hash.new {|h,k| h[k] = k.to_i*10 } - -#test_ok(!nil, h.default_proc) -#test_equal(100, h[10]) -#test_equal(20, h.default(2)) - -#behavior change in 1.8.5 led to this: h = java.util.HashMap.new test_equal(nil, h.default) @@ -253,23 +250,17 @@ test_equal(nil, h.default_proc) test_equal(5, h[12]) + end -### -# Maybe this test doens't work for a Java object. -#class << h -# def default(k); 2; end -#end + it 'behaves properly when sub-classed' do + class HashExt < java.util.HashMap; end + test_equal(HashExt, HashExt.new.class) -#test_equal(nil, h.default_proc) -#test_equal(2, h[30]) -### + test_equal(HashExt, HashExt.new.clone.class) -# test that extensions of the base classes are typed correctly - class HashExt < java.util.HashMap - end - test_equal(HashExt, HashExt.new.class) # [] method of JavaProxy is used, and the test fails. #test_equal(HashExt, HashExt[:foo => :bar].class) + end ### # no need to test these against java.util.HashMap @@ -294,19 +285,16 @@ class HashExt < java.util.HashMap #end ### -# Test hash coercion + it 'coerces to_hash' do class ToHashImposter def initialize(hash) @hash = hash end - def to_hash - @hash - end + def to_hash; @hash end end - class SubHash < Hash - end + class SubHash < Hash; end x = java.util.HashMap.new x.put(:a, 1); x.put(:b, 2) @@ -325,23 +313,20 @@ class SubHash < Hash x.put(:a, 1); x.put(:b, 2) - # Java 8 adds a replace method to Map that takes a key and value - if ENV_JAVA['java.specification.version'] < '1.8' - x.replace(ToHashImposter.new({:a => 10, :b => 20})) - test_equal(10, x[:a]) - test_equal(20, x[:b]) - test_exception(TypeError) { x.replace(ToHashImposter.new(4)) } - - x.put(:a, 1); x.put(:b, 2) - x.replace(ToHashImposter.new(sub2)) - test_equal(10, x[:a]) - test_equal(20, x[:b]) - end + x.ruby_replace(ToHashImposter.new({:a => 10, :b => 20})) + test_equal(10, x[:a]) + test_equal(20, x[:b]) + test_exception(TypeError) { x.ruby_replace(ToHashImposter.new(4)) } - class H1 < java.util.HashMap - end + x.put(:a, 1); x.put(:b, 2) + x.ruby_replace(ToHashImposter.new(sub2)) + test_equal(10, x[:a]) + test_equal(20, x[:b]) + end - test_no_exception { H1.new.clone } + it 'aliases toString as to_s' do + map = java.util.concurrent.ConcurrentSkipListMap.new({ 'a'.to_java => 111, 'b' => 222.to_java, 'c' => 300.0.to_java }) + expect(map.to_s).to eql '{a=111, b=222, c=300.0}' end it 'converts to_hash' do