blob: 51199f585bbb1dc80e9e915c2630ae8a0000a84e [file] [log] [blame]
// Copyright 2023 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.starlark.java.eval;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
import javax.annotation.Nullable;
/** A mutable implementation of StarlarkList. */
final class MutableStarlarkList<E> extends StarlarkList<E> {
// The implementation strategy is similar to ArrayList,
// but without the extra indirection of using ArrayList.
// elems[0:size] holds the logical elements, and elems[size:] are not used.
// elems.getClass() == Object[].class. This is necessary to avoid ArrayStoreException.
private int size;
private int iteratorCount; // number of active iterators (unused once frozen)
Object[] elems = StarlarkList.EMPTY_ARRAY; // elems[i] == null iff i >= size
/** Final except for {@link #unsafeShallowFreeze}; must not be modified any other way. */
private Mutability mutability;
MutableStarlarkList(@Nullable Mutability mutability, Object[] elems, int size) {
Preconditions.checkArgument(elems.getClass() == Object[].class);
this.elems = elems;
this.size = size;
this.mutability = mutability == null ? Mutability.IMMUTABLE : mutability;
}
@Override
public boolean isImmutable() {
return mutability().isFrozen();
}
@Override
public boolean updateIteratorCount(int delta) {
if (mutability().isFrozen()) {
return false;
}
if (delta > 0) {
iteratorCount++;
} else if (delta < 0) {
iteratorCount--;
}
return iteratorCount > 0;
}
@Override
public Mutability mutability() {
return mutability;
}
@Override
public void unsafeShallowFreeze() {
Mutability.Freezable.checkUnsafeShallowFreezePrecondition(this);
this.mutability = Mutability.IMMUTABLE;
}
@Override
public ImmutableList<E> getImmutableList() {
// Optimization: a frozen array needn't be copied.
// If the entire array is full, we can wrap it directly.
if (elems.length == size && mutability().isFrozen()) {
return Tuple.wrapImmutable(elems);
}
return ImmutableList.copyOf(this);
}
@Override
@SuppressWarnings("unchecked")
public E get(int i) {
Preconditions.checkElementIndex(i, size);
return (E) elems[i]; // unchecked
}
@Override
public int size() {
return size;
}
@Override
public boolean contains(Object o) {
// StarlarkList contains only valid Starlark objects (which are non-null)
if (o == null) {
return false;
}
for (int i = 0; i < size; i++) {
Object elem = elems[i];
if (o.equals(elem)) {
return true;
}
}
return false;
}
// Postcondition: elems.length >= mincap.
private void grow(int mincap) {
int oldcap = elems.length;
if (oldcap < mincap) {
int newcap = oldcap + (oldcap >> 1); // grow by at least 50%
if (newcap < mincap) {
newcap = mincap;
}
elems = Arrays.copyOf(elems, newcap);
}
}
// Grow capacity enough to insert given number of elements
private void growAdditional(int additional) throws EvalException {
int mincap = size + additional;
if (mincap < 0 || mincap > MAX_ALLOC) {
throw Starlark.errorf("excessive capacity requested (%d + %d elements)", size, additional);
}
grow(mincap);
}
@Override
public void addElement(E element) throws EvalException {
Starlark.checkMutable(this);
growAdditional(1);
elems[size++] = element;
}
@Override
public void addElementAt(int index, E element) throws EvalException {
Starlark.checkMutable(this);
growAdditional(1);
System.arraycopy(elems, index, elems, index + 1, size - index);
elems[index] = element;
size++;
}
@Override
public void addElements(Iterable<? extends E> elements) throws EvalException {
Starlark.checkMutable(this);
if (elements instanceof MutableStarlarkList) {
MutableStarlarkList<?> that = (MutableStarlarkList) elements;
// (safe even if this == that)
growAdditional(that.size);
System.arraycopy(that.elems, 0, this.elems, this.size, that.size);
this.size += that.size;
} else if (elements instanceof Collection) {
// collection of known size
Collection<?> that = (Collection) elements;
growAdditional(that.size());
for (Object x : that) {
elems[size++] = x;
}
} else {
// iterable
for (Object x : elements) {
growAdditional(1);
elems[size++] = x;
}
}
}
@Override
public void removeElementAt(int index) throws EvalException {
Starlark.checkMutable(this);
int n = size - index - 1;
if (n > 0) {
System.arraycopy(elems, index + 1, elems, index, n);
}
elems[--size] = null; // aid GC
}
@Override
public void setElementAt(int index, E value) throws EvalException {
Starlark.checkMutable(this);
Preconditions.checkArgument(index < size);
elems[index] = value;
}
@Override
public void clearElements() throws EvalException {
Starlark.checkMutable(this);
for (int i = 0; i < size; i++) {
elems[i] = null; // aid GC
}
size = 0;
}
/** Returns a new array of class Object[] containing the list elements. */
@Override
public Object[] toArray() {
return size != 0 ? Arrays.copyOf(elems, size, Object[].class) : EMPTY_ARRAY;
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] a) {
if (a.length < size) {
return (T[]) Arrays.copyOf(elems, size, a.getClass());
} else {
System.arraycopy(elems, 0, a, 0, size);
Arrays.fill(a, size, a.length, null);
return a;
}
}
@Override
Object[] elems() {
return elems;
}
@Override
public StarlarkList<E> unsafeOptimizeMemoryLayout() {
Preconditions.checkState(mutability.isFrozen());
// Canonicalize our Mutability, on the off-chance the old one may be freed.
mutability = Mutability.IMMUTABLE;
if (elems.length > size) {
// shrink the Object array
elems = Arrays.copyOf(elems, size);
}
// Give the caller an immutable specialization of StarlarkList.
return wrap(mutability, elems);
}
}