blob: 547f2bf0568119049b9d41dc436ce5337b05a7b6 [file] [log] [blame]
/*
* Copyright 2009 Google Inc.
*
* 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 com.google.gwt.collections;
import java.util.Arrays;
/**
* An array whose content and length can change over time.
*
* @param <E> The type stored in the array elements
*/
public class MutableArray<E> extends Array<E> {
// TODO: refactor the unchecked elems construction into a separate method
// The elements in the array
E[] elems;
// Tracks when this array is frozen (for assertion enforcement only)
private boolean frozen;
/**
* Can only be constructed via {@link CollectionFactory}.
*/
MutableArray() {
}
@ConstantTime
public void add(E elem) {
insert(size(), elem);
}
@ConstantTime
public void clear() {
Assertions.assertNotFrozen(this);
elems = null;
}
/**
* Creates an immutable array based on this one. Also marks this object as read-only.
* After calling {@code freeze()}, only use methods from {@link Array} or the returned
* {@link ImmutableArray} should be to access the elements
* of the array is preferred.
*/
public ImmutableArray<E> freeze() {
Assertions.markFrozen(this);
ImmutableArray<E> r;
if (elems != null) {
r = new ImmutableArrayImpl<E>(elems);
} else {
r = ImmutableArray.getEmptyInstance();
}
return r;
}
@Override
@ConstantTime
public E get(int index) {
Assertions.assertIndexInRange(index, 0, size());
if (elems != null) {
return elems[index];
} else {
return null;
}
}
/**
* Inserts {@code element} before the element residing at {@code index}.
*
* @param index in the range [0, this.size()], inclusive; if index is equal
* to the array's current size, the result is equivalent to calling
* {@link #add(Object)}
* @param elem the element to insert or {@code null}
*/
@SuppressWarnings("unchecked")
@LinearTime
public void insert(int index, E elem) {
Assertions.assertNotFrozen(this);
Assertions.assertIndexInRange(index, 0, size() + 1);
// TODO benchmark to see if we need to grow smartly (2x size?)
// TODO benchmark to see if we need to consider single element arrays separately
if (elems != null) {
int oldLen = elems.length;
E[] newElems = (E[]) new Object[oldLen + 1];
System.arraycopy(elems, 0, newElems, 0, index);
System.arraycopy(elems, index, newElems, index + 1, oldLen - index);
newElems[index] = elem;
elems = newElems;
} else {
elems = (E[]) (new Object[] {elem});
}
}
/**
* Removes the element at the specified index.
*/
@SuppressWarnings("unchecked")
@LinearTime
public void remove(int index) {
Assertions.assertNotFrozen(this);
Assertions.assertIndexInRange(index, 0, size());
if (elems != null && elems.length >= 1) {
int oldLen = elems.length;
E[] newElems = (E[]) new Object[oldLen - 1];
System.arraycopy(elems, 0, newElems, 0, index);
System.arraycopy(elems, index + 1, newElems, index, oldLen - index - 1);
elems = newElems;
} else if (elems != null && elems.length == 1) {
elems = null;
} else {
assert false : "index " + index + " in range [0, " + size() + "), but remove(int) failed";
}
}
/**
* Replaces the element at the specified index.
*
* @param index in the range [0, this.size()), exclusive
* @param elem the element to insert or {@code null}
*/
@ConstantTime
public void set(int index, E elem) {
Assertions.assertNotFrozen(this);
Assertions.assertIndexInRange(index, 0, size());
if (elems != null) {
elems[index] = elem;
} else {
assert false : "index " + index + " in range [0, " + size() + "), but set(int,E) failed";
}
}
/**
* Changes the array size. If {@code newSize} is less than the current size, the array is
* truncated. If {@code newSize} is greater than the current size the array is grown and
* the new elements of the array filled up with {@code fillValue}.
*/
@SuppressWarnings("unchecked")
@LinearTime
public void setSize(int newSize, E fillValue) {
Assertions.assertNotFrozen(this);
assert newSize >= 0 : "expecting newSize >= 0, got " + newSize;
int fillStart;
if (newSize == size()) {
return;
} else if (newSize == 0) {
elems = null;
return;
}
if (elems == null) {
fillStart = 0;
} else if (newSize < elems.length) {
// nothing to fill
fillStart = newSize;
} else {
fillStart = elems.length;
}
E[] newElems = (E[]) new Object[newSize];
if (fillStart != 0) {
System.arraycopy(elems, 0, newElems, 0, fillStart);
}
Arrays.fill(newElems, fillStart, newSize, fillValue);
elems = newElems;
}
@Override
@ConstantTime
public int size() {
if (elems != null) {
return elems.length;
} else {
return 0;
}
}
// Only meant to be called from within Assertions
boolean isFrozen() {
return frozen;
}
// Only meant to be called from within Assertions
void markFrozen() {
frozen = true;
}
}