org.jruby.RubyArray Maven / Gradle / Ivy
/*
**** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2001 Alan Moore
* Copyright (C) 2001 Chad Fowler
* Copyright (C) 2001-2002 Benoit Cerrina
* Copyright (C) 2001-2004 Jan Arne Petersen
* Copyright (C) 2002-2004 Anders Bengtsson
* Copyright (C) 2002-2005 Thomas E Enebo
* Copyright (C) 2004-2005 Charles O Nutter
* Copyright (C) 2004 Stefan Matthias Aust
* Copyright (C) 2006 Ola Bini
* Copyright (C) 2006 Daniel Steer
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby;
import org.jcodings.Encoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.java.util.ArrayUtils;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.encoding.EncodingCapable;
import org.jruby.runtime.invokedynamic.MethodNames;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.Pack;
import org.jruby.util.Qsort;
import org.jruby.util.RecursiveComparator;
import org.jruby.util.TypeConverter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;
import java.util.concurrent.Callable;
import static org.jruby.CompatVersion.RUBY1_8;
import static org.jruby.CompatVersion.RUBY1_9;
import static org.jruby.CompatVersion.RUBY2_0;
import static org.jruby.RubyEnumerator.enumeratorize;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.runtime.invokedynamic.MethodNames.HASH;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_CMP;
/**
* The implementation of the built-in class Array in Ruby.
*
* Concurrency: no synchronization is required among readers, but
* all users must synchronize externally with writers.
*
*/
@JRubyClass(name="Array")
public class RubyArray extends RubyObject implements List, RandomAccess {
public static final int DEFAULT_INSPECT_STR_SIZE = 10;
public static RubyClass createArrayClass(Ruby runtime) {
RubyClass arrayc = runtime.defineClass("Array", runtime.getObject(), ARRAY_ALLOCATOR);
runtime.setArray(arrayc);
arrayc.index = ClassIndex.ARRAY;
arrayc.setReifiedClass(RubyArray.class);
arrayc.kindOf = new RubyModule.JavaClassKindOf(RubyArray.class);
arrayc.includeModule(runtime.getEnumerable());
arrayc.defineAnnotatedMethods(RubyArray.class);
return arrayc;
}
private static ObjectAllocator ARRAY_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyArray(runtime, klass, IRubyObject.NULL_ARRAY);
}
};
@Override
public int getNativeTypeIndex() {
return ClassIndex.ARRAY;
}
private final void concurrentModification() {
concurrentModification(getRuntime());
}
private static void concurrentModification(Ruby runtime) {
throw runtime.newConcurrencyError("Detected invalid array contents due to unsynchronized modifications with concurrent users");
}
/** rb_ary_s_create
*
*/
@JRubyMethod(name = "[]", rest = true, meta = true)
public static IRubyObject create(IRubyObject klass, IRubyObject[] args, Block block) {
RubyArray arr = (RubyArray) ((RubyClass) klass).allocate();
if (args.length > 0) {
arr.values = new IRubyObject[args.length];
System.arraycopy(args, 0, arr.values, 0, args.length);
arr.realLength = args.length;
}
return arr;
}
/** rb_ary_new2
*
*/
public static final RubyArray newArray(final Ruby runtime, final long len) {
checkLength(runtime, len);
return newArray(runtime, (int)len);
}
public static final RubyArray newArrayLight(final Ruby runtime, final long len) {
checkLength(runtime, len);
return newArrayLight(runtime, (int)len);
}
public static final RubyArray newArray(final Ruby runtime, final int len) {
RubyArray array = new RubyArray(runtime, len);
Helpers.fillNil(array.values, 0, array.values.length, runtime);
return array;
}
public static final RubyArray newArrayLight(final Ruby runtime, final int len) {
RubyArray array = new RubyArray(runtime, len, false);
Helpers.fillNil(array.values, 0, array.values.length, runtime);
return array;
}
/** rb_ary_new
*
*/
public static final RubyArray newArray(final Ruby runtime) {
return newArray(runtime, ARRAY_DEFAULT_SIZE);
}
/** rb_ary_new
*
*/
public static final RubyArray newArrayLight(final Ruby runtime) {
/* Ruby arrays default to holding 16 elements, so we create an
* ArrayList of the same size if we're not told otherwise
*/
return newArrayLight(runtime, ARRAY_DEFAULT_SIZE);
}
public static RubyArray newArray(Ruby runtime, IRubyObject obj) {
return new RubyArray(runtime, new IRubyObject[] { obj });
}
public static RubyArray newArrayLight(Ruby runtime, IRubyObject obj) {
return new RubyArray(runtime, new IRubyObject[] { obj }, false);
}
public static RubyArray newArrayLight(Ruby runtime, IRubyObject... objs) {
return new RubyArray(runtime, objs, false);
}
/** rb_assoc_new
*
*/
public static RubyArray newArray(Ruby runtime, IRubyObject car, IRubyObject cdr) {
return new RubyArray(runtime, new IRubyObject[] { car, cdr });
}
public static RubyArray newEmptyArray(Ruby runtime) {
return new RubyArray(runtime, NULL_ARRAY);
}
/** rb_ary_new4, rb_ary_new3
*
*/
public static RubyArray newArray(Ruby runtime, IRubyObject[] args) {
RubyArray arr = new RubyArray(runtime, new IRubyObject[args.length]);
System.arraycopy(args, 0, arr.values, 0, args.length);
arr.realLength = args.length;
return arr;
}
public static RubyArray newArrayNoCopy(Ruby runtime, IRubyObject[] args) {
return new RubyArray(runtime, args);
}
public static RubyArray newArrayNoCopy(Ruby runtime, IRubyObject[] args, int begin) {
return new RubyArray(runtime, args, begin);
}
public static RubyArray newArrayNoCopy(Ruby runtime, IRubyObject[] args, int begin, int length) {
assert begin >= 0 : "begin must be >= 0";
assert length >= 0 : "length must be >= 0";
return new RubyArray(runtime, args, begin, length);
}
public static RubyArray newArrayNoCopyLight(Ruby runtime, IRubyObject[] args) {
RubyArray arr = new RubyArray(runtime, false);
arr.values = args;
arr.realLength = args.length;
return arr;
}
public static RubyArray newArray(Ruby runtime, Collection extends IRubyObject> collection) {
return new RubyArray(runtime, collection.toArray(new IRubyObject[collection.size()]));
}
public static final int ARRAY_DEFAULT_SIZE = 16;
// volatile to ensure that initial nil-fill is visible to other threads
private volatile IRubyObject[] values;
private static final int TMPLOCK_ARR_F = 1 << 9;
private static final int TMPLOCK_OR_FROZEN_ARR_F = TMPLOCK_ARR_F | FROZEN_F;
private volatile boolean isShared = false;
private int begin = 0;
private int realLength = 0;
private static final ByteList EMPTY_ARRAY_BYTELIST = new ByteList(ByteList.plain("[]"), USASCIIEncoding.INSTANCE);
private static final ByteList RECURSIVE_ARRAY_BYTELIST = new ByteList(ByteList.plain("[...]"), USASCIIEncoding.INSTANCE);
/*
* plain internal array assignment
*/
private RubyArray(Ruby runtime, IRubyObject[] vals) {
super(runtime, runtime.getArray());
values = vals;
realLength = vals.length;
}
/*
* plain internal array assignment
*/
private RubyArray(Ruby runtime, IRubyObject[] vals, boolean objectSpace) {
super(runtime, runtime.getArray(), objectSpace);
values = vals;
realLength = vals.length;
}
/*
* plain internal array assignment
*/
private RubyArray(Ruby runtime, IRubyObject[] vals, int begin) {
super(runtime, runtime.getArray());
this.values = vals;
this.begin = begin;
this.realLength = vals.length - begin;
this.isShared = true;
}
private RubyArray(Ruby runtime, IRubyObject[] vals, int begin, int length) {
super(runtime, runtime.getArray());
this.values = vals;
this.begin = begin;
this.realLength = length;
this.isShared = true;
}
private RubyArray(Ruby runtime, RubyClass metaClass, IRubyObject[] vals, int begin, int length) {
super(runtime, metaClass);
this.values = vals;
this.begin = begin;
this.realLength = length;
this.isShared = true;
}
protected RubyArray(Ruby runtime, int length) {
super(runtime, runtime.getArray());
values = length == 0 ? IRubyObject.NULL_ARRAY : new IRubyObject[length];
}
private RubyArray(Ruby runtime, int length, boolean objectspace) {
super(runtime, runtime.getArray(), objectspace);
values = length == 0 ? IRubyObject.NULL_ARRAY : new IRubyObject[length];
}
/* NEWOBJ and OBJSETUP equivalent
* fastest one, for shared arrays, optional objectspace
*/
private RubyArray(Ruby runtime, boolean objectSpace) {
super(runtime, runtime.getArray(), objectSpace);
}
private RubyArray(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
/* Array constructors taking the MetaClass to fulfil MRI Array subclass behaviour
*
*/
private RubyArray(Ruby runtime, RubyClass klass, int length) {
super(runtime, klass);
values = length == 0 ? IRubyObject.NULL_ARRAY : new IRubyObject[length];
}
private RubyArray(Ruby runtime, RubyClass klass, IRubyObject[]vals, boolean objectspace) {
super(runtime, klass, objectspace);
values = vals;
}
private RubyArray(Ruby runtime, RubyClass klass, boolean objectSpace) {
super(runtime, klass, objectSpace);
}
private RubyArray(Ruby runtime, RubyClass klass, RubyArray original) {
super(runtime, klass);
realLength = original.realLength;
values = new IRubyObject[realLength];
safeArrayCopy(runtime, original.values, original.begin, values, 0, realLength);
}
private RubyArray(Ruby runtime, RubyClass klass, IRubyObject[] vals) {
super(runtime, klass);
values = vals;
realLength = vals.length;
}
private void alloc(int length) {
IRubyObject[] newValues = length == 0 ? IRubyObject.NULL_ARRAY : new IRubyObject[length];
Helpers.fillNil(newValues, getRuntime());
values = newValues;
begin = 0;
}
private void realloc(int newLength, int valuesLength) {
IRubyObject[] reallocated = new IRubyObject[newLength];
if (newLength > valuesLength) {
Helpers.fillNil(reallocated, valuesLength, newLength, getRuntime());
safeArrayCopy(values, begin, reallocated, 0, valuesLength); // elements and trailing nils
} else {
safeArrayCopy(values, begin, reallocated, 0, newLength); // ???
}
begin = 0;
values = reallocated;
}
private static void fill(IRubyObject[]arr, int from, int to, IRubyObject with) {
for (int i=from; i= Integer.MAX_VALUE) {
throw runtime.newArgumentError("array size too big");
}
}
/** Getter for property list.
* @return Value of property list.
*/
public List getList() {
return Arrays.asList(toJavaArray());
}
public int getLength() {
return realLength;
}
public IRubyObject[] toJavaArray() {
IRubyObject[] copy = new IRubyObject[realLength];
safeArrayCopy(values, begin, copy, 0, realLength);
return copy;
}
public IRubyObject[] toJavaArrayUnsafe() {
return !isShared ? values : toJavaArray();
}
public IRubyObject[] toJavaArrayMaybeUnsafe() {
return (!isShared && begin == 0 && values.length == realLength) ? values : toJavaArray();
}
/** rb_ary_make_shared
*
*/
private RubyArray makeShared() {
return makeShared(begin, realLength, getMetaClass());
}
private RubyArray makeShared(int beg, int len, RubyClass klass) {
return makeShared(beg, len, new RubyArray(klass.getRuntime(), klass));
}
private RubyArray makeShared(int beg, int len, RubyArray sharedArray) {
isShared = true;
sharedArray.values = values;
sharedArray.isShared = true;
sharedArray.begin = beg;
sharedArray.realLength = len;
return sharedArray;
}
/** ary_shared_first
*
*/
private RubyArray makeSharedFirst(ThreadContext context, IRubyObject num, boolean last, RubyClass klass) {
int n = RubyNumeric.num2int(num);
if (n > realLength) {
n = realLength;
} else if (n < 0) {
throw context.runtime.newArgumentError("negative array size");
}
return makeShared(last ? begin + realLength - n : begin, n, klass);
}
/** rb_ary_modify_check
*
*/
private final void modifyCheck() {
if ((flags & TMPLOCK_OR_FROZEN_ARR_F) != 0) {
if ((flags & FROZEN_F) != 0) throw getRuntime().newFrozenError("array");
if ((flags & TMPLOCK_ARR_F) != 0) throw getRuntime().newTypeError("can't modify array during iteration");
}
}
/** rb_ary_modify
*
*/
private final void modify() {
modifyCheck();
if (isShared) {
IRubyObject[] vals = new IRubyObject[realLength];
safeArrayCopy(values, begin, vals, 0, realLength);
begin = 0;
values = vals;
isShared = false;
}
}
/* ================
* Instance Methods
* ================
*/
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
switch (args.length) {
case 0:
return initialize(context, block);
case 1:
return initializeCommon(context, args[0], null, block);
case 2:
return initializeCommon(context, args[0], args[1], block);
default:
Arity.raiseArgumentError(getRuntime(), args.length, 0, 2);
return null; // not reached
}
}
/** rb_ary_initialize
*
*/
@JRubyMethod(visibility = PRIVATE)
public IRubyObject initialize(ThreadContext context, Block block) {
modifyCheck();
Ruby runtime = context.runtime;
realLength = 0;
if (block.isGiven() && runtime.isVerbose()) {
runtime.getWarnings().warning(ID.BLOCK_UNUSED, "given block not used");
}
return this;
}
/** rb_ary_initialize
*
*/
@JRubyMethod(visibility = PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject arg0, Block block) {
return initializeCommon(context, arg0, null, block);
}
/** rb_ary_initialize
*
*/
@JRubyMethod(visibility = PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
return initializeCommon(context, arg0, arg1, block);
}
private IRubyObject initializeCommon(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
Ruby runtime = context.runtime;
if (arg1 == null && !(arg0 instanceof RubyFixnum)) {
IRubyObject val = arg0.checkArrayType();
if (!val.isNil()) {
replace(val);
return this;
}
}
long len = RubyNumeric.num2long(arg0);
if (len < 0) throw runtime.newArgumentError("negative array size");
if (len >= Integer.MAX_VALUE) throw runtime.newArgumentError("array size too big");
int ilen = (int) len;
modify();
if (ilen > values.length - begin) {
values = new IRubyObject[ilen];
begin = 0;
}
if (block.isGiven()) {
if (arg1 != null) {
runtime.getWarnings().warn(ID.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
}
if (block.getBody().getArgumentType() == BlockBody.ZERO_ARGS) {
IRubyObject nil = runtime.getNil();
for (int i = 0; i < ilen; i++) {
store(i, block.yield(context, nil));
realLength = i + 1;
}
} else {
for (int i = 0; i < ilen; i++) {
store(i, block.yield(context, RubyFixnum.newFixnum(runtime, i)));
realLength = i + 1;
}
}
} else {
try {
if (arg1 == null) {
Helpers.fillNil(values, begin, begin + ilen, runtime);
} else {
fill(values, begin, begin + ilen, arg1);
}
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
realLength = ilen;
}
return this;
}
/** rb_ary_initialize_copy
*
*/
@JRubyMethod(name = {"initialize_copy"}, required = 1, visibility=PRIVATE)
@Override
public IRubyObject initialize_copy(IRubyObject orig) {
return this.replace(orig);
}
/**
* Overridden dup for fast-path logic.
*
* @return A new RubyArray sharing the original backing store.
*/
public IRubyObject dup() {
if (metaClass.index != ClassIndex.ARRAY) return super.dup();
RubyArray dup = new RubyArray(metaClass.getClassRuntime(), values, begin, realLength);
dup.isShared = isShared = true;
dup.flags |= flags & TAINTED_F; // from DUP_SETUP
dup.flags |= flags & UNTRUSTED_F;
return dup;
}
/** rb_ary_replace
*
*/
@JRubyMethod(name = {"replace"}, required = 1)
public IRubyObject replace(IRubyObject orig) {
modifyCheck();
RubyArray origArr = orig.convertToArray();
if (this == orig) return this;
origArr.isShared = true;
isShared = true;
values = origArr.values;
realLength = origArr.realLength;
begin = origArr.begin;
return this;
}
/** rb_ary_to_s
*
*/
@JRubyMethod(name = "to_s")
@Override
public IRubyObject to_s() {
if (getRuntime().is1_9()) {
// 1.9 seems to just do inspect for to_s now
return inspect();
}
if (realLength == 0) return RubyString.newEmptyString(getRuntime());
return join(getRuntime().getCurrentContext(), getRuntime().getGlobalVariables().get("$,"));
}
public boolean includes(ThreadContext context, IRubyObject item) {
int myBegin = this.begin;
int end = myBegin + realLength;
IRubyObject[] values = this.values;
for (int i = myBegin; i < end; i++) {
final IRubyObject value = safeArrayRef(values, i);
if (equalInternal(context, value, item)) return true;
}
return false;
}
/** rb_ary_hash
*
*/
@JRubyMethod(name = "hash", compat = RUBY1_8)
public RubyFixnum hash(ThreadContext context) {
Ruby runtime = context.runtime;
if (runtime.isInspecting(this)) return RubyFixnum.zero(runtime);
try {
runtime.registerInspecting(this);
int myBegin = this.begin;
int h = realLength;
for (int i = myBegin; i < myBegin + realLength; i++) {
h = (h << 1) | (h < 0 ? 1 : 0);
final IRubyObject value = safeArrayRef(values, i);
h ^= RubyNumeric.num2long(invokedynamic(context, value, HASH));
}
return runtime.newFixnum(h);
} finally {
runtime.unregisterInspecting(this);
}
}
/** rb_ary_hash
*
*/
@JRubyMethod(name = "hash", compat = RUBY1_9)
public RubyFixnum hash19(final ThreadContext context) {
return (RubyFixnum) getRuntime().execRecursiveOuter(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
int begin = RubyArray.this.begin;
long h = realLength;
if (recur) {
h ^= RubyNumeric.num2long(invokedynamic(context, context.runtime.getArray(), HASH));
} else {
for (int i = begin; i < begin + realLength; i++) {
h = (h << 1) | (h < 0 ? 1 : 0);
final IRubyObject value = safeArrayRef(values, i);
h ^= RubyNumeric.num2long(invokedynamic(context, value, HASH));
}
}
return getRuntime().newFixnum(h);
}
}, RubyArray.this);
}
/** rb_ary_store
*
*/
public final IRubyObject store(long index, IRubyObject value) {
if (index < 0 && (index += realLength) < 0) throw getRuntime().newIndexError("index " + (index - realLength) + " out of array");
modify();
if (index >= realLength) {
int valuesLength = values.length - begin;
if (index >= valuesLength) storeRealloc(index, valuesLength);
realLength = (int) index + 1;
}
safeArraySet(values, begin + (int) index, value);
return value;
}
private void storeRealloc(long index, int valuesLength) {
long newLength = valuesLength >> 1;
if (newLength < ARRAY_DEFAULT_SIZE) newLength = ARRAY_DEFAULT_SIZE;
newLength += index;
if (index >= Integer.MAX_VALUE || newLength >= Integer.MAX_VALUE) {
throw getRuntime().newArgumentError("index too big");
}
realloc((int) newLength, valuesLength);
}
/** rb_ary_elt
*
*/
private final IRubyObject elt(long offset) {
if (offset < 0 || offset >= realLength) {
return getRuntime().getNil();
}
return eltOk(offset);
}
public final IRubyObject eltOk(long offset) {
return safeArrayRef(values, begin + (int)offset);
}
/** rb_ary_entry
*
*/
public final IRubyObject entry(long offset) {
return (offset < 0 ) ? elt(offset + realLength) : elt(offset);
}
public final IRubyObject entry(int offset) {
return (offset < 0 ) ? elt(offset + realLength) : elt(offset);
}
public final IRubyObject eltInternal(int offset) {
return values[begin + offset];
}
public final IRubyObject eltInternalSet(int offset, IRubyObject item) {
return values[begin + offset] = item;
}
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject fetch(ThreadContext context, IRubyObject[] args, Block block) {
switch (args.length) {
case 1:
return fetch(context, args[0], block);
case 2:
return fetch(context, args[0], args[1], block);
default:
Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
return null; // not reached
}
}
/** rb_ary_fetch
*
*/
@JRubyMethod
public IRubyObject fetch(ThreadContext context, IRubyObject arg0, Block block) {
long index = RubyNumeric.num2long(arg0);
if (index < 0) index += realLength;
if (index < 0 || index >= realLength) {
if (block.isGiven()) return block.yield(context, arg0);
throw getRuntime().newIndexError("index " + index + " out of array");
}
return safeArrayRef(values, begin + (int) index);
}
/** rb_ary_fetch
*
*/
@JRubyMethod
public IRubyObject fetch(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
if (block.isGiven()) getRuntime().getWarnings().warn(ID.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
long index = RubyNumeric.num2long(arg0);
if (index < 0) index += realLength;
if (index < 0 || index >= realLength) {
if (block.isGiven()) return block.yield(context, arg0);
return arg1;
}
return safeArrayRef(values, begin + (int) index);
}
/** rb_ary_to_ary
*
*/
private static RubyArray aryToAry(IRubyObject obj) {
if (obj instanceof RubyArray) return (RubyArray) obj;
if (obj.respondsTo("to_ary")) return obj.convertToArray();
RubyArray arr = new RubyArray(obj.getRuntime(), false); // possibly should not in object space
arr.values = new IRubyObject[]{obj};
arr.realLength = 1;
return arr;
}
/** rb_ary_splice
*
*/
private final void splice(long beg, long len, IRubyObject rpl, boolean oneNine) {
if (len < 0) throw getRuntime().newIndexError("negative length (" + len + ")");
if (beg < 0 && (beg += realLength) < 0) throw getRuntime().newIndexError("index " + (beg - realLength) + " out of array");
final RubyArray rplArr;
final int rlen;
if (rpl == null || (rpl.isNil() && !oneNine)) {
rplArr = null;
rlen = 0;
} else if (rpl.isNil()) {
// 1.9 replaces with nil
rplArr = newArray(getRuntime(), rpl);
rlen = 1;
} else {
rplArr = aryToAry(rpl);
rlen = rplArr.realLength;
}
modify();
int valuesLength = values.length - begin;
if (beg >= realLength) {
len = beg + rlen;
if (len >= valuesLength) spliceRealloc((int)len, valuesLength);
try {
Helpers.fillNil(values, begin + realLength, begin + ((int) beg), getRuntime());
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
realLength = (int) len;
} else {
if (beg + len > realLength) len = realLength - beg;
int alen = realLength + rlen - (int)len;
if (alen >= valuesLength) spliceRealloc(alen, valuesLength);
if (len != rlen) {
safeArrayCopy(values, begin + (int) (beg + len), values, begin + (int) beg + rlen, realLength - (int) (beg + len));
realLength = alen;
}
}
if (rlen > 0) {
safeArrayCopy(rplArr.values, rplArr.begin, values, begin + (int) beg, rlen);
}
}
/** rb_ary_splice
*
*/
private final void spliceOne(long beg, IRubyObject rpl) {
if (beg < 0 && (beg += realLength) < 0) throw getRuntime().newIndexError("index " + (beg - realLength) + " out of array");
modify();
int valuesLength = values.length - begin;
if (beg >= realLength) {
int len = (int) beg + 1;
if (len >= valuesLength) spliceRealloc(len, valuesLength);
Helpers.fillNil(values, begin + realLength, begin + ((int) beg), getRuntime());
realLength = len;
} else {
int len = beg > realLength ? realLength - (int) beg : 0;
int alen = realLength + 1 - len;
if (alen >= valuesLength) spliceRealloc(alen, valuesLength);
if (len == 0) {
safeArrayCopy(values, begin + (int) beg, values, begin + (int) beg + 1, realLength - (int) beg);
realLength = alen;
}
}
safeArraySet(values, begin + (int)beg, rpl);
}
private void spliceRealloc(int length, int valuesLength) {
int tryLength = valuesLength + (valuesLength >> 1);
int len = length > tryLength ? length : tryLength;
IRubyObject[] vals = new IRubyObject[len];
System.arraycopy(values, begin, vals, 0, realLength);
// only fill if there actually will remain trailing storage
if (len > length) Helpers.fillNil(vals, length, len, getRuntime());
begin = 0;
values = vals;
}
@JRubyMethod
public IRubyObject insert() {
throw getRuntime().newArgumentError(0, 1);
}
/** rb_ary_insert
*
*/
@JRubyMethod(name = "insert", compat = RUBY1_8)
public IRubyObject insert(IRubyObject arg) {
return this;
}
@JRubyMethod(name = "insert", compat = RUBY1_9)
public IRubyObject insert19(IRubyObject arg) {
modifyCheck();
return insert(arg);
}
@JRubyMethod(name = "insert", compat = RUBY1_8)
public IRubyObject insert(IRubyObject arg1, IRubyObject arg2) {
long pos = RubyNumeric.num2long(arg1);
if (pos == -1) pos = realLength;
if (pos < 0) pos++;
spliceOne(pos, arg2); // rb_ary_new4
return this;
}
@JRubyMethod(name = "insert", compat = RUBY1_9)
public IRubyObject insert19(IRubyObject arg1, IRubyObject arg2) {
modifyCheck();
return insert(arg1, arg2);
}
@JRubyMethod(name = "insert", required = 1, rest = true, compat = RUBY1_8)
public IRubyObject insert(IRubyObject[] args) {
if (args.length == 1) return this;
long pos = RubyNumeric.num2long(args[0]);
if (pos == -1) pos = realLength;
if (pos < 0) pos++;
RubyArray inserted = new RubyArray(getRuntime(), false);
inserted.values = args;
inserted.begin = 1;
inserted.realLength = args.length - 1;
splice(pos, 0, inserted, false); // rb_ary_new4
return this;
}
@JRubyMethod(name = "insert", required = 1, rest = true, compat = RUBY1_9)
public IRubyObject insert19(IRubyObject[] args) {
modifyCheck();
return insert(args);
}
/** rb_ary_dup
*
*/
public final RubyArray aryDup() {
RubyArray dup = new RubyArray(metaClass.getClassRuntime(), metaClass, values, begin, realLength);
dup.isShared = true;
isShared = true;
dup.flags |= flags & (TAINTED_F | UNTRUSTED_F); // from DUP_SETUP
// rb_copy_generic_ivar from DUP_SETUP here ...unlikely..
return dup;
}
public final RubyArray aryDup19() {
// In 1.9, rb_ary_dup logic changed so that on subclasses of Array,
// dup returns an instance of Array, rather than an instance of the subclass
// Also, taintedness and trustedness are not inherited to duplicates
RubyArray dup = new RubyArray(metaClass.getClassRuntime(), values, begin, realLength);
dup.isShared = true;
isShared = true;
// rb_copy_generic_ivar from DUP_SETUP here ...unlikely..
return dup;
}
/** rb_ary_transpose
*
*/
@JRubyMethod(name = "transpose")
public RubyArray transpose() {
RubyArray tmp, result = null;
int alen = realLength;
if (alen == 0) return aryDup();
Ruby runtime = getRuntime();
int elen = -1;
int end = begin + alen;
for (int i = begin; i < end; i++) {
tmp = elt(i - begin).convertToArray();
if (elen < 0) {
elen = tmp.realLength;
result = new RubyArray(runtime, elen);
for (int j = 0; j < elen; j++) {
result.store(j, new RubyArray(runtime, alen));
}
} else if (elen != tmp.realLength) {
throw runtime.newIndexError("element size differs (" + tmp.realLength
+ " should be " + elen + ")");
}
for (int j = 0; j < elen; j++) {
((RubyArray) result.elt(j)).store(i - begin, tmp.elt(j));
}
}
return result;
}
/** rb_values_at (internal)
*
*/
private final IRubyObject values_at(long olen, IRubyObject[] args) {
RubyArray result = new RubyArray(getRuntime(), args.length);
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof RubyFixnum) {
result.append(entry(((RubyFixnum)args[i]).getLongValue()));
continue;
}
long beglen[];
if (!(args[i] instanceof RubyRange)) {
} else if ((beglen = ((RubyRange) args[i]).begLen(olen, 0)) == null) {
continue;
} else {
int beg = (int) beglen[0];
int len = (int) beglen[1];
int end = begin + len;
for (int j = begin; j < end; j++) {
result.append(entry(j + beg));
}
continue;
}
result.append(entry(RubyNumeric.num2long(args[i])));
}
Helpers.fillNil(result.values, result.realLength, result.values.length, getRuntime());
return result;
}
/** rb_values_at
*
*/
@JRubyMethod(name = "values_at", rest = true)
public IRubyObject values_at(IRubyObject[] args) {
return values_at(realLength, args);
}
/** rb_ary_subseq
*
*/
public IRubyObject subseq(long beg, long len) {
int realLength = this.realLength;
if (beg > realLength || beg < 0 || len < 0) return getRuntime().getNil();
if (beg + len > realLength) {
len = realLength - beg;
if (len < 0) len = 0;
}
if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), IRubyObject.NULL_ARRAY);
return makeShared(begin + (int) beg, (int) len, getMetaClass());
}
/** rb_ary_subseq
*
*/
public IRubyObject subseqLight(long beg, long len) {
Ruby runtime = getRuntime();
if (beg > realLength || beg < 0 || len < 0) return runtime.getNil();
if (beg + len > realLength) {
len = realLength - beg;
if (len < 0) len = 0;
}
if (len == 0) return new RubyArray(runtime, getMetaClass(), IRubyObject.NULL_ARRAY, false);
return makeShared(begin + (int) beg, (int) len, new RubyArray(runtime, getMetaClass(), false));
}
/** rb_ary_length
*
*/
@JRubyMethod(name = "length", alias = "size")
public RubyFixnum length() {
return getRuntime().newFixnum(realLength);
}
/** rb_ary_push - specialized rb_ary_store
*
*/
@JRubyMethod(name = "<<", required = 1)
public RubyArray append(IRubyObject item) {
modify();
int valuesLength = values.length - begin;
if (realLength == valuesLength) {
if (realLength == Integer.MAX_VALUE) throw getRuntime().newArgumentError("index too big");
long newLength = valuesLength + (valuesLength >> 1);
if (newLength > Integer.MAX_VALUE) {
newLength = Integer.MAX_VALUE;
} else if (newLength < ARRAY_DEFAULT_SIZE) {
newLength = ARRAY_DEFAULT_SIZE;
}
realloc((int) newLength, valuesLength);
}
safeArraySet(values, begin + realLength++, item);
return this;
}
/** rb_ary_push_m - instance method push
*
*/
@JRubyMethod(name = "push", rest = true, compat = RUBY1_8)
public RubyArray push_m(IRubyObject[] items) {
for (int i = 0; i < items.length; i++) {
append(items[i]);
}
return this;
}
@JRubyMethod(name = "push", rest = true, compat = RUBY1_9)
public RubyArray push_m19(IRubyObject[] items) {
modifyCheck();
return push_m(items);
}
/** rb_ary_pop
*
*/
@JRubyMethod
public IRubyObject pop(ThreadContext context) {
modifyCheck();
if (realLength == 0) return context.runtime.getNil();
if (isShared) {
return safeArrayRef(values, begin + --realLength);
} else {
int index = begin + --realLength;
return safeArrayRefSet(values, index, context.runtime.getNil());
}
}
@JRubyMethod
public IRubyObject pop(ThreadContext context, IRubyObject num) {
modifyCheck();
RubyArray result = makeSharedFirst(context, num, true, context.runtime.getArray());
realLength -= result.realLength;
return result;
}
/** rb_ary_shift
*
*/
@JRubyMethod(name = "shift")
public IRubyObject shift(ThreadContext context) {
modifyCheck();
Ruby runtime = context.runtime;
if (realLength == 0) return runtime.getNil();
final IRubyObject obj = safeArrayRefCondSet(values, begin, !isShared, runtime.getNil());
begin++;
realLength--;
return obj;
}
@JRubyMethod(name = "shift")
public IRubyObject shift(ThreadContext context, IRubyObject num) {
modify();
RubyArray result = makeSharedFirst(context, num, false, context.runtime.getArray());
int n = result.realLength;
begin += n;
realLength -= n;
return result;
}
@JRubyMethod(name = "unshift", compat = RUBY1_8)
public IRubyObject unshift() {
return this;
}
@JRubyMethod(name = "unshift", compat = RUBY1_9)
public IRubyObject unshift19() {
modifyCheck();
return this;
}
/** rb_ary_unshift
*
*/
@JRubyMethod(name = "unshift", compat = RUBY1_8)
public IRubyObject unshift(IRubyObject item) {
if (begin == 0 || isShared) {
modify();
final int valuesLength = values.length - begin;
if (realLength == valuesLength) {
int newLength = valuesLength >> 1;
if (newLength < ARRAY_DEFAULT_SIZE) newLength = ARRAY_DEFAULT_SIZE;
newLength += valuesLength;
IRubyObject[]vals = new IRubyObject[newLength];
safeArrayCopy(values, begin, vals, 1, valuesLength);
Helpers.fillNil(vals, valuesLength + 1, newLength, getRuntime());
values = vals;
begin = 0;
} else {
safeArrayCopy(values, begin, values, begin + 1, realLength);
}
} else {
modifyCheck();
begin--;
}
realLength++;
values[begin] = item;
return this;
}
@JRubyMethod(name = "unshift", compat = RUBY1_9)
public IRubyObject unshift19(IRubyObject item) {
modifyCheck();
return unshift(item);
}
@JRubyMethod(name = "unshift", rest = true, compat = RUBY1_8)
public IRubyObject unshift(IRubyObject[] items) {
long len = realLength;
if (items.length == 0) return this;
store(len + items.length - 1, getRuntime().getNil());
try {
System.arraycopy(values, begin, values, begin + items.length, (int) len);
System.arraycopy(items, 0, values, begin, items.length);
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
return this;
}
@JRubyMethod(name = "unshift", rest = true, compat = RUBY1_9)
public IRubyObject unshift19(IRubyObject[] items) {
modifyCheck();
return unshift(items);
}
/** rb_ary_includes
*
*/
@JRubyMethod(name = "include?", required = 1)
public RubyBoolean include_p(ThreadContext context, IRubyObject item) {
return context.runtime.newBoolean(includes(context, item));
}
/** rb_ary_frozen_p
*
*/
@JRubyMethod(name = "frozen?")
@Override
public RubyBoolean frozen_p(ThreadContext context) {
return context.runtime.newBoolean(isFrozen() || (flags & TMPLOCK_ARR_F) != 0);
}
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject aref(IRubyObject[] args) {
switch (args.length) {
case 1:
return aref(args[0]);
case 2:
return aref(args[0], args[1]);
default:
Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
return null; // not reached
}
}
/** rb_ary_aref
*/
@JRubyMethod(name = {"[]", "slice"}, compat = RUBY1_8)
public IRubyObject aref(IRubyObject arg0) {
assert !arg0.getRuntime().is1_9();
if (arg0 instanceof RubyFixnum) return entry(((RubyFixnum)arg0).getLongValue());
if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
return arefCommon(arg0);
}
@JRubyMethod(name = {"[]", "slice"}, compat = RUBY1_9)
public IRubyObject aref19(IRubyObject arg0) {
return arg0 instanceof RubyFixnum ? entry(((RubyFixnum)arg0).getLongValue()) : arefCommon(arg0);
}
private IRubyObject arefCommon(IRubyObject arg0) {
if (arg0 instanceof RubyRange) {
long[] beglen = ((RubyRange) arg0).begLen(realLength, 0);
return beglen == null ? getRuntime().getNil() : subseq(beglen[0], beglen[1]);
}
return entry(RubyNumeric.num2long(arg0));
}
@JRubyMethod(name = {"[]", "slice"}, compat = RUBY1_8)
public IRubyObject aref(IRubyObject arg0, IRubyObject arg1) {
assert !arg0.getRuntime().is1_9();
if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
return arefCommon(arg0, arg1);
}
@JRubyMethod(name = {"[]", "slice"}, compat = RUBY1_9)
public IRubyObject aref19(IRubyObject arg0, IRubyObject arg1) {
return arefCommon(arg0, arg1);
}
private IRubyObject arefCommon(IRubyObject arg0, IRubyObject arg1) {
long beg = RubyNumeric.num2long(arg0);
if (beg < 0) beg += realLength;
return subseq(beg, RubyNumeric.num2long(arg1));
}
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject aset(IRubyObject[] args) {
switch (args.length) {
case 2:
return aset(args[0], args[1]);
case 3:
return aset(args[0], args[1], args[2]);
default:
throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 2)");
}
}
@JRubyMethod(name = "[]=", compat = RUBY1_8)
public IRubyObject aset(IRubyObject arg0, IRubyObject arg1) {
assert !getRuntime().is1_9();
if (arg0 instanceof RubyFixnum) {
store(((RubyFixnum)arg0).getLongValue(), arg1);
} else if (arg0 instanceof RubyRange) {
RubyRange range = (RubyRange)arg0;
long beg = range.begLen0(realLength);
splice(beg, range.begLen1(realLength, beg), arg1, false);
} else if(arg0 instanceof RubySymbol) {
throw getRuntime().newTypeError("Symbol as array index");
} else {
store(RubyNumeric.num2long(arg0), arg1);
}
return arg1;
}
@JRubyMethod(name = "[]=", compat = RUBY1_9)
public IRubyObject aset19(IRubyObject arg0, IRubyObject arg1) {
modifyCheck();
if (arg0 instanceof RubyFixnum) {
store(((RubyFixnum)arg0).getLongValue(), arg1);
} else if (arg0 instanceof RubyRange) {
RubyRange range = (RubyRange)arg0;
long beg = range.begLen0(realLength);
splice(beg, range.begLen1(realLength, beg), arg1, true);
} else {
store(RubyNumeric.num2long(arg0), arg1);
}
return arg1;
}
/** rb_ary_aset
*
*/
@JRubyMethod(name = "[]=", compat = RUBY1_8)
public IRubyObject aset(IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
assert !getRuntime().is1_9();
if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
if (arg1 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as subarray length");
splice(RubyNumeric.num2long(arg0), RubyNumeric.num2long(arg1), arg2, false);
return arg2;
}
@JRubyMethod(name = "[]=", compat = RUBY1_9)
public IRubyObject aset19(IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
modifyCheck();
splice(RubyNumeric.num2long(arg0), RubyNumeric.num2long(arg1), arg2, true);
return arg2;
}
/** rb_ary_at
*
*/
@JRubyMethod(name = "at", required = 1)
public IRubyObject at(IRubyObject pos) {
return entry(RubyNumeric.num2long(pos));
}
/** rb_ary_concat
*
*/
@JRubyMethod(name = "concat", required = 1, compat = RUBY1_8)
public RubyArray concat(IRubyObject obj) {
RubyArray ary = obj.convertToArray();
if (ary.realLength > 0) splice(realLength, 0, ary, false);
return this;
}
@JRubyMethod(name = "concat", required = 1, compat = RUBY1_9)
public RubyArray concat19(IRubyObject obj) {
modifyCheck();
return concat(obj);
}
/** inspect_ary
*
*/
private IRubyObject inspectAry(ThreadContext context) {
Encoding encoding = context.runtime.getDefaultInternalEncoding();
if (encoding == null) encoding = USASCIIEncoding.INSTANCE;
RubyString str = RubyString.newStringLight(context.runtime, DEFAULT_INSPECT_STR_SIZE, encoding);
str.cat((byte)'[');
boolean tainted = isTaint();
boolean untrust = isUntrusted();
boolean is19 = context.runtime.is1_9();
for (int i = 0; i < realLength; i++) {
if (i > 0) str.cat((byte)',').cat((byte)' ');
RubyString str2 = inspect(context, safeArrayRef(values, begin + i));
if (i == 0 && str2.getEncoding() != encoding) str.setEncoding(str2.getEncoding());
if (str2.isTaint()) tainted = true;
if (str2.isUntrusted()) untrust = true;
if (is19) {
str.cat19(str2);
} else {
str.cat(str2);
}
}
str.cat((byte)']');
if (tainted) str.setTaint(true);
if (untrust) str.setUntrusted(true);
return str;
}
/** rb_ary_inspect
*
*/
@JRubyMethod(name = "inspect")
@Override
public IRubyObject inspect() {
if (realLength == 0) return RubyString.newStringShared(getRuntime(), EMPTY_ARRAY_BYTELIST);
if (getRuntime().isInspecting(this)) return RubyString.newStringShared(getRuntime(), RECURSIVE_ARRAY_BYTELIST);
try {
getRuntime().registerInspecting(this);
return inspectAry(getRuntime().getCurrentContext());
} finally {
getRuntime().unregisterInspecting(this);
}
}
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject first(IRubyObject[] args) {
switch (args.length) {
case 0:
return first();
case 1:
return first(args[0]);
default:
Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
return null; // not reached
}
}
/** rb_ary_first
*
*/
@JRubyMethod(name = "first")
public IRubyObject first() {
if (realLength == 0) return getRuntime().getNil();
return values[begin];
}
/** rb_ary_first
*
*/
@JRubyMethod(name = "first")
public IRubyObject first(IRubyObject arg0) {
long n = RubyNumeric.num2long(arg0);
if (n > realLength) {
n = realLength;
} else if (n < 0) {
throw getRuntime().newArgumentError("negative array size (or size too big)");
}
return makeShared(begin, (int) n, getRuntime().getArray());
}
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject last(IRubyObject[] args) {
switch (args.length) {
case 0:
return last();
case 1:
return last(args[0]);
default:
Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
return null; // not reached
}
}
/** rb_ary_last
*
*/
@JRubyMethod(name = "last")
public IRubyObject last() {
if (realLength == 0) return getRuntime().getNil();
return values[begin + realLength - 1];
}
/** rb_ary_last
*
*/
@JRubyMethod(name = "last")
public IRubyObject last(IRubyObject arg0) {
long n = RubyNumeric.num2long(arg0);
if (n > realLength) {
n = realLength;
} else if (n < 0) {
throw getRuntime().newArgumentError("negative array size (or size too big)");
}
return makeShared(begin + realLength - (int) n, (int) n, getRuntime().getArray());
}
/** rb_ary_each
*
*/
public IRubyObject eachCommon(ThreadContext context, Block block) {
if (!block.isGiven()) {
throw context.runtime.newLocalJumpErrorNoBlock();
}
for (int i = 0; i < realLength; i++) {
// do not coarsen the "safe" catch, since it will misinterpret AIOOBE from the yielded code.
// See JRUBY-5434
block.yield(context, safeArrayRef(values, begin + i));
}
return this;
}
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
return block.isGiven() ? eachCommon(context, block) : enumeratorize(context.runtime, this, "each");
}
public IRubyObject eachSlice(ThreadContext context, int size, Block block) {
Ruby runtime = context.runtime;
// local copies of everything
int localRealLength = realLength;
IRubyObject[] localValues = values;
int localBegin = begin;
// sliding window
RubyArray window = newArrayNoCopy(runtime, localValues, localBegin, size);
makeShared();
// don't expose shared array to ruby
final boolean specificArity = (block.arity().isFixed()) && (block.arity().required() != 1);
for (; localRealLength >= size; localRealLength -= size) {
block.yield(context, window);
if (specificArity) { // array is never exposed to ruby, just use for yielding
window.begin = localBegin += size;
} else { // array may be exposed to ruby, create new
window = newArrayNoCopy(runtime, localValues, localBegin += size, size);
}
}
// remainder
if (localRealLength > 0) {
window.realLength = localRealLength;
block.yield(context, window);
}
return runtime.getNil();
}
@JRubyMethod
public IRubyObject each_slice(ThreadContext context, IRubyObject arg, Block block) {
final int size = RubyNumeric.num2int(arg);
final Ruby runtime = context.runtime;
if (size <= 0) throw runtime.newArgumentError("invalid slice size");
return block.isGiven() ? eachSlice(context, size, block) : enumeratorize(context.runtime, this, "each_slice", arg);
}
/** rb_ary_each_index
*
*/
public IRubyObject eachIndex(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) {
throw runtime.newLocalJumpErrorNoBlock();
}
for (int i = 0; i < realLength; i++) {
block.yield(context, runtime.newFixnum(i));
}
return this;
}
@JRubyMethod
public IRubyObject each_index(ThreadContext context, Block block) {
return block.isGiven() ? eachIndex(context, block) : enumeratorize(context.runtime, this, "each_index");
}
/** rb_ary_reverse_each
*
*/
public IRubyObject reverseEach(ThreadContext context, Block block) {
int len = realLength;
while(len-- > 0) {
// do not coarsen the "safe" catch, since it will misinterpret AIOOBE from the yielded code.
// See JRUBY-5434
block.yield(context, safeArrayRef(values, begin + len));
if (realLength < len) len = realLength;
}
return this;
}
@JRubyMethod
public IRubyObject reverse_each(ThreadContext context, Block block) {
return block.isGiven() ? reverseEach(context, block) : enumeratorize(context.runtime, this, "reverse_each");
}
private IRubyObject inspectJoin(ThreadContext context, RubyArray tmp, IRubyObject sep) {
Ruby runtime = context.runtime;
// If already inspecting, there is no need to register/unregister again.
if (runtime.isInspecting(this)) {
return tmp.join(context, sep);
}
try {
runtime.registerInspecting(this);
return tmp.join(context, sep);
} finally {
runtime.unregisterInspecting(this);
}
}
/** rb_ary_join
*
*/
@JRubyMethod(name = "join", compat = RUBY1_8)
public IRubyObject join(ThreadContext context, IRubyObject sep) {
final Ruby runtime = context.runtime;
if (realLength == 0) return RubyString.newEmptyString(runtime);
if (sep.isNil()) sep = runtime.getGlobalVariables().get("$,");
boolean sepTainted = sep.isTaint();
boolean taint = isTaint();
boolean untrusted = isUntrusted() || sep.isUntrusted();
int len = 1;
for (int i = begin; i < begin + realLength; i++) {
// do not coarsen the "safe" catch, since it will misinterpret AIOOBE from to_str.
// See JRUBY-5434
IRubyObject value = safeArrayRef(values, i);
IRubyObject tmp = value.checkStringType();
len += tmp.isNil() ? 10 : ((RubyString) tmp).getByteList().length();
}
ByteList sepBytes = null;
if (!sep.isNil()) {
sepBytes = sep.convertToString().getByteList();
len += sepBytes.getRealSize() * (realLength - 1);
}
boolean appended = false;
ByteList buf = new ByteList(len);
for (int i = 0; i < realLength; i++) {
// do not coarsen the "safe" catch, since it will misinterpret AIOOBE from inspect.
// See JRUBY-5434
IRubyObject tmp = safeArrayRef(values, begin + i);
if (!(tmp instanceof RubyString)) {
if (tmp instanceof RubyArray) {
if (tmp == this || runtime.isInspecting(tmp)) {
tmp = runtime.newString("[...]");
} else {
tmp = inspectJoin(context, (RubyArray)tmp, sep);
}
} else {
tmp = RubyString.objAsString(context, tmp);
}
}
if (i > 0 && sepBytes != null) {
buf.append(sepBytes);
appended = true;
}
buf.append(tmp.asString().getByteList());
if (tmp.isTaint()) taint |= true;
if (appended) taint |= sepTainted;
if (tmp.isUntrusted()) untrusted = true;
}
RubyString result = runtime.newString(buf);
if (taint) result.setTaint(true);
if (untrusted) result.untrust(context);
return result;
}
@JRubyMethod(name = "join", compat = RUBY1_8)
public IRubyObject join(ThreadContext context) {
return join(context, context.runtime.getGlobalVariables().get("$,"));
}
// 1.9 MRI: ary_join_0
private RubyString joinStrings(RubyString sep, int max, RubyString result) {
IRubyObject first = values[begin];
if (max - begin > 0 && first instanceof EncodingCapable) {
result.setEncoding(((EncodingCapable)first).getEncoding());
}
try {
for(int i = begin; i < max; i++) {
if (i > begin && sep != null) result.append19(sep);
result.append19(values[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
return result;
}
// 1.9 MRI: ary_join_1
private RubyString joinAny(ThreadContext context, IRubyObject obj, RubyString sep,
int i, RubyString result) {
assert i >= begin : "joining elements before beginning of array";
RubyClass arrayClass = context.runtime.getArray();
for (; i < begin + realLength; i++) {
if (i > begin && sep != null) result.append19(sep);
IRubyObject val = safeArrayRef(values, i);
if (val instanceof RubyString) {
result.append19(val);
} else if (val instanceof RubyArray) {
obj = val;
recursiveJoin(context, obj, sep, result, val);
} else {
IRubyObject tmp = val.checkStringType19();
if (!tmp.isNil()) {
result.append19(tmp);
continue;
}
tmp = TypeConverter.convertToTypeWithCheck(val, arrayClass, "to_ary");
if (!tmp.isNil()) {
obj = val;
recursiveJoin(context, obj, sep, result, tmp);
} else {
result.append19(RubyString.objAsString(context, val));
}
}
}
return result;
}
private void recursiveJoin(final ThreadContext context, final IRubyObject outValue,
final RubyString sep, final RubyString result, final IRubyObject ary) {
final Ruby runtime = context.runtime;
if (ary == this) throw runtime.newArgumentError("recursive array join");
runtime.execRecursive(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
if (recur) throw runtime.newArgumentError("recursive array join");
RubyArray recAry = ((RubyArray) ary);
recAry.joinAny(context, outValue, sep, recAry.begin, result);
return runtime.getNil();
}}, outValue);
}
/** rb_ary_join
*
*/
@JRubyMethod(name = "join", compat = RUBY1_9)
public IRubyObject join19(final ThreadContext context, IRubyObject sep) {
final Ruby runtime = context.runtime;
if (realLength == 0) return RubyString.newEmptyString(runtime, USASCIIEncoding.INSTANCE);
if (sep.isNil()) sep = runtime.getGlobalVariables().get("$,");
int len = 1;
RubyString sepString = null;
if (!sep.isNil()) {
sepString = sep.convertToString();
len += sepString.size() * (realLength - 1);
}
for (int i = begin; i < begin + realLength; i++) {
IRubyObject val = safeArrayRef(values, i);
IRubyObject tmp = val.checkStringType19();
if (tmp.isNil() || tmp != val) {
len += ((begin + realLength) - i) * 10;
final RubyString result = (RubyString) RubyString.newStringLight(runtime, len, USASCIIEncoding.INSTANCE).infectBy(this);
final RubyString sepStringFinal = sepString;
final int iFinal = i;
return runtime.recursiveListOperation(new Callable() {
public IRubyObject call() {
return joinAny(context, RubyArray.this, sepStringFinal, iFinal, joinStrings(sepStringFinal, iFinal, result));
}
});
}
len += ((RubyString) tmp).getByteList().length();
}
return joinStrings(sepString, begin + realLength,
(RubyString) RubyString.newStringLight(runtime, len).infectBy(this));
}
@JRubyMethod(name = "join", compat = RUBY1_9)
public IRubyObject join19(ThreadContext context) {
return join19(context, context.runtime.getGlobalVariables().get("$,"));
}
/** rb_ary_to_a
*
*/
@JRubyMethod(name = "to_a")
@Override
public RubyArray to_a() {
if(getMetaClass() != getRuntime().getArray()) {
RubyArray dup = new RubyArray(getRuntime(), getRuntime().isObjectSpaceEnabled());
isShared = true;
dup.isShared = true;
dup.values = values;
dup.realLength = realLength;
dup.begin = begin;
return dup;
}
return this;
}
@JRubyMethod(name = "to_ary")
public IRubyObject to_ary() {
return this;
}
@Override
public RubyArray convertToArray() {
return this;
}
@Override
public IRubyObject checkArrayType(){
return this;
}
/** rb_ary_equal
*
*/
@JRubyMethod(name = "==", required = 1)
@Override
public IRubyObject op_equal(ThreadContext context, IRubyObject obj) {
Ruby runtime = context.runtime;
if (this == obj) {
return runtime.getTrue();
}
if (!(obj instanceof RubyArray)) {
if (obj == context.nil) return runtime.getFalse();
if (!obj.respondsTo("to_ary")) {
return runtime.getFalse();
}
return Helpers.rbEqual(context, obj, this);
}
return RecursiveComparator.compare(context, MethodNames.OP_EQUAL, this, obj);
}
public RubyBoolean compare(ThreadContext context, MethodNames method, IRubyObject other) {
if (!(other instanceof RubyArray)) {
if (!other.respondsTo("to_ary")) return context.runtime.getFalse();
return Helpers.rbEqual(context, other, this);
}
RubyArray ary = (RubyArray) other;
if (realLength != ary.realLength) return context.runtime.getFalse();
for (int i = 0; i < realLength; i++) {
IRubyObject a = elt(i);
IRubyObject b = ary.elt(i);
if (a == b) continue; // matching MRI opt. mock frameworks can throw errors if we don't
if (!invokedynamic(context, a, method, b).isTrue()) return context.runtime.getFalse();
}
return context.runtime.getTrue();
}
/** rb_ary_eql
*
*/
@JRubyMethod(name = "eql?", required = 1)
public IRubyObject eql(ThreadContext context, IRubyObject obj) {
if(!(obj instanceof RubyArray)) {
return context.runtime.getFalse();
}
return RecursiveComparator.compare(context, MethodNames.EQL, this, obj);
}
/** rb_ary_compact_bang
*
*/
@JRubyMethod(name = "compact!")
public IRubyObject compact_bang() {
modify();
int p = begin;
int t = p;
int end = p + realLength;
try {
while (t < end) {
if (values[t].isNil()) {
t++;
} else {
values[p++] = values[t++];
}
}
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
p -= begin;
if (realLength == p) return getRuntime().getNil();
realloc(p, values.length - begin);
realLength = p;
return this;
}
/** rb_ary_compact
*
*/
@JRubyMethod(name = "compact", compat = RUBY1_8)
public IRubyObject compact() {
RubyArray ary = aryDup();
ary.compact_bang();
return ary;
}
@JRubyMethod(name = "compact", compat = RUBY1_9)
public IRubyObject compatc19() {
RubyArray ary = aryDup19();
ary.compact_bang();
return ary;
}
/** rb_ary_empty_p
*
*/
@JRubyMethod(name = "empty?")
public IRubyObject empty_p() {
return realLength == 0 ? getRuntime().getTrue() : getRuntime().getFalse();
}
/** rb_ary_clear
*
*/
@JRubyMethod(name = "clear")
public IRubyObject rb_clear() {
modifyCheck();
if (isShared) {
alloc(ARRAY_DEFAULT_SIZE);
isShared = false;
} else if (values.length > ARRAY_DEFAULT_SIZE << 1) {
alloc(ARRAY_DEFAULT_SIZE << 1);
} else {
try {
begin = 0;
Helpers.fillNil(values, 0, realLength, getRuntime());
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
}
realLength = 0;
return this;
}
@JRubyMethod
public IRubyObject fill(ThreadContext context, Block block) {
if (block.isGiven()) return fillCommon(context, 0, realLength, block);
throw context.runtime.newArgumentError(0, 1);
}
@JRubyMethod
public IRubyObject fill(ThreadContext context, IRubyObject arg, Block block) {
if (block.isGiven()) {
if (arg instanceof RubyRange) {
int[] beglen = ((RubyRange) arg).begLenInt(realLength, 1);
return fillCommon(context, beglen[0], beglen[1], block);
}
int beg;
return fillCommon(context, beg = fillBegin(arg), fillLen(beg, null), block);
} else {
return fillCommon(context, 0, realLength, arg);
}
}
@JRubyMethod
public IRubyObject fill(ThreadContext context, IRubyObject arg1, IRubyObject arg2, Block block) {
if (block.isGiven()) {
int beg;
return fillCommon(context, beg = fillBegin(arg1), fillLen(beg, arg2), block);
} else {
if (arg2 instanceof RubyRange) {
int[] beglen = ((RubyRange) arg2).begLenInt(realLength, 1);
return fillCommon(context, beglen[0], beglen[1], arg1);
}
int beg;
return fillCommon(context, beg = fillBegin(arg2), fillLen(beg, null), arg1);
}
}
@JRubyMethod
public IRubyObject fill(ThreadContext context, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) {
if (block.isGiven()) {
throw context.runtime.newArgumentError(3, 2);
} else {
int beg;
return fillCommon(context, beg = fillBegin(arg2), fillLen(beg, arg3), arg1);
}
}
private int fillBegin(IRubyObject arg) {
int beg = arg.isNil() ? 0 : RubyNumeric.num2int(arg);
if (beg < 0) {
beg = realLength + beg;
if (beg < 0) beg = 0;
}
return beg;
}
private long fillLen(long beg, IRubyObject arg) {
if (arg == null || arg.isNil()) {
return realLength - beg;
} else {
return RubyNumeric.num2long(arg);
}
// TODO: In MRI 1.9, an explicit check for negative length is
// added here. IndexError is raised when length is negative.
// See [ruby-core:12953] for more details.
//
// New note: This is actually under re-evaluation,
// see [ruby-core:17483].
}
private IRubyObject fillCommon(ThreadContext context, int beg, long len, IRubyObject item) {
modify();
// See [ruby-core:17483]
if (len < 0) return this;
if (len > Integer.MAX_VALUE - beg) throw context.runtime.newArgumentError("argument too big");
int end = (int)(beg + len);
if (end > realLength) {
int valuesLength = values.length - begin;
if (end >= valuesLength) realloc(end, valuesLength);
realLength = end;
}
if (len > 0) {
try {
fill(values, begin + beg, begin + end, item);
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
}
return this;
}
private IRubyObject fillCommon(ThreadContext context, int beg, long len, Block block) {
modify();
// See [ruby-core:17483]
if (len < 0) return this;
if (len > Integer.MAX_VALUE - beg) throw getRuntime().newArgumentError("argument too big");
int end = (int)(beg + len);
if (end > realLength) {
int valuesLength = values.length - begin;
if (end >= valuesLength) realloc(end, valuesLength);
realLength = end;
}
Ruby runtime = context.runtime;
for (int i = beg; i < end; i++) {
IRubyObject v = block.yield(context, runtime.newFixnum(i));
if (i >= realLength) break;
safeArraySet(values, begin + i, v);
}
return this;
}
/** rb_ary_index
*
*/
public IRubyObject index(ThreadContext context, IRubyObject obj) {
Ruby runtime = context.runtime;
for (int i = 0; i < realLength; i++) {
if (equalInternal(context, eltOk(i), obj)) return runtime.newFixnum(i);
}
return runtime.getNil();
}
@JRubyMethod(name = {"index", "find_index"})
public IRubyObject index(ThreadContext context, IRubyObject obj, Block unused) {
if (unused.isGiven()) context.runtime.getWarnings().warn(ID.BLOCK_UNUSED, "given block not used");
return index(context, obj);
}
@JRubyMethod(name = {"index", "find_index"})
public IRubyObject index(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) return enumeratorize(runtime, this, "index");
for (int i = 0; i < realLength; i++) {
if (block.yield(context, eltOk(i)).isTrue()) return runtime.newFixnum(i);
}
return runtime.getNil();
}
@JRubyMethod(compat = RUBY2_0)
public IRubyObject bsearch(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) {
return enumeratorize(context.runtime, this, "bsearch");
}
int low = 0, high = realLength, mid;
boolean smaller = false, satisfied = false;
IRubyObject v, val;
while (low < high) {
mid = low + ((high - low) / 2);
val = eltOk(mid);
v = block.yieldSpecific(context, val);
if (v instanceof RubyFixnum) {
long fixValue = ((RubyFixnum)v).getLongValue();
if (fixValue == 0) return val;
smaller = fixValue < 0;
} else if (v == runtime.getTrue()) {
satisfied = true;
smaller = true;
} else if (v == runtime.getFalse() || v == runtime.getNil()) {
smaller = false;
} else if (runtime.getNumeric().isInstance(v)) {
switch (RubyComparable.cmpint(context, invokedynamic(context, v, OP_CMP, RubyFixnum.zero(runtime)), v, RubyFixnum.zero(runtime))) {
case 0: return val;
case 1: smaller = true; break;
case -1: smaller = false;
}
} else {
throw runtime.newTypeError("wrong argument type " + v.getType().getName() + " (must be numeric, true, false or nil");
}
if (smaller) {
high = mid;
} else {
low = mid + 1;
}
}
if (low == realLength) return context.nil;
if (!satisfied) return context.nil;
return eltOk(low);
}
/** rb_ary_rindex
*
*/
public IRubyObject rindex(ThreadContext context, IRubyObject obj) {
Ruby runtime = context.runtime;
int i = realLength;
while (i-- > 0) {
if (i > realLength) {
i = realLength;
continue;
}
if (equalInternal(context, eltOk(i), obj)) return runtime.newFixnum(i);
}
return runtime.getNil();
}
@JRubyMethod
public IRubyObject rindex(ThreadContext context, IRubyObject obj, Block unused) {
if (unused.isGiven()) context.runtime.getWarnings().warn(ID.BLOCK_UNUSED, "given block not used");
return rindex(context, obj);
}
@JRubyMethod
public IRubyObject rindex(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) return enumeratorize(runtime, this, "rindex");
int i = realLength;
while (i-- > 0) {
if (i >= realLength) {
i = realLength;
continue;
}
if (block.yield(context, eltOk(i)).isTrue()) return runtime.newFixnum(i);
}
return runtime.getNil();
}
/** rb_ary_indexes
*
*/
@JRubyMethod(name = {"indexes", "indices"}, required = 1, rest = true)
public IRubyObject indexes(IRubyObject[] args) {
getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "Array#indexes is deprecated; use Array#values_at");
RubyArray ary = new RubyArray(getRuntime(), args.length);
for (int i = 0; i < args.length; i++) {
ary.append(aref(args[i]));
}
return ary;
}
/** rb_ary_reverse_bang
*
*/
@JRubyMethod(name = "reverse!")
public IRubyObject reverse_bang() {
modify();
try {
if (realLength > 1) {
IRubyObject[] vals = values;
int p = begin;
int len = realLength;
for (int i = 0; i < len >> 1; i++) {
IRubyObject tmp = vals[p + i];
vals[p + i] = vals[p + len - i - 1];
vals[p + len - i - 1] = tmp;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
return this;
}
/** rb_ary_reverse_m
*
*/
@JRubyMethod(name = "reverse")
public IRubyObject reverse() {
if (realLength > 1) {
RubyArray dup = safeReverse();
dup.flags |= flags & TAINTED_F; // from DUP_SETUP
dup.flags |= flags & UNTRUSTED_F; // from DUP_SETUP
// rb_copy_generic_ivar from DUP_SETUP here ...unlikely..
return dup;
} else {
return dup();
}
}
private RubyArray safeReverse() {
int length = realLength;
int myBegin = this.begin;
IRubyObject[] myValues = this.values;
IRubyObject[] vals = new IRubyObject[length];
try {
for (int i = 0; i <= length >> 1; i++) {
vals[i] = myValues[myBegin + length - i - 1];
vals[length - i - 1] = myValues[myBegin + i];
}
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
return new RubyArray(getRuntime(), getMetaClass(), vals);
}
/** rb_ary_collect
*
*/
@JRubyMethod(name = {"collect", "map"}, compat = RUBY1_8)
public IRubyObject collect(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) return new RubyArray(runtime, runtime.getArray(), this);
IRubyObject[] arr = new IRubyObject[realLength];
int i;
for (i = 0; i < realLength; i++) {
// Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield
// See JRUBY-5434
arr[i] = block.yield(context, safeArrayRef(values, i + begin));
}
// use iteration count as new size in case something was deleted along the way
return new RubyArray(runtime, arr, 0, i);
}
@JRubyMethod(name = {"collect"}, compat = RUBY1_9)
public IRubyObject collect19(ThreadContext context, Block block) {
return block.isGiven() ? collect(context, block) : enumeratorize(context.runtime, this, "collect");
}
@JRubyMethod(name = {"map"}, compat = RUBY1_9)
public IRubyObject map19(ThreadContext context, Block block) {
return block.isGiven() ? collect(context, block) : enumeratorize(context.runtime, this, "map");
}
/** rb_ary_collect_bang
*
*/
public RubyArray collectBang(ThreadContext context, Block block) {
if (!block.isGiven()) throw context.runtime.newLocalJumpErrorNoBlock();
modify();
for (int i = 0, len = realLength; i < len; i++) {
// Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield
// See JRUBY-5434
store(i, block.yield(context, safeArrayRef(values, begin + i)));
}
return this;
}
/** rb_ary_collect_bang
*
*/
@JRubyMethod(name = "collect!")
public IRubyObject collect_bang(ThreadContext context, Block block) {
return block.isGiven() ? collectBang(context, block) : enumeratorize(context.runtime, this, "collect!");
}
/** rb_ary_collect_bang
*
*/
@JRubyMethod(name = "map!")
public IRubyObject map_bang(ThreadContext context, Block block) {
return block.isGiven() ? collectBang(context, block) : enumeratorize(context.runtime, this, "map!");
}
/** rb_ary_select
*
*/
public IRubyObject selectCommon(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
RubyArray result = new RubyArray(runtime, realLength);
for (int i = 0; i < realLength; i++) {
// Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield
// See JRUBY-5434
IRubyObject value = safeArrayRef(values, begin + i);
if (block.yield(context, value).isTrue()) result.append(value);
}
Helpers.fillNil(result.values, result.realLength, result.values.length, runtime);
return result;
}
@JRubyMethod
public IRubyObject select(ThreadContext context, Block block) {
return block.isGiven() ? selectCommon(context, block) : enumeratorize(context.runtime, this, "select");
}
@JRubyMethod(name = "select!", compat = RUBY1_9)
public IRubyObject select_bang(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) return enumeratorize(runtime, this, "select!");
modify();
int newLength = 0;
IRubyObject[] aux = new IRubyObject[values.length];
for (int oldIndex = 0; oldIndex < realLength; oldIndex++) {
// Do not coarsen the "safe" check, since it will misinterpret
// AIOOBE from the yield (see JRUBY-5434)
IRubyObject value = safeArrayRef(values, begin + oldIndex);
if (!block.yield(context, value).isTrue()) continue;
aux[begin + newLength++] = value;
}
if (realLength == newLength) return runtime.getNil(); // No change
safeArrayCopy(aux, begin, values, begin, newLength);
realLength = newLength;
return this;
}
@JRubyMethod(compat = RUBY1_9)
public IRubyObject keep_if(ThreadContext context, Block block) {
if (!block.isGiven()) {
return enumeratorize(context.runtime, this, "keep_if");
}
select_bang(context, block);
return this;
}
/** rb_ary_delete
*
*/
@JRubyMethod(required = 1)
public IRubyObject delete(ThreadContext context, IRubyObject item, Block block) {
int i2 = 0;
IRubyObject value = item;
Ruby runtime = context.runtime;
for (int i1 = 0; i1 < realLength; i1++) {
// Do not coarsen the "safe" check, since it will misinterpret AIOOBE from equalInternal
// See JRUBY-5434
IRubyObject e = safeArrayRef(values, begin + i1);
if (equalInternal(context, e, item)) {
value = e;
continue;
}
if (i1 != i2) store(i2, e);
i2++;
}
if (realLength == i2) {
if (block.isGiven()) return block.yield(context, item);
return runtime.getNil();
}
modify();
final int myRealLength = this.realLength;
final int myBegin = this.begin;
final IRubyObject[] myValues = this.values;
try {
if (myRealLength > i2) {
Helpers.fillNil(myValues, myBegin + i2, myBegin + myRealLength, context.runtime);
this.realLength = i2;
int valuesLength = myValues.length - myBegin;
if (i2 << 1 < valuesLength && valuesLength > ARRAY_DEFAULT_SIZE) realloc(i2 << 1, valuesLength);
}
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
return context.is19 ? value : item;
}
/** rb_ary_delete_at
*
*/
public final IRubyObject delete_at(int pos) {
int len = realLength;
if (pos >= len || (pos < 0 && (pos += len) < 0)) return getRuntime().getNil();
modify();
IRubyObject nil = getRuntime().getNil();
IRubyObject obj = null; // should never return null below
try {
obj = values[begin + pos];
// fast paths for head and tail
if (pos == 0) {
values[begin] = nil;
begin++;
realLength--;
return obj;
} else if (pos == realLength - 1) {
values[begin + realLength - 1] = nil;
realLength--;
return obj;
}
System.arraycopy(values, begin + pos + 1, values, begin + pos, len - (pos + 1));
values[begin + len - 1] = getRuntime().getNil();
} catch (ArrayIndexOutOfBoundsException e) {
concurrentModification();
}
realLength--;
return obj;
}
/** rb_ary_delete_at_m
*
*/
@JRubyMethod(name = "delete_at", required = 1)
public IRubyObject delete_at(IRubyObject obj) {
return delete_at((int) RubyNumeric.num2long(obj));
}
/** rb_ary_reject_bang
*
*/
public IRubyObject rejectCommon(ThreadContext context, Block block) {
RubyArray ary = aryDup();
ary.reject_bang(context, block);
return ary;
}
@JRubyMethod
public IRubyObject reject(ThreadContext context, Block block) {
return block.isGiven() ? rejectCommon(context, block) : enumeratorize(context.runtime, this, "reject");
}
/** rb_ary_reject_bang
*
*/
public IRubyObject rejectBang(ThreadContext context, Block block) {
if (!block.isGiven()) throw context.runtime.newLocalJumpErrorNoBlock();
IRubyObject result = context.runtime.getNil();
modify();
for (int i = 0; i < realLength; /* we adjust i in the loop */) {
// Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield
// See JRUBY-5434
IRubyObject v = safeArrayRef(values, begin + i);
if (block.yield(context, v).isTrue()) {
delete_at(i);
result = this;
} else {
i++;
}
}
return result;
}
@JRubyMethod(name = "reject!")
public IRubyObject reject_bang(ThreadContext context, Block block) {
return block.isGiven() ? rejectBang(context, block) : enumeratorize(context.runtime, this, "reject!");
}
/** rb_ary_delete_if
*
*/
public IRubyObject deleteIf(ThreadContext context, Block block) {
reject_bang(context, block);
return this;
}
@JRubyMethod
public IRubyObject delete_if(ThreadContext context, Block block) {
return block.isGiven() ? deleteIf(context, block) : enumeratorize(context.runtime, this, "delete_if");
}
/** rb_ary_zip
*
*/
@JRubyMethod(optional = 1, rest = true)
public IRubyObject zip(ThreadContext context, IRubyObject[] args, Block block) {
final Ruby runtime = context.runtime;
final int aLen = args.length + 1;
RubyClass array = runtime.getArray();
final IRubyObject[] newArgs = new IRubyObject[args.length];
boolean hasUncoercible = false;
for (int i = 0; i < args.length; i++) {
newArgs[i] = TypeConverter.convertToType(args[i], array, "to_ary", false);
if (newArgs[i].isNil()) {
hasUncoercible = true;
}
}
// Handle uncoercibles by trying to_enum conversion
if (hasUncoercible) {
RubySymbol each = runtime.newSymbol("each");
for (int i = 0; i < args.length; i++) {
newArgs[i] = args[i].callMethod(context, "to_enum", each);
}
}
if (hasUncoercible) {
return zipCommon(context, newArgs, block, new ArgumentVisitor() {
public IRubyObject visit(ThreadContext ctx, IRubyObject arg, int i) {
return RubyEnumerable.zipEnumNext(ctx, arg);
}
});
} else {
return zipCommon(context, newArgs, block, new ArgumentVisitor() {
public IRubyObject visit(ThreadContext ctx, IRubyObject arg, int i) {
return ((RubyArray) arg).elt(i);
}
});
}
}
// This can be shared with RubyEnumerable to clean #zipCommon{Enum,Arg} a little
public static interface ArgumentVisitor {
IRubyObject visit(ThreadContext ctx, IRubyObject arg, int i);
}
private IRubyObject zipCommon(ThreadContext context, IRubyObject[] args, Block block, ArgumentVisitor visitor) {
Ruby runtime = context.runtime;
if (block.isGiven()) {
for (int i = 0; i < realLength; i++) {
IRubyObject[] tmp = new IRubyObject[args.length + 1];
// Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield
// See JRUBY-5434
tmp[0] = safeArrayRef(values, begin + i);
for (int j = 0; j < args.length; j++) {
tmp[j + 1] = visitor.visit(context, args[j], i);
}
block.yield(context, newArrayNoCopyLight(runtime, tmp));
}
return runtime.getNil();
}
IRubyObject[] result = new IRubyObject[realLength];
try {
for (int i = 0; i < realLength; i++) {
IRubyObject[] tmp = new IRubyObject[args.length + 1];
tmp[0] = values[begin + i];
for (int j = 0; j < args.length; j++) {
tmp[j + 1] = visitor.visit(context, args[j], i);
}
result[i] = newArrayNoCopyLight(runtime, tmp);
}
} catch (ArrayIndexOutOfBoundsException aioob) {
concurrentModification();
}
return newArrayNoCopy(runtime, result);
}
/** rb_ary_cmp
*
*/
@JRubyMethod(name = "<=>", required = 1)
public IRubyObject op_cmp(ThreadContext context, IRubyObject obj) {
Ruby runtime = context.runtime;
IRubyObject ary2 = runtime.getNil();
boolean isAnArray = (obj instanceof RubyArray) || obj.getMetaClass().getSuperClass() == runtime.getArray();
if (!isAnArray && !obj.respondsTo("to_ary")) {
return ary2;
} else if (!isAnArray) {
ary2 = obj.callMethod(context, "to_ary");
} else {
ary2 = obj.convertToArray();
}
return cmpCommon(context, runtime, (RubyArray) ary2);
}
private IRubyObject cmpCommon(ThreadContext context, Ruby runtime, RubyArray ary2) {
if (this == ary2 || runtime.isInspecting(this)) return RubyFixnum.zero(runtime);
try {
runtime.registerInspecting(this);
int len = realLength;
if (len > ary2.realLength) len = ary2.realLength;
for (int i = 0; i < len; i++) {
IRubyObject v = invokedynamic(context, elt(i), OP_CMP, ary2.elt(i));
if (!(v instanceof RubyFixnum) || ((RubyFixnum) v).getLongValue() != 0) return v;
}
} finally {
runtime.unregisterInspecting(this);
}
int len = realLength - ary2.realLength;
if (len == 0) return RubyFixnum.zero(runtime);
if (len > 0) return RubyFixnum.one(runtime);
return RubyFixnum.minus_one(runtime);
}
/**
* Variable arity version for compatibility. Not bound to a Ruby method.
* @deprecated Use the versions with zero, one, or two args.
*/
public IRubyObject slice_bang(IRubyObject[] args) {
switch (args.length) {
case 1:
return slice_bang(args[0]);
case 2:
return slice_bang(args[0], args[1]);
default:
Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
return null; // not reached
}
}
private IRubyObject slice_internal(long pos, long len,
IRubyObject arg0, IRubyObject arg1, Ruby runtime) {
if(len < 0) return runtime.getNil();
int orig_len = realLength;
if(pos < 0) {
pos += orig_len;
if(pos < 0) {
return runtime.getNil();
}
} else if(orig_len < pos) {
return runtime.getNil();
}
if(orig_len < pos + len) {
len = orig_len - pos;
}
if(len == 0) {
return runtime.newEmptyArray();
}
arg1 = makeShared(begin + (int)pos, (int)len, getMetaClass());
splice(pos, len, null, false);
return arg1;
}
/** rb_ary_slice_bang
*
*/
@JRubyMethod(name = "slice!")
public IRubyObject slice_bang(IRubyObject arg0) {
modifyCheck();
Ruby runtime = getRuntime();
if (arg0 instanceof RubyRange) {
RubyRange range = (RubyRange) arg0;
if (!range.checkBegin(realLength)) {
return runtime.getNil();
}
long pos = range.begLen0(realLength);
long len = range.begLen1(realLength, pos);
return slice_internal(pos, len, arg0, null, runtime);
}
return delete_at((int) RubyNumeric.num2long(arg0));
}
/** rb_ary_slice_bang
*
*/
@JRubyMethod(name = "slice!")
public IRubyObject slice_bang(IRubyObject arg0, IRubyObject arg1) {
modifyCheck();
long pos = RubyNumeric.num2long(arg0);
long len = RubyNumeric.num2long(arg1);
return slice_internal(pos, len, arg0, arg1, getRuntime());
}
/** rb_ary_assoc
*
*/
@JRubyMethod(name = "assoc", required = 1)
public IRubyObject assoc(ThreadContext context, IRubyObject key) {
Ruby runtime = context.runtime;
for (int i = 0; i < realLength; i++) {
IRubyObject v = eltOk(i);
if (v instanceof RubyArray) {
RubyArray arr = (RubyArray)v;
if (arr.realLength > 0 && equalInternal(context, arr.elt(0), key)) return arr;
}
}
return runtime.getNil();
}
/** rb_ary_rassoc
*
*/
@JRubyMethod(name = "rassoc", required = 1)
public IRubyObject rassoc(ThreadContext context, IRubyObject value) {
Ruby runtime = context.runtime;
for (int i = 0; i < realLength; i++) {
IRubyObject v = eltOk(i);
if (v instanceof RubyArray) {
RubyArray arr = (RubyArray)v;
if (arr.realLength > 1 && equalInternal(context, arr.eltOk(1), value)) return arr;
}
}
return runtime.getNil();
}
private boolean flatten(ThreadContext context, int level, RubyArray result) {
Ruby runtime = context.runtime;
RubyArray stack = new RubyArray(runtime, ARRAY_DEFAULT_SIZE, false);
IdentityHashMap
© 2015 - 2025 Weber Informatics LLC | Privacy Policy