Skip to content

Commit

Permalink
Merge pull request #5219 from jruby/ji-inspect
Browse files Browse the repository at this point in the history
[ji] make inspect on Java proxies work
  • Loading branch information
kares committed Oct 13, 2018
2 parents 09fd1b7 + 6bf8026 commit f880e60
Show file tree
Hide file tree
Showing 16 changed files with 387 additions and 153 deletions.
Expand Up @@ -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
Expand Down
135 changes: 132 additions & 3 deletions 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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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;
}

}
25 changes: 16 additions & 9 deletions core/src/main/java/org/jruby/java/proxies/JavaProxy.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
38 changes: 27 additions & 11 deletions core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java
Expand Up @@ -426,14 +426,38 @@ public IRubyObject set_default_proc(IRubyObject proc) {
return getOrCreateRubyHashMap().set_default_proc(proc);
}

/** rb_hash_inspect
/**
* <code>hash_inspect</code> provided for compatibility if in a need to revert to pre 9.2 behaviour
*
* <code>MapJavaProxy.class_eval { public alias_method :inspect, :hash_inspect }</code>
*
* @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); }

/**
* <code>hash_inspect</code> provided for compatibility if in a need to revert to pre 9.2 behaviour
*
* <code>MapJavaProxy.class_eval { public alias_method :inspect, :hash_inspect }</code>
*
* @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
*
*/
Expand Down Expand Up @@ -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
*
*/
Expand Down
32 changes: 11 additions & 21 deletions core/src/main/java/org/jruby/javasupport/Java.java
Expand Up @@ -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")
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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());
Expand All @@ -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();
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/java/org/jruby/javasupport/JavaSupport.java
Expand Up @@ -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();
Expand All @@ -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();
Expand Down

0 comments on commit f880e60

Please sign in to comment.