package org.jruby.javasupport.ext;
import org.jruby.*;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.javasupport.Java;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import static org.jruby.javasupport.JavaUtil.convertJavaArrayToRuby;
import static org.jruby.javasupport.JavaUtil.convertJavaToUsableRubyObject;
import static org.jruby.javasupport.JavaUtil.unwrapIfJavaObject;
import static org.jruby.javasupport.JavaUtil.unwrapJavaObject;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_EQUAL;
public abstract class JavaUtil {
public static void define(final Ruby runtime) {
JavaExtensions.put(runtime, java.util.Enumeration.class, (proxyClass) -> Enumeration.define(runtime, proxyClass));
JavaExtensions.put(runtime, java.util.Iterator.class, (proxyClass) -> Iterator.define(runtime, proxyClass));
JavaExtensions.put(runtime, java.util.Collection.class, (proxyClass) -> Collection.define(runtime, proxyClass));
JavaExtensions.put(runtime, java.util.List.class, (proxyClass) -> List.define(runtime, proxyClass));
}
@JRubyModule(name = "Java::JavaUtil::Enumeration", include = "Enumerable")
public static class Enumeration {
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.includeModule( runtime.getEnumerable() );
proxy.defineAnnotatedMethods(Enumeration.class);
return proxy;
}
@JRubyMethod
public static IRubyObject each(final ThreadContext context, final IRubyObject self, final Block block) {
final Ruby runtime = context.runtime;
java.util.Enumeration enumeration = unwrapIfJavaObject(self);
while ( enumeration.hasMoreElements() ) {
final Object value = enumeration.nextElement();
block.yield(context, convertJavaToUsableRubyObject(runtime, value));
}
return context.nil;
}
}
@JRubyModule(name = "Java::JavaUtil::Iterator", include = "Enumerable")
public static class Iterator {
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.includeModule( runtime.getEnumerable() );
proxy.defineAnnotatedMethods(Iterator.class);
return proxy;
}
@JRubyMethod
public static IRubyObject each(final ThreadContext context, final IRubyObject self, final Block block) {
final Ruby runtime = context.runtime;
java.util.Iterator iterator = unwrapIfJavaObject(self);
while ( iterator.hasNext() ) {
final Object value = iterator.next();
block.yield(context, convertJavaToUsableRubyObject(runtime, value));
}
return context.nil;
}
}
@JRubyModule(name = "Java::JavaUtil::Collection", include = "Enumerable")
public static class Collection {
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.includeModule( runtime.getEnumerable() );
proxy.defineAnnotatedMethods(Collection.class);
return proxy;
}
@JRubyMethod(name = { "length", "size" })
public static RubyNumeric length(final ThreadContext context, final IRubyObject self) {
return RubyFixnum.int2fix(context.runtime, ((java.util.Collection) unwrapIfJavaObject(self)).size());
}
@JRubyMethod
public static IRubyObject each(final ThreadContext context, final IRubyObject self, final Block block) {
return JavaLang.Iterable.each(context, self, block);
}
@JRubyMethod
public static IRubyObject each_with_index(final ThreadContext context, final IRubyObject self, final Block block) {
return JavaLang.Iterable.each_with_index(context, self, block);
}
@JRubyMethod(name = { "include?", "member?" })
public static RubyBoolean include_p(final ThreadContext context, final IRubyObject self, final IRubyObject obj) {
final java.util.Collection coll = unwrapIfJavaObject(self);
return RubyBoolean.newBoolean(context, coll.contains( obj.toJava(java.lang.Object.class) ) );
}
@JRubyMethod(name = { "first", "ruby_first" })
public static IRubyObject first(final ThreadContext context, final IRubyObject self) {
final java.util.Collection coll = unwrapIfJavaObject(self);
return coll.isEmpty() ? context.nil : convertJavaToUsableRubyObject(context.runtime, coll.iterator().next());
}
@JRubyMethod(name = { "first", "ruby_first" })
public static IRubyObject first(final ThreadContext context, final IRubyObject self, final IRubyObject count) {
final java.util.Collection coll = unwrapIfJavaObject(self);
int len = count.convertToInteger().getIntValue();
int size = coll.size(); if ( len > size ) len = size;
final Ruby runtime = context.runtime;
if ( len == 0 ) return RubyArray.newEmptyArray(runtime);
final RubyArray arr = RubyArray.newArray(runtime, len);
int i = 0; for ( java.util.Iterator it = coll.iterator(); i < len; i++ ) {
arr.append( convertJavaToUsableRubyObject(runtime, it.next() ) );
}
return arr;
}
@JRubyMethod(name = { "<<" })
public static IRubyObject append(final IRubyObject self, final IRubyObject item) {
java.util.Collection coll = unwrapIfJavaObject(self);
coll.add( item.toJava(java.lang.Object.class) );
return self;
}
@JRubyMethod(name = { "to_a", "entries" })
public static RubyArray to_a(final ThreadContext context, final IRubyObject self) {
final Object[] array = ((java.util.Collection) unwrapIfJavaObject(self)).toArray();
if ( IRubyObject.class.isAssignableFrom(array.getClass().getComponentType()) ) {
return RubyArray.newArrayMayCopy(context.runtime, (IRubyObject[]) array);
}
return RubyArray.newArrayNoCopy(context.runtime, convertJavaArrayToRuby(context.runtime, array));
}
@JRubyMethod(name = "+", required = 1)
public static IRubyObject op_plus(final ThreadContext context, final IRubyObject self, final IRubyObject coll) {
final IRubyObject dup = self.callMethod(context, "dup");
java.util.Collection javaDup = unwrapIfJavaObject(dup);
if ( coll instanceof java.util.Collection ) {
javaDup.addAll((java.util.Collection) coll);
}
else {
javaDup.addAll((java.util.Collection) unwrapJavaObject(coll));
}
return dup;
}
@JRubyMethod(name = "-", required = 1)
public static IRubyObject op_minus(final ThreadContext context, final IRubyObject self, final IRubyObject coll) {
final IRubyObject dup = self.callMethod(context, "dup");
java.util.Collection javaDup = unwrapIfJavaObject(dup);
if ( coll instanceof java.util.Collection ) {
javaDup.removeAll((java.util.Collection) coll);
}
else {
javaDup.removeAll((java.util.Collection) unwrapJavaObject(coll));
}
return dup;
}
@JRubyMethod
public static IRubyObject dup(final ThreadContext context, final IRubyObject self) {
java.util.Collection coll = unwrapIfJavaObject(self);
final JavaProxy dup = (JavaProxy) ((RubyBasicObject) self).dup();
if ( coll == dup.getObject() && ! (coll instanceof Cloneable) ) {
dup.setObject( tryNewEqualInstance(coll) );
}
return dup;
}
@JRubyMethod
public static IRubyObject clone(final ThreadContext context, final IRubyObject self) {
java.util.Collection coll = unwrapIfJavaObject(self);
final JavaProxy dup = (JavaProxy) ((RubyBasicObject) self).rbClone();
if ( coll == dup.getObject() && ! (coll instanceof Cloneable) ) {
dup.setObject( tryNewEqualInstance(coll) );
}
return dup;
}
@JRubyMethod
public static IRubyObject join(final ThreadContext context, final IRubyObject self) {
return to_a(context, self).join(context);
}
@JRubyMethod
public static IRubyObject join(final ThreadContext context, final IRubyObject self, final IRubyObject sep) {
return to_a(context, self).join(context, sep);
}
}
@JRubyModule(name = "Java::JavaUtil::List")
public static class List {
static RubyModule define(final Ruby runtime, final RubyModule proxy) {
proxy.defineAnnotatedMethods(List.class);
return proxy;
}
@JRubyMethod(name = "[]")
public static IRubyObject aref(final ThreadContext context, final IRubyObject self, final IRubyObject idx) {
final java.util.List list = unwrapIfJavaObject(self);
final int size = list.size();
if ( idx instanceof RubyRange ) {
int first = idx.callMethod(context, "first").convertToInteger().getIntValue();
int last = idx.callMethod(context, "last").convertToInteger().getIntValue();
if ( last < 0 ) last += size;
if ( first < 0 ) first += size;
if ( first < 0 || first >= size ) return context.nil;
if ( ! ((RubyRange) idx).isExcludeEnd() ) last++;
if ( last > size ) last = size;
return Java.getInstance(context.runtime, list.subList(first, last));
}
int i = idx.convertToInteger().getIntValue();
if ( i < 0 ) i = size + i;
if ( i >= size || i < 0 ) return context.nil;
return convertJavaToUsableRubyObject(context.runtime, list.get(i));
}
@JRubyMethod(name = "[]")
public static IRubyObject aref(final ThreadContext context, final IRubyObject self,
final IRubyObject idx, final IRubyObject len) {
if ( len.isNil() ) return aref(context, self, idx);
final java.util.List list = unwrapIfJavaObject(self);
int i = idx.convertToInteger().getIntValue();
final int size = list.size();
if ( i < 0 ) i = size + i;
if ( i >= size || i < 0 ) return context.nil;
int last = len.convertToInteger().getIntValue();
if ( last < 0 ) return context.nil;
last += i; if ( last > size ) last = size;
return Java.getInstance(context.runtime, list.subList(i, last));
}
@JRubyMethod(name = "[]=")
public static IRubyObject aset(final ThreadContext context, final IRubyObject self,
final IRubyObject idx, final IRubyObject val) {
final java.util.List list = unwrapIfJavaObject(self);
final int size = list.size();
if ( idx instanceof RubyRange ) {
int first = idx.callMethod(context, "first").convertToInteger().getIntValue();
int last = idx.callMethod(context, "last").convertToInteger().getIntValue();
if ( last < 0 ) last += size;
if ( first < 0 ) first += size;
if ( ((RubyRange) idx).isExcludeEnd() ) last--;
for ( int i = last; i >= first; i-- ) {
if ( i < size ) list.remove(i);
else list.add(null);
}
list.add(last, val.toJava(java.lang.Object.class));
return val;
}
int i = idx.convertToInteger().getIntValue();
if ( i < 0 ) i = size + i;
if ( i >= size ) {
for ( int t = 0; t < i - size; t++ ) list.add(null);
list.add(val.toJava(java.lang.Object.class));
}
else {
list.set(i, val.toJava(java.lang.Object.class));
}
return val;
}
@JRubyMethod(name = { "first", "ruby_first" })
public static IRubyObject first(final ThreadContext context, final IRubyObject self) {
final java.util.List list = unwrapIfJavaObject(self);
return list.isEmpty() ? context.nil : convertJavaToUsableRubyObject(context.runtime, list.get(0));
}
@JRubyMethod(name = { "first", "ruby_first" })
public static IRubyObject first(final ThreadContext context, final IRubyObject self, final IRubyObject count) {
final java.util.List list = unwrapIfJavaObject(self);
int len = count.convertToInteger().getIntValue();
int size = list.size(); if ( len > size ) len = size;
return Java.getInstance(context.runtime, list.subList(0, len));
}
@JRubyMethod(name = { "last", "ruby_last" })
public static IRubyObject last(final ThreadContext context, final IRubyObject self) {
final java.util.List list = unwrapIfJavaObject(self);
return list.isEmpty() ? context.nil : convertJavaToUsableRubyObject(context.runtime, list.get(list.size() - 1));
}
@JRubyMethod(name = { "last", "ruby_last" })
public static IRubyObject last(final ThreadContext context, final IRubyObject self, final IRubyObject count) {
final java.util.List list = unwrapIfJavaObject(self);
int len = count.convertToInteger().getIntValue();
int size = list.size();
int start = size - len; if ( start < 0 ) start = 0;
int end = start + len; if ( end > size ) end = size;
return Java.getInstance(context.runtime, list.subList(start, end));
}
@JRubyMethod(name = "index", required = 0)
public static IRubyObject index(final ThreadContext context, final IRubyObject self, final Block block) {
final Ruby runtime = context.runtime;
if ( ! block.isGiven() ) {
return runtime.getEnumerator().callMethod("new", self, runtime.newSymbol("index"));
}
final java.util.List list = unwrapIfJavaObject(self);
if ( list instanceof java.util.RandomAccess ) {
for ( int i = 0; i < list.size(); i++ ) {
IRubyObject ret = block.yield(context, convertJavaToUsableRubyObject(runtime, list.get(i)));
if ( ret.isTrue() ) return runtime.newFixnum(i);
}
}
else {
int i = 0;
for ( Object elem : list ) {
IRubyObject ret = block.yield(context, convertJavaToUsableRubyObject(runtime, elem));
if ( ret.isTrue() ) return runtime.newFixnum(i);
i++;
}
}
return context.nil;
}
@JRubyMethod(name = "index", required = 1)
public static IRubyObject index(final ThreadContext context, final IRubyObject self, final IRubyObject val,
final Block ignoredBlock) {
final Ruby runtime = context.runtime;
final java.util.List list = unwrapIfJavaObject(self);
if ( list instanceof java.util.RandomAccess ) {
for ( int i = 0; i < list.size(); i++ ) {
final Object elem = list.get(i);
if ( val == elem ||
invokedynamic(context, val, OP_EQUAL, convertJavaToUsableRubyObject(runtime, elem)).isTrue() ) {
return runtime.newFixnum(i);
}
}
}
else {
int i = 0;
for ( Object elem : list ) {
if ( val == elem ||
invokedynamic(context, val, OP_EQUAL, convertJavaToUsableRubyObject(runtime, elem)).isTrue() ) {
return runtime.newFixnum(i);
}
i++;
}
}
return context.nil;
}
@JRubyMethod(name = "rindex", required = 0)
public static IRubyObject rindex(final ThreadContext context, final IRubyObject self, final Block block) {
final Ruby runtime = context.runtime;
if ( ! block.isGiven() ) {
return runtime.getEnumerator().callMethod("new", self, runtime.newSymbol("rindex"));
}
final java.util.List list = unwrapIfJavaObject(self);
if ( list instanceof java.util.RandomAccess ) {
for ( int i = list.size() - 1; i >= 0; i-- ) {
IRubyObject ret = block.yield(context, convertJavaToUsableRubyObject(runtime, list.get(i)));
if ( ret.isTrue() ) return runtime.newFixnum(i);
}
}
else {
int i = list.size() - 1;
for ( java.util.ListIterator it = list.listIterator(i); it.hasPrevious(); ) {
final Object elem = it.previous();
IRubyObject ret = block.yield(context, convertJavaToUsableRubyObject(runtime, elem));
if ( ret.isTrue() ) return runtime.newFixnum(i);
i--;
}
}
return context.nil;
}
@JRubyMethod(name = "rindex", required = 1)
public static IRubyObject rindex(final ThreadContext context, final IRubyObject self, final IRubyObject val,
final Block ignoredBlock) {
final Ruby runtime = context.runtime;
final java.util.List list = unwrapIfJavaObject(self);
if ( list instanceof java.util.RandomAccess ) {
for ( int i = list.size() - 1; i >= 0; i-- ) {
final Object elem = list.get(i);
if ( val == elem ||
invokedynamic(context, val, OP_EQUAL, convertJavaToUsableRubyObject(runtime, elem)).isTrue() ) {
return runtime.newFixnum(i);
}
}
}
else {
int i = list.size() - 1;
for ( java.util.ListIterator it = list.listIterator(i); it.hasPrevious(); ) {
final Object elem = it.previous();
if ( val == elem ||
invokedynamic(context, val, OP_EQUAL, convertJavaToUsableRubyObject(runtime, elem)).isTrue() ) {
return runtime.newFixnum(i);
}
i--;
}
}
return context.nil;
}
@JRubyMethod(name = { "to_a", "to_ary" })
public static RubyArray to_a(final ThreadContext context, final IRubyObject self) {
final Ruby runtime = context.runtime;
final java.util.List list = unwrapIfJavaObject(self);
final IRubyObject[] array = new IRubyObject[ list.size() ];
int i = 0; for ( Object elem : list ) {
array[i++] = convertJavaToUsableRubyObject(runtime, elem);;
}
return RubyArray.newArrayMayCopy(runtime, array);
}
@SuppressWarnings("unchecked")
@JRubyMethod(name = { "sort", "ruby_sort" })
public static IRubyObject sort(final ThreadContext context, final IRubyObject self, final Block block) {
final IRubyObject dup = self.callMethod(context, "dup");
java.util.List dupList = unwrapIfJavaObject(dup);
if ( dup == self ) {
dupList = new java.util.ArrayList(dupList);
}
sortImpl(context, dupList, block);
return Java.getInstance(context.runtime, dupList);
}
@SuppressWarnings("unchecked")
@JRubyMethod(name = "sort!")
public static IRubyObject sort_bang(final ThreadContext context, final IRubyObject self, final Block block) {
final java.util.List list = unwrapIfJavaObject(self);
sortImpl(context, list, block);
return self;
}
private static void sortImpl(final ThreadContext context, final java.util.List list, final Block block) {
final java.util.Comparator comparator = block.isGiven() ?
new BlockComparator(context, block) :
new SpaceshipComparator(context);
java.util.Collections.sort(list, comparator);
}
private static final class BlockComparator implements java.util.Comparator {
final ThreadContext context;
private final Block block;
BlockComparator(final ThreadContext context, final Block block) {
this.context = context; this.block = block;
}
@Override
public int compare(final Object o1, final Object o2) {
final IRubyObject r1, r2;
if ( o1 instanceof IRubyObject ) r1 = (IRubyObject) o1;
else r1 = convertJavaToUsableRubyObject(context.runtime, o1);
if ( o2 instanceof IRubyObject ) r2 = (IRubyObject) o2;
else r2 = convertJavaToUsableRubyObject(context.runtime, o2);
return RubyInteger.fix2int( compare(context, r1, r2) );
}
public final IRubyObject compare(final ThreadContext context,
final IRubyObject o1, final IRubyObject o2) {
return block.call(context, o1, o2);
}
}
private static final class SpaceshipComparator implements java.util.Comparator {
final ThreadContext context;
SpaceshipComparator(final ThreadContext context) {
this.context = context;
}
@Override
@SuppressWarnings("unchecked")
public int compare(final Object o1, final Object o2) {
if ( o1 instanceof Comparable && o2 instanceof Comparable ) {
return ((Comparable) o1).compareTo(o2);
}
final IRubyObject r1, r2;
if ( o1 instanceof IRubyObject ) r1 = (IRubyObject) o1;
else r1 = convertJavaToUsableRubyObject(context.runtime, o1);
if ( o2 instanceof IRubyObject ) r2 = (IRubyObject) o2;
else r2 = convertJavaToUsableRubyObject(context.runtime, o2);
return RubyInteger.fix2int( compare(context, r1, r2) );
}
public final IRubyObject compare(final ThreadContext context,
final IRubyObject o1, final IRubyObject o2) {
return o1.callMethod(context, "<=>", o2);
}
}
}
private static java.util.Collection tryNewEqualInstance(final java.util.Collection coll) {
final Class<? extends java.util.Collection> klass = coll.getClass();
try {
Constructor best = null;
for ( Constructor ctor : klass.getDeclaredConstructors() ) {
final Class[] params = ctor.getParameterTypes();
if ( params.length == 1 && params[0].isAssignableFrom(klass) ) {
if ( best == null ) best = ctor;
else {
if ( best.getParameterTypes()[0].isAssignableFrom(params[0]) ) {
best = ctor;
}
}
}
}
if ( best != null ) {
if ( RubyInstanceConfig.SET_ACCESSIBLE ) Java.trySetAccessible(best);
return (java.util.Collection) best.newInstance(coll);
}
}
catch (IllegalAccessException e) {
}
catch (InstantiationException e) {
Helpers.throwException(e); return null;
}
catch (InvocationTargetException e) {
Helpers.throwException(e.getTargetException()); return null;
}
final java.util.Collection clone;
try {
clone = klass.newInstance();
}
catch (IllegalAccessException e) {
return coll;
}
catch (InstantiationException e) {
Helpers.throwException(e); return null;
}
clone.addAll(coll);
return clone;
}
}