package org.jruby.specialized;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyComparable;
import org.jruby.RubyFixnum;
import org.jruby.RubyString;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.JavaSites;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.invokedynamic.MethodNames;
import org.jruby.util.ByteList;
import static org.jruby.runtime.Helpers.arrayOf;
import static org.jruby.runtime.Helpers.invokedynamic;
public class RubyArrayTwoObject extends RubyArraySpecialized {
private IRubyObject car;
private IRubyObject cdr;
public RubyArrayTwoObject(Ruby runtime, IRubyObject car, IRubyObject cdr) {
super(runtime, false);
this.car = car;
this.cdr = cdr;
this.realLength = 2;
}
public RubyArrayTwoObject(RubyClass otherClass, IRubyObject car, IRubyObject cdr) {
super(otherClass, false);
this.car = car;
this.cdr = cdr;
this.realLength = 2;
}
RubyArrayTwoObject(RubyArrayTwoObject other) {
this(other.getMetaClass(), other.car, other.cdr);
}
RubyArrayTwoObject(RubyClass metaClass, RubyArrayTwoObject other) {
this(metaClass, other.car, other.cdr);
}
@Override
public final IRubyObject eltInternal(int index) {
if (!packed()) return super.eltInternal(index);
else if (index == 0) return car;
else if (index == 1) return cdr;
throw new ArrayIndexOutOfBoundsException(index);
}
@Override
public final IRubyObject eltInternalSet(int index, IRubyObject value) {
if (!packed()) return super.eltInternalSet(index, value);
if (index == 0) return this.car = value;
if (index == 1) return this.cdr = value;
throw new ArrayIndexOutOfBoundsException(index);
}
@Override
protected void finishUnpack(IRubyObject nil) {
car = cdr = nil;
}
@Override
public RubyArray aryDup() {
if (!packed()) return super.aryDup();
return new RubyArrayTwoObject(getRuntime().getArray(), this);
}
@Override
public IRubyObject rb_clear() {
if (!packed()) return super.rb_clear();
modifyCheck();
IRubyObject nil = getRuntime().getNil();
car = cdr = nil;
values = IRubyObject.NULL_ARRAY;
realLength = 0;
return this;
}
@Override
public void copyInto(IRubyObject[] target, int start) {
if (!packed()) {
super.copyInto(target, start);
return;
}
target[start] = car;
target[start + 1] = cdr;
}
@Override
public void copyInto(IRubyObject[] target, int start, int len) {
if (!packed()) {
super.copyInto(target, start, len);
return;
}
if (len != 2) {
unpack();
super.copyInto(target, start, len);
return;
}
target[start] = car;
target[start + 1] = cdr;
}
@Override
protected RubyArray dupImpl(Ruby runtime, RubyClass metaClass) {
if (!packed()) return super.dupImpl(runtime, metaClass);
return new RubyArrayTwoObject(metaClass, this);
}
@Override
public boolean includes(ThreadContext context, IRubyObject item) {
if (!packed()) return super.includes(context, item);
if (equalInternal(context, car, item)) return true;
if (equalInternal(context, cdr, item)) return true;
return false;
}
@Override
public int indexOf(Object element) {
if (!packed()) return super.indexOf(element);
if (element != null) {
IRubyObject convertedElement = JavaUtil.convertJavaToUsableRubyObject(getRuntime(), element);
if (convertedElement.equals(car)) return 0;
if (convertedElement.equals(cdr)) return 1;
}
return -1;
}
@Override
protected IRubyObject inspectAry(ThreadContext context) {
if (!packed()) return super.inspectAry(context);
final Ruby runtime = context.runtime;
RubyString str = RubyString.newStringLight(runtime, DEFAULT_INSPECT_STR_SIZE, USASCIIEncoding.INSTANCE);
str.cat((byte) '[');
boolean tainted = isTaint();
RubyString s1 = inspect(context, car);
RubyString s2 = inspect(context, cdr);
if (s1.isTaint()) tainted = true;
else str.setEncoding(s1.getEncoding());
str.cat19(s1);
ByteList bytes = str.getByteList();
bytes.ensure(2 + s2.size() + 1);
bytes.append((byte) ',').append((byte) ' ');
if (s2.isTaint()) tainted = true;
else str.setEncoding(s2.getEncoding());
str.cat19(s2);
str.cat((byte) ']');
if (tainted) str.setTaint(true);
return str;
}
@Override
protected IRubyObject internalRotate(ThreadContext context, int cnt) {
if (!packed()) return super.internalRotate(context, cnt);
if (cnt % 2 == 1) return new RubyArrayTwoObject(context.runtime, cdr, car);
return new RubyArrayTwoObject(context.runtime, car, cdr);
}
@Override
protected IRubyObject internalRotateBang(ThreadContext context, int cnt) {
if (!packed()) return super.internalRotateBang(context, cnt);
modifyCheck();
if (cnt % 2 == 1) {
IRubyObject tmp = car;
car = cdr;
cdr = tmp;
}
return context.nil;
}
@Override
public IRubyObject op_plus(IRubyObject obj) {
if (!packed()) return super.op_plus(obj);
RubyArray y = obj.convertToArray();
if (y.size() == 0) return new RubyArrayTwoObject(this);
return super.op_plus(y);
}
@Override
public IRubyObject replace(IRubyObject orig) {
if (!packed()) return super.replace(orig);
modifyCheck();
RubyArray origArr = orig.convertToArray();
if (this == orig) return this;
if (origArr.size() == 2) {
car = origArr.eltInternal(0);
cdr = origArr.eltInternal(1);
return this;
}
unpack();
return super.replace(origArr);
}
@Override
public IRubyObject reverse_bang() {
if (!packed()) return super.reverse_bang();
IRubyObject tmp = car;
car = cdr;
cdr = tmp;
return this;
}
@Override
protected RubyArray safeReverse() {
if (!packed()) return super.safeReverse();
return new RubyArrayTwoObject(getMetaClass(), cdr, car);
}
@Override
protected IRubyObject sortInternal(ThreadContext context, Block block) {
if (!packed()) return super.sortInternal(context, block);
IRubyObject ret = block.yieldArray(context, newArray(context.runtime, car, cdr), null);
int compare = RubyComparable.cmpint(context, ret, car, cdr);
if (compare > 0) reverse_bang();
return this;
}
@Override
protected IRubyObject sortInternal(final ThreadContext context, boolean honorOverride) {
if (!packed()) return super.sortInternal(context, honorOverride);
Ruby runtime = context.runtime;
JavaSites.Array2Sites sites = sites(context);
final boolean fixnumBypass = !honorOverride || sites.op_cmp_fixnum.retrieveCache(runtime.getFixnum()).method.isBuiltin();
final boolean stringBypass = !honorOverride || sites.op_cmp_string.retrieveCache(runtime.getString()).method.isBuiltin();
IRubyObject o1 = car;
IRubyObject o2 = cdr;
int compare;
if (fixnumBypass && o1 instanceof RubyFixnum && o2 instanceof RubyFixnum) {
compare = compareFixnums((RubyFixnum) o1, (RubyFixnum) o2);
} else if (stringBypass && o1 instanceof RubyString && o2 instanceof RubyString) {
compare = ((RubyString) o1).op_cmp((RubyString) o2);
} else {
compare = compareOthers(context, o1, o2);
}
if (compare > 0) reverse_bang();
return this;
}
@Override
protected void storeInternal(final int index, final IRubyObject value) {
if (packed()) {
switch (index) {
case 0: car = value; return;
case 1: cdr = value; return;
}
unpack();
}
super.storeInternal(index, value);
}
@Override
public IRubyObject subseq(RubyClass metaClass, long beg, long len, boolean light) {
if (!packed()) return super.subseq(metaClass, beg, len, light);
Ruby runtime = getRuntime();
if (beg > 2 || beg < 0 || len < 0) return runtime.getNil();
if (len == 0 || beg == 2) return new RubyArray(runtime, metaClass, IRubyObject.NULL_ARRAY);
if (beg == 0) {
if (len == 1) return new RubyArrayOneObject(metaClass, car);
return new RubyArrayTwoObject(metaClass, this);
}
return new RubyArrayOneObject(metaClass, cdr);
}
@Override
public IRubyObject[] toJavaArray() {
if (!packed()) return super.toJavaArray();
return arrayOf(car, cdr);
}
@Override
public IRubyObject uniq(ThreadContext context) {
if (!packed()) return super.uniq(context);
if (invokedynamic(context, car, MethodNames.HASH).equals(invokedynamic(context, cdr, MethodNames.HASH)) &&
(car == cdr || invokedynamic(context, car, MethodNames.EQL, cdr).isTrue())) {
return new RubyArrayOneObject(getMetaClass(), cdr);
} else {
return new RubyArrayTwoObject(this);
}
}
private static final JavaSites.Array2Sites sites(ThreadContext context) {
return context.sites.Array2;
}
}