Skip to content

Commit

Permalink
Showing 6 changed files with 416 additions and 193 deletions.
82 changes: 42 additions & 40 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -101,7 +101,7 @@
* and internal variables are handled this way, but the implementation
* in RubyObject only returns "this" in {@link #getInstanceVariables()} and
* {@link #getInternalVariables()}.
*
*
* Methods that are implemented here, such as "initialize" should be implemented
* with care; reification of Ruby classes into Java classes can produce
* conflicting method names in rare cases. See JRUBY-5906 for an example.
@@ -111,7 +111,7 @@ public class RubyBasicObject implements Cloneable, IRubyObject, Serializable, Co
private static final Logger LOG = LoggerFactory.getLogger("RubyBasicObject");

private static final boolean DEBUG = false;

/** The class of this object */
protected transient RubyClass metaClass;

@@ -120,13 +120,13 @@ public class RubyBasicObject implements Cloneable, IRubyObject, Serializable, Co

/** variable table, lazily allocated as needed (if needed) */
public transient Object[] varTable;

/** locking stamp for Unsafe ops updating the vartable */
public transient volatile int varTableStamp;

/** offset of the varTable field in RubyBasicObject */
public static final long VAR_TABLE_OFFSET = UnsafeHolder.fieldOffset(RubyBasicObject.class, "varTable");

/** offset of the varTableTamp field in RubyBasicObject */
public static final long STAMP_OFFSET = UnsafeHolder.fieldOffset(RubyBasicObject.class, "varTableStamp");

@@ -441,7 +441,7 @@ public boolean isTaint() {
public void setTaint(boolean taint) {
// JRUBY-4113: callers should not call setTaint on immediate objects
if (isImmediate()) return;

if (taint) {
flags |= TAINTED_F;
} else {
@@ -817,26 +817,28 @@ public Object toJava(Class target) {
// for callers that unconditionally pass null retval type (JRUBY-4737)
if (target == void.class) return null;

if (dataGetStruct() instanceof JavaObject) {
final Object innerWrapper = dataGetStruct();
if (innerWrapper instanceof JavaObject) {
// for interface impls

JavaObject innerWrapper = (JavaObject)dataGetStruct();

final Object value = ((JavaObject) innerWrapper).getValue();
// ensure the object is associated with the wrapper we found it in,
// so that if it comes back we don't re-wrap it
if (target.isAssignableFrom(innerWrapper.getValue().getClass())) {
getRuntime().getJavaSupport().getObjectProxyCache().put(innerWrapper.getValue(), this);
if (target.isAssignableFrom(value.getClass())) {
getRuntime().getJavaSupport().getObjectProxyCache().put(value, this);

return innerWrapper.getValue();
return value;
}
} else if (JavaUtil.isDuckTypeConvertable(getClass(), target)) {
}
else if (JavaUtil.isDuckTypeConvertable(getClass(), target)) {
if (!respondsTo("java_object")) {
return JavaUtil.convertProcToInterface(getRuntime().getCurrentContext(), this, target);
}
} else if (target.isAssignableFrom(getClass())) {
}
else if (target.isAssignableFrom(getClass())) {
return this;
}

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

@@ -1121,7 +1123,7 @@ private StringBuilder inspectObj(StringBuilder part) {
for (Map.Entry<String, VariableAccessor> entry : metaClass.getVariableTableManager().getVariableAccessorsForRead().entrySet()) {
Object value = entry.getValue().get(this);
if (value == null || !(value instanceof IRubyObject) || !IdUtil.isInstanceVariable(entry.getKey())) continue;

part.append(sep).append(" ").append(entry.getKey()).append("=");
part.append(invokedynamic(context, (IRubyObject)value, INSPECT));
sep = ",";
@@ -1158,22 +1160,22 @@ public int compareTo(IRubyObject other) {
try {
IRubyObject cmp = invokedynamic(getRuntime().getCurrentContext(),
this, OP_CMP, other);

// if RubyBasicObject#op_cmp is used, the result may be nil
if (!cmp.isNil()) {
return (int) cmp.convertToInteger().getLongValue();
}
} catch (RaiseException ex) {
getRuntime().getGlobalVariables().set("$!", oldExc);
}

/* We used to raise an error if two IRubyObject were not comparable, but
* in order to support the new ConcurrentHashMapV8 and other libraries
* and containers that arbitrarily call compareTo expecting it to always
* succeed, we have opted to return 0 here. This will allow all
* RubyBasicObject subclasses to be compared, but if the comparison is
* not valid we they will appear the same for sorting purposes.
*
*
* See https://jira.codehaus.org/browse/JRUBY-7013
*/
return 0;
@@ -1264,7 +1266,7 @@ public void removeFinalizers() {
public Object getVariable(int index) {
return VariableAccessor.getVariable(this, index);
}

@Override
public void setVariable(int index, Object value) {
ensureInstanceVariablesSettable();
@@ -1286,8 +1288,8 @@ public final void setFFIHandle(Object value) {

/**
* Returns true if object has any variables
*
* @see VariableTableManager#hasVariables(org.jruby.RubyBasicObject)
*
* @see VariableTableManager#hasVariables(org.jruby.RubyBasicObject)
*/
@Override
public boolean hasVariables() {
@@ -1420,7 +1422,7 @@ public Object removeInternalVariable(String name) {
assert !IdUtil.isRubyVariable(name);
return variableTableRemove(name);
}

/**
* Sync one this object's variables with other's - this is used to make
* rbClone work correctly.
@@ -1429,7 +1431,7 @@ public Object removeInternalVariable(String name) {
public void syncVariables(IRubyObject other) {
metaClass.getVariableTableManager().syncVariables(this, other);
}

//
// INSTANCE VARIABLE API METHODS
//
@@ -1627,7 +1629,7 @@ public IRubyObject send19(ThreadContext context, IRubyObject[] args, Block block

return getMetaClass().finvoke(context, this, name, newArgs, block);
}

@JRubyMethod(name = "instance_eval",
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
@@ -1908,7 +1910,7 @@ public void finalize() {
}
}
}

private void callFinalizer(IRubyObject finalizer) {
Helpers.invoke(
finalizer.getRuntime().getCurrentContext(),
@@ -2658,7 +2660,7 @@ public IRubyObject send(ThreadContext context, IRubyObject arg0, IRubyObject arg
}
public IRubyObject send(ThreadContext context, IRubyObject[] args, Block block) {
if (args.length == 0) return send(context, block);

String name = RubySymbol.objectToSymbolString(args[0]);
int newArgsLength = args.length - 1;

@@ -2872,20 +2874,20 @@ protected String validateInstanceVariable(String name) {

throw getRuntime().newNameError("`" + name + "' is not allowable as an instance variable name", name);
}

/**
* Serialization of a Ruby (basic) object involves three steps:
*
*
* <ol>
* <li>Dump the object itself</li>
* <li>Dump a String used to load the appropriate Ruby class</li>
* <li>Dump each variable from varTable in turn</li>
* </ol>
*
*
* The metaClass field is marked transient since Ruby classes generally will
* not be able to serialize (since they hold references to method tables,
* other classes, and potentially thread-, runtime-, or jvm-local state.
*
*
* The varTable field is transient because the layout of the same class may
* differ across runtimes, since it is determined at runtime based on the
* order in which variables get assigned for a given class. We serialize
@@ -2895,36 +2897,36 @@ private void writeObject(ObjectOutputStream oos) throws IOException {
if (metaClass.isSingleton()) {
throw new IOException("can not serialize singleton object");
}

oos.defaultWriteObject();
oos.writeUTF(metaClass.getName());

metaClass.getVariableTableManager().serializeVariables(this, oos);
}

/**
* Deserialization proceeds as follows:
*
*
* <ol>
* <li>Deserialize the object instance. It will have null metaClass and
* varTable fields.</li>
* <li>Deserialize the name of the object's class, and retrieve class from a
* thread-local JRuby instance.</li>
* <li>Retrieve each variable in turn, re-assigning them by name.</li>
* </ol>
*
* @see RubyBasicObject#writeObject(java.io.ObjectOutputStream)
*
* @see RubyBasicObject#writeObject(java.io.ObjectOutputStream)
*/
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
Ruby ruby = Ruby.getThreadLocalRuntime();

if (ruby == null) {
throw new IOException("No thread-local org.jruby.Ruby available; can't deserialize Ruby object. Set with Ruby#setThreadLocalRuntime.");
}

ois.defaultReadObject();
metaClass = (RubyClass)ruby.getClassFromPath(ois.readUTF());

metaClass.getVariableTableManager().deserializeVariables(this, ois);
}

12 changes: 11 additions & 1 deletion core/src/main/java/org/jruby/RubyMethod.java
Original file line number Diff line number Diff line change
@@ -197,7 +197,17 @@ public int getLine() {
return RubyMethod.this.getLine();
}
};
BlockBody body = CompiledBlockLight19.newCompiledBlockLight(method.getArity(), runtime.getStaticScopeFactory().getDummyScope(), callback, false, 0, JRubyLibrary.MethodExtensions.methodParameters(runtime, method));
int argumentType;
if (method.getArity().isFixed()) {
if (method.getArity().required() > 0) {
argumentType = BlockBody.MULTIPLE_ASSIGNMENT;
} else {
argumentType = BlockBody.ZERO_ARGS;
}
} else {
argumentType = BlockBody.MULTIPLE_ASSIGNMENT;
}
BlockBody body = CompiledBlockLight19.newCompiledBlockLight(method.getArity(), runtime.getStaticScopeFactory().getDummyScope(), callback, false, argumentType, JRubyLibrary.MethodExtensions.methodParameters(runtime, method));
Block b = new Block(body, context.currentBinding(receiver, Visibility.PUBLIC));

return RubyProc.newProc(runtime, b, Block.Type.LAMBDA);
368 changes: 238 additions & 130 deletions core/src/main/java/org/jruby/java/proxies/ArrayJavaProxy.java
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@
import java.lang.reflect.Array;
import java.util.Arrays;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyInteger;
@@ -20,137 +22,139 @@
import org.jruby.runtime.builtin.IRubyObject;

public class ArrayJavaProxy extends JavaProxy {

private final JavaUtil.JavaConverter converter;

public ArrayJavaProxy(Ruby runtime, RubyClass klazz, Object ary) {
this(runtime, klazz, ary, JavaUtil.getJavaConverter(ary.getClass().getComponentType()));
}

public ArrayJavaProxy(Ruby runtime, RubyClass klazz, Object ary, JavaUtil.JavaConverter converter) {
super(runtime, klazz, ary);
this.converter = converter;
}

public static RubyClass createArrayJavaProxy(ThreadContext context) {
Ruby runtime = context.runtime;

RubyClass arrayJavaProxy = runtime.defineClass("ArrayJavaProxy",
runtime.getJavaSupport().getJavaProxyClass(),
ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);

RubyClass singleton = arrayJavaProxy.getSingletonClass();

final DynamicMethod oldNew = singleton.searchMethod("new");

singleton.addMethod("new", new ArrayNewMethod(singleton, Visibility.PUBLIC, oldNew));

singleton.addMethod("new", new ArrayNewMethod(singleton, Visibility.PUBLIC));

arrayJavaProxy.defineAnnotatedMethods(ArrayJavaProxy.class);
arrayJavaProxy.includeModule(runtime.getEnumerable());

return arrayJavaProxy;
}

public JavaArray getJavaArray() {
JavaArray javaArray = (JavaArray)dataGetStruct();
JavaArray javaArray = (JavaArray) dataGetStruct();

if (javaArray == null) {
javaArray = new JavaArray(getRuntime(), getObject());
dataWrapStruct(javaArray);
}

return javaArray;
}
@JRubyMethod(name = {"length","size"})

@JRubyMethod(name = {"length", "size"})
public IRubyObject length(ThreadContext context) {
return context.runtime.newFixnum(Array.getLength(this.getObject()));
return context.runtime.newFixnum( Array.getLength( getObject() ) );
}

@JRubyMethod(name = "empty?")
public IRubyObject empty(ThreadContext context) {
return context.runtime.newBoolean(Array.getLength(this.getObject()) == 0);
return context.runtime.newBoolean( Array.getLength( getObject() ) == 0 );
}

@JRubyMethod(name = "[]")
public IRubyObject op_aref(ThreadContext context, IRubyObject arg) {
if (arg instanceof RubyInteger) {
int index = (int)((RubyInteger)arg).getLongValue();
return ArrayUtils.arefDirect(context.runtime, getObject(), converter, index);
} else {
return getRange(context, arg);
}
if ( arg instanceof RubyRange ) return arrayRange(context, (RubyRange) arg);
final int i = convertArrayIndex(arg);
return ArrayUtils.arefDirect(context.runtime, getObject(), converter, i);
}

@JRubyMethod(name = "[]", required = 1, rest = true)
public IRubyObject op_aref(ThreadContext context, IRubyObject[] args) {
if (args.length == 1 && args[0] instanceof RubyInteger) {
int index = (int)((RubyInteger)args[0]).getLongValue();
return ArrayUtils.arefDirect(context.runtime, getObject(), converter, index);
} else {
return getRange(context, args);
}
if ( args.length == 1 ) return op_aref(context, args[0]);
return getRange(context, args);
}

@JRubyMethod(name = "[]=")
public IRubyObject op_aset(ThreadContext context, IRubyObject index, IRubyObject value) {
ArrayUtils.asetDirect(context.runtime, getObject(), converter, (int)((RubyInteger)index).getLongValue(), value);
return value;
final int i = convertArrayIndex(index);
return ArrayUtils.asetDirect(context.runtime, getObject(), converter, i, value);
}

@JRubyMethod
public IRubyObject at(ThreadContext context, IRubyObject indexObj) {
Ruby runtime = context.runtime;
Object array = getObject();
int length = Array.getLength(array);
long index = indexObj.convertToInteger().getLongValue();

if (index < 0) {
index = index + length;

private static int convertArrayIndex(final IRubyObject index) {
if ( index instanceof RubyInteger ) {
return (int) ((RubyInteger) index).getLongValue();
}

if (index >= 0 && index < length) {
return ArrayUtils.arefDirect(runtime, array, converter, (int)index);
} else {
return context.nil;
if ( index instanceof JavaProxy ) {
return (Integer) index.toJava(Integer.class);
}
return (int) index.convertToInteger().getLongValue();
}

@JRubyMethod
public IRubyObject at(ThreadContext context, IRubyObject index) {
final Ruby runtime = context.runtime;
final Object array = getObject();
final int length = Array.getLength(array);

int i = convertArrayIndex(index);

if ( i < 0 ) i = i + length;

if ( i >= 0 && i < length ) {
return ArrayUtils.arefDirect(runtime, array, converter, i);
}
return context.nil;
}

@JRubyMethod(name = "+")
public IRubyObject op_plus(ThreadContext context, IRubyObject other) {
if (other instanceof ArrayJavaProxy) {
Object otherArray = ((ArrayJavaProxy)other).getObject();

if (getObject().getClass().getComponentType().isAssignableFrom(otherArray.getClass().getComponentType())) {
return ArrayUtils.concatArraysDirect(context, getObject(), otherArray);
final Object array = getObject();
if ( other instanceof ArrayJavaProxy ) {
final Object otherArray = ((ArrayJavaProxy) other).getObject();
final Class<?> componentType = array.getClass().getComponentType();
if ( componentType.isAssignableFrom( otherArray.getClass().getComponentType() ) ) {
return ArrayUtils.concatArraysDirect(context, array, otherArray);
}
}
return ArrayUtils.concatArraysDirect(context, getObject(), other);
return ArrayUtils.concatArraysDirect(context, array, other);
}

@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
int length = Array.getLength(getObject());
final Ruby runtime = context.runtime;
final Object array = getObject();
final int length = Array.getLength(array);

for (int i = 0; i < length; i++) {
IRubyObject rubyObj = ArrayUtils.arefDirect(runtime, getObject(), converter, i);
block.yield(context, rubyObj);
for ( int i = 0; i < length; i++ ) {
IRubyObject element = ArrayUtils.arefDirect(runtime, array, converter, i);
block.yield(context, element);
}
return this;
}

@JRubyMethod(name = {"to_a","to_ary"})
public IRubyObject to_a(ThreadContext context) {
return JavaUtil.convertJavaArrayToRubyWithNesting(context, this.getObject());

@JRubyMethod(name = {"to_a", "to_ary"})
public RubyArray to_a(ThreadContext context) {
final Object array = getObject();
return JavaUtil.convertJavaArrayToRubyWithNesting(context, array);
}

@JRubyMethod
public IRubyObject inspect(ThreadContext context) {
StringBuilder buffer = new StringBuilder();
Class componentClass = getObject().getClass().getComponentType();
final StringBuilder buffer = new StringBuilder();
Class<?> componentClass = getObject().getClass().getComponentType();

buffer.append(componentClass.getName());

if (componentClass.isPrimitive()) {
switch (componentClass.getName().charAt(0)) {
case 'b':
@@ -177,90 +181,194 @@ public IRubyObject inspect(ThreadContext context) {
break;
}
} else {
buffer.append(Arrays.toString((Object[])getObject()));
buffer.append(Arrays.toString((Object[]) getObject()));
}
buffer.append('@').append(Integer.toHexString(inspectHashCode()));
return context.runtime.newString(buffer.toString());
}

@JRubyMethod(name = "==")
@Override
public RubyBoolean op_eqq(ThreadContext context, IRubyObject other) {
return eql_p(context, other);
}

@JRubyMethod(name = "eql?")
public RubyBoolean eql_p(ThreadContext context, IRubyObject obj) {
boolean equals = false;
if ( obj instanceof ArrayJavaProxy ) {
final ArrayJavaProxy that = (ArrayJavaProxy) obj;
equals = arraysEquals(this.getObject(), that.getObject());
}
if ( obj.getClass().isArray() ) {
equals = arraysEquals(getObject(), obj);
}
return context.runtime.newBoolean(equals);
}

@Override
public boolean equals(Object obj) {
if ( obj instanceof ArrayJavaProxy ) {
final ArrayJavaProxy that = (ArrayJavaProxy) obj;
final Object thisArray = this.getObject();
final Object thatArray = that.getObject();
return arraysEquals(thisArray, thatArray);
}
return false;
}

private static boolean arraysEquals(final Object thisArray, final Object thatArray) {
final Class<?> componentType = thisArray.getClass().getComponentType();
if ( ! componentType.equals(thatArray.getClass().getComponentType()) ) {
return false;
}
if ( componentType.isPrimitive() ) {
switch ( componentType.getName().charAt(0) ) {
case 'b':
if (componentType == byte.class) return Arrays.equals((byte[]) thisArray, (byte[]) thatArray);
if (componentType == boolean.class) return Arrays.equals((boolean[]) thisArray, (boolean[]) thatArray);
case 's':
if (componentType == short.class) return Arrays.equals((short[]) thisArray, (short[]) thatArray);
case 'c':
if (componentType == char.class) return Arrays.equals((char[]) thisArray, (char[]) thatArray);
case 'i':
if (componentType == int.class) return Arrays.equals((int[]) thisArray, (int[]) thatArray);
case 'l':
if (componentType == long.class) return Arrays.equals((long[]) thisArray, (long[]) thatArray);
case 'f':
if (componentType == float.class) return Arrays.equals((float[]) thisArray, (float[]) thatArray);
case 'd':
if (componentType == double.class) return Arrays.equals((double[]) thisArray, (double[]) thatArray);
}
}
buffer.append('@')
.append(Integer.toHexString(inspectHashCode()));
return context.runtime.newString(buffer.toString());
return Arrays.equals((Object[]) thisArray, (Object[]) thatArray);
}


@JRubyMethod
@Override
public RubyFixnum hash() {
return getRuntime().newFixnum( hashCode() );
}

@Override
public int hashCode() {
final Object array = getObject();
final Class<?> componentType = array.getClass().getComponentType();
if ( componentType.isPrimitive() ) {
switch ( componentType.getName().charAt(0) ) {
case 'b':
if (componentType == byte.class) return 11 * Arrays.hashCode((byte[]) array);
if (componentType == boolean.class) return 11 * Arrays.hashCode((boolean[]) array);
case 's':
if (componentType == short.class) return 11 * Arrays.hashCode((short[]) array);
case 'c':
if (componentType == char.class) return 11 * Arrays.hashCode((char[]) array);
case 'i':
if (componentType == int.class) return 11 * Arrays.hashCode((int[]) array);
case 'l':
if (componentType == long.class) return 11 * Arrays.hashCode((long[]) array);
case 'f':
if (componentType == float.class) return 11 * Arrays.hashCode((float[]) array);
case 'd':
if (componentType == double.class) return 11 * Arrays.hashCode((double[]) array);
}
}
return 11 * Arrays.hashCode((Object[]) array);
}

public IRubyObject getRange(ThreadContext context, IRubyObject[] args) {
if (args.length == 1) {
return getRange(context, args[0]);
} else if (args.length == 2) {
}
if (args.length == 2) {
return getRange(context, args[0], args[1]);
} else {
throw context.runtime.newArgumentError(args.length, 1);
}
throw context.runtime.newArgumentError(args.length, 1);
}

public IRubyObject getRange(ThreadContext context, IRubyObject arg0) {
int length = Array.getLength(getObject());

if (arg0 instanceof RubyRange) {
RubyRange range = (RubyRange)arg0;
if (range.first(context) instanceof RubyFixnum && range.last(context) instanceof RubyFixnum) {
int first = (int)((RubyFixnum)range.first(context)).getLongValue();
int last = (int)((RubyFixnum)range.last(context)).getLongValue();

first = first >= 0 ? first : length + first;
last = last >= 0 ? last : length + last;
int newLength = last - first;
if (range.exclude_end_p().isFalse()) newLength += 1;

if (newLength <= 0) {
return ArrayUtils.emptyJavaArrayDirect(context, getObject().getClass().getComponentType());
}

return ArrayUtils.javaArraySubarrayDirect(context, getObject(), first, newLength);
} else {
throw context.runtime.newTypeError("only Fixnum ranges supported");
}
} else {
throw context.runtime.newTypeError(arg0, context.runtime.getRange());
if ( arg0 instanceof RubyRange ) {
return arrayRange(context, (RubyRange) arg0);
}
throw context.runtime.newTypeError(arg0, context.runtime.getRange());
}

public IRubyObject getRange(ThreadContext context, IRubyObject firstObj, IRubyObject lengthObj) {
if (firstObj instanceof RubyFixnum && lengthObj instanceof RubyFixnum) {
int first = (int)((RubyFixnum)firstObj).getLongValue();
int length = (int)((RubyFixnum)lengthObj).getLongValue();

if (length > Array.getLength(getObject())) {
throw context.runtime.newIndexError("length specifed is longer than array");
private IRubyObject arrayRange(final ThreadContext context, final RubyRange range) {
final Object array = getObject();
final int arrayLength = Array.getLength( array );

final IRubyObject rFirst = range.first(context);
final IRubyObject rLast = range.last(context);
if ( rFirst instanceof RubyFixnum && rLast instanceof RubyFixnum ) {
int first = (int) ((RubyFixnum) rFirst).getLongValue();
int last = (int) ((RubyFixnum) rLast).getLongValue();

first = first >= 0 ? first : arrayLength + first;
last = last >= 0 ? last : arrayLength + last;

int newLength = last - first;
if ( range.exclude_end_p().isFalse() ) newLength += 1;

if ( newLength <= 0 ) {
return ArrayUtils.emptyJavaArrayDirect(context, array.getClass().getComponentType());
}

first = first >= 0 ? first : Array.getLength(getObject()) + first;
return ArrayUtils.javaArraySubarrayDirect(context, array, first, newLength);
}
throw context.runtime.newTypeError("only Fixnum ranges supported");
}

public IRubyObject getRange(ThreadContext context, IRubyObject first, IRubyObject length) {
return arrayRange(context, first, length);
}

private IRubyObject arrayRange(final ThreadContext context,
final IRubyObject rFirst, final IRubyObject rLength) {
final Object array = getObject();
final int arrayLength = Array.getLength( array );

if ( rFirst instanceof RubyFixnum && rLength instanceof RubyFixnum ) {
int first = (int) ((RubyFixnum) rFirst).getLongValue();
int length = (int) ((RubyFixnum) rLength).getLongValue();

if (length <= 0) {
return ArrayUtils.emptyJavaArrayDirect(context, getObject().getClass().getComponentType());
if ( length > arrayLength ) {
throw context.runtime.newIndexError("length specifed is longer than array");
}
if ( length <= 0 ) {
return ArrayUtils.emptyJavaArrayDirect(context, array.getClass().getComponentType());
}

return ArrayUtils.javaArraySubarrayDirect(context, getObject(), first, length);
} else {
throw context.runtime.newTypeError("only Fixnum ranges supported");
first = first >= 0 ? first : arrayLength + first;
return ArrayUtils.javaArraySubarrayDirect(context, array, first, length);
}
throw context.runtime.newTypeError("only Fixnum ranges supported");
}

public static class ArrayNewMethod extends org.jruby.internal.runtime.methods.JavaMethod.JavaMethodOne {
private DynamicMethod oldNew;


private final DynamicMethod newMethod;

ArrayNewMethod(RubyModule implClass, Visibility visibility) {
this(implClass, visibility, implClass.searchMethod("new"));
}

public ArrayNewMethod(RubyModule implClass, Visibility visibility, DynamicMethod oldNew) {
super(implClass, visibility);
this.oldNew = oldNew;
this.newMethod = oldNew;
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0) {
Ruby runtime = context.runtime;
IRubyObject proxy = oldNew.call(context, self, clazz, "new_proxy");

if (arg0 instanceof JavaArray) {
proxy.dataWrapStruct(arg0);
return proxy;
} else {
public final IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0) {
final Ruby runtime = context.runtime;

if ( ! ( arg0 instanceof JavaArray ) ) {
throw runtime.newTypeError(arg0, runtime.getJavaSupport().getJavaArrayClass());
}

IRubyObject proxy = newMethod.call(context, self, clazz, "new_proxy");
proxy.dataWrapStruct(arg0);
return proxy;
}

}
}
41 changes: 19 additions & 22 deletions core/src/main/java/org/jruby/java/util/ArrayUtils.java
Original file line number Diff line number Diff line change
@@ -26,38 +26,35 @@ public static IRubyObject arefDirect(Ruby runtime, Object array, JavaUtil.JavaCo
" for length " + Array.getLength(array) + ")");
}
}

public static IRubyObject concatArraysDirect(ThreadContext context, Object original, Object additional) {
int oldLength = Array.getLength(original);
int addLength = Array.getLength(additional);

ArrayJavaProxy proxy = newProxiedArray(context.runtime, original.getClass().getComponentType(), oldLength + addLength);
Object newArray = proxy.getObject();

System.arraycopy(original, 0, newArray, 0, oldLength);
System.arraycopy(additional, 0, newArray, oldLength, addLength);

return proxy;
}
public static ArrayJavaProxy newProxiedArray(Ruby runtime, Class componentType, int size) {

public static ArrayJavaProxy newProxiedArray(Ruby runtime, Class<?> componentType, int size) {
return newProxiedArray(runtime, componentType, JavaUtil.getJavaConverter(componentType), size);
}

public static ArrayJavaProxy newProxiedArray(Ruby runtime, Class componentType, JavaUtil.JavaConverter converter, int size) {
Object ary = Array.newInstance(componentType, size);
RubyClass newProxyClass = (RubyClass)JavaClass.get(runtime, ary.getClass()).getProxyClass();

ArrayJavaProxy proxy = new ArrayJavaProxy(runtime, newProxyClass, ary, converter);

return proxy;
public static ArrayJavaProxy newProxiedArray(Ruby runtime, Class<?> componentType, JavaUtil.JavaConverter converter, int size) {
final Object array = Array.newInstance(componentType, size);
RubyClass proxyClass = JavaClass.get(runtime, array.getClass()).getProxyClass();
return new ArrayJavaProxy(runtime, proxyClass, array, converter);
}

public static IRubyObject emptyJavaArrayDirect(ThreadContext context, Class componentType) {
Ruby runtime = context.runtime;
return newProxiedArray(runtime, componentType, 0);
}

public static IRubyObject javaArraySubarrayDirect(ThreadContext context, Object fromArray, int index, int size) {
int actualLength = Array.getLength(fromArray);
if (index >= actualLength) {
@@ -66,23 +63,23 @@ public static IRubyObject javaArraySubarrayDirect(ThreadContext context, Object
if (index + size > actualLength) {
size = actualLength - index;
}

ArrayJavaProxy proxy = ArrayUtils.newProxiedArray(context.runtime, fromArray.getClass().getComponentType(), size);
Object newArray = proxy.getObject();
System.arraycopy(fromArray, index, newArray, 0, size);

return proxy;
}
}

public static IRubyObject concatArraysDirect(ThreadContext context, Object original, IRubyObject additional) {
Ruby runtime = context.runtime;
int oldLength = Array.getLength(original);
int addLength = (int)((RubyFixnum) Helpers.invoke(context, additional, "length")).getLongValue();

ArrayJavaProxy proxy = ArrayUtils.newProxiedArray(runtime, original.getClass().getComponentType(), oldLength + addLength);
Object newArray = proxy.getObject();

System.arraycopy(original, 0, newArray, 0, oldLength);

for (int i = 0; i < addLength; i++) {
@@ -111,7 +108,7 @@ public static IRubyObject asetDirect(Ruby runtime, Object array, JavaUtil.JavaCo
}
return value;
}

public static void setWithExceptionHandlingDirect(Ruby runtime, Object ary, int intIndex, Object javaObject) {
try {
Array.set(ary, intIndex, javaObject);
@@ -142,14 +139,14 @@ public static void copyDataToJavaArrayDirect(
Array.set(javaArray, i, rubyArray.entry(i).toJava(targetType));
}
}

public static void copyDataToJavaArray(
ThreadContext context, RubyArray rubyArray, int src, JavaArray javaArray, int dest, int length) {
Class targetType = javaArray.getComponentType();

int destLength = (int)javaArray.length().getLongValue();
int srcLength = rubyArray.getLength();

for (int i = 0; src + i < srcLength && dest + i < destLength && i < length; i++) {
javaArray.setWithExceptionHandling(dest + i, rubyArray.entry(src + i).toJava(targetType));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
describe "A proc created from a Method object" do
it "receives block arguments based on its arity" do
$GH2632 = nil
o = Object.new
def o.foo(a); $GH2632 = a; end
m = o.method :foo
(1..1).each &m
expect($GH2632).to eq(1)
end
end
96 changes: 96 additions & 0 deletions test/jruby/test_higher_javasupport.rb
Original file line number Diff line number Diff line change
@@ -123,6 +123,102 @@ def test_creating_arrays
assert_equal(17.0, array[2])
end

class IntLike
def initialize(value)
@value = value
end
def to_int; @value end
end

def test_array_with_non_ruby_integer_indexes
size = IntLike.new(2)
array = Java::byte[size].new

array[ 0 ] = 42.to_java(:byte)
assert_equal 42, array[ 0.to_java(:int) ]
# TODO: this should work as well, right?!
#assert_equal 42, array[ 0.to_java(:short) ]

assert_equal 42, array[ IntLike.new(0) ]

array[ 1.to_java('java.lang.Integer') ] = 21
assert_equal 21, array[1]

array[ IntLike.new(1) ] = 41
assert_equal 41, array[1]

assert_nil array.at(3)
assert_equal 41, array.at( 1.0 )
assert_nil array.at( IntLike.new(2) )
assert_equal 42, array.at( IntLike.new(-2) )
assert_equal 41, array.at( -1.to_java(:int) )
end

def test_array_eql_and_hash
array1 = java.lang.Long[4].new
array2 = java.lang.Long[4].new

do_test_eql_arrays(array1, array2)

array1 = Java::long[5].new
array2 = Java::long[5].new

do_test_eql_arrays(array1, array2)

array1 = Java::long[4].new
array2 = Java::long[5].new
assert_equal false, array1 == array2
end

def do_test_eql_arrays(array1, array2)
assert_equal(array1, array2)
assert array1.eql?(array2)

array1[0] = 1
assert_equal false, array1.eql?(array2)

array2[0] = 1
array2[1] = 2
array2[2] = 3
array1[1] = 2
array1[2] = 3

assert_equal(array2, array1)
assert_equal(array2.hash, array1.hash)
assert array2.eql?(array1)
assert_equal true, array1 == array2

assert ! array2.equal?(array1)
assert array2.equal?(array2)
end
private :do_test_eql_arrays

def test_bare_eql_and_hash
l1 = java.lang.Long.valueOf 100_000_000
l2 = java.lang.Long.valueOf 100_000_000
assert l1.eql? l2
assert l1 == l2

s1 = Java::JavaLang::String.new 'a-string'
s2 = 'a-string'.to_java
assert ! s1.equal?(s2)
assert s1.eql? s2
assert s1 == s2

a1 = java.util.Arrays.asList(1, 2, 3)
a2 = java.util.ArrayList.new
a2 << 0; a2 << 2; a2 << 3
assert_equal false, a1.eql?(a2)
assert_equal false, a2 == a1
assert_not_equal a1.hash, a2.hash
a2[0] = 1
assert_equal true, a2.eql?(a1)
assert_equal true, a1 == a2
assert_equal true, a2 == a1
assert_equal a1.hash, a2.hash
assert_equal false, a2.equal?(a1)
end

Pipe = java.nio.channels.Pipe
def test_inner_classes
assert_equal("java.nio.channels.Pipe$SinkChannel",

0 comments on commit 3b067b7

Please sign in to comment.