Lightweight collections. ImmutableArray and MutableArray.freeze()
implementation.
Review at http://gwt-code-reviews.appspot.com/291801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7845 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/collections/Array.java b/bikeshed/src/com/google/gwt/collections/Array.java
index f6cded1..96a5cfe 100644
--- a/bikeshed/src/com/google/gwt/collections/Array.java
+++ b/bikeshed/src/com/google/gwt/collections/Array.java
@@ -31,4 +31,5 @@
@ConstantTime
public abstract int size();
+
}
diff --git a/bikeshed/src/com/google/gwt/collections/Assertions.java b/bikeshed/src/com/google/gwt/collections/Assertions.java
index 1a34e8d..282249c 100644
--- a/bikeshed/src/com/google/gwt/collections/Assertions.java
+++ b/bikeshed/src/com/google/gwt/collections/Assertions.java
@@ -20,13 +20,27 @@
*/
class Assertions {
+ static void assertGetFromImmutableEmpty() {
+ assert false : "Attempt to get an element from an immutable empty array";
+ }
+
static void assertIndexInRange(int index, int minInclusive, int maxExclusive) {
assert (index >= minInclusive && index < maxExclusive) : "Index " + index
+ " was not in the acceptable range [" + minInclusive + ", " + maxExclusive + ")";
}
+ static <E> void assertNotFrozen(MutableArray<E> a) {
+ assert !a.isFrozen() : "This operation is illegal on a frozen collection";
+ }
+
static void assertNotNull(Object ref) {
assert (ref != null) : "A null reference is not allowed here";
}
+
+ static <E> void markFrozen(MutableArray<E> a) {
+ if (Assertions.class.desiredAssertionStatus()) {
+ a.markFrozen();
+ }
+ }
}
diff --git a/bikeshed/src/com/google/gwt/collections/ImmutableArray.java b/bikeshed/src/com/google/gwt/collections/ImmutableArray.java
new file mode 100644
index 0000000..41d55ff
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/collections/ImmutableArray.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * An array that is guaranteed not to change, thus making it safe for disparate
+ * portions of code to maintain references to a shared instance, rather than
+ * feeling the need to make defensive copies.
+ *
+ * @param <E> The type stored in the array elements
+ */
+public abstract class ImmutableArray<E> extends Array<E> {
+
+ @SuppressWarnings("unchecked")
+ private static final ImmutableArray EMPTY = new ImmutableArrayEmptyImpl();
+
+ @SuppressWarnings("unchecked")
+ static <E> ImmutableArray<E> getEmptyInstance() {
+ return EMPTY;
+ }
+
+ ImmutableArray() {
+ }
+
+}
diff --git a/bikeshed/src/com/google/gwt/collections/ImmutableArrayEmptyImpl.java b/bikeshed/src/com/google/gwt/collections/ImmutableArrayEmptyImpl.java
new file mode 100644
index 0000000..bd0809c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/collections/ImmutableArrayEmptyImpl.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * The standard byte code implementation of an immutable array.
+ *
+ * @param <E> The type stored in the array elements
+ */
+class ImmutableArrayEmptyImpl<E> extends ImmutableArray<E> {
+
+ @Override
+ public E get(int index) {
+ Assertions.assertGetFromImmutableEmpty();
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+}
diff --git a/bikeshed/src/com/google/gwt/collections/ImmutableArrayImpl.java b/bikeshed/src/com/google/gwt/collections/ImmutableArrayImpl.java
new file mode 100644
index 0000000..6fee8c0
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/collections/ImmutableArrayImpl.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * The standard byte code implementation of an immutable array.
+ *
+ * @param <E> The type stored in the array elements
+ */
+public class ImmutableArrayImpl<E> extends ImmutableArray<E> {
+
+ final E[] elems;
+
+ ImmutableArrayImpl(E[] elems) {
+ Assertions.assertNotNull(elems);
+ this.elems = elems;
+ }
+
+ @Override
+ public E get(int index) {
+ Assertions.assertIndexInRange(index, 0, elems.length);
+ return elems[index];
+ }
+
+ @Override
+ public int size() {
+ return elems.length;
+ }
+
+}
diff --git a/bikeshed/src/com/google/gwt/collections/MutableArray.java b/bikeshed/src/com/google/gwt/collections/MutableArray.java
index 3201458..444594e 100644
--- a/bikeshed/src/com/google/gwt/collections/MutableArray.java
+++ b/bikeshed/src/com/google/gwt/collections/MutableArray.java
@@ -23,7 +23,7 @@
public class MutableArray<E> extends Array<E> {
// The elements in the array
- private E[] elems;
+ E[] elems;
// Tracks when this array is frozen (for assertion enforcement only)
private boolean frozen;
@@ -41,8 +41,28 @@
@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
@@ -66,6 +86,7 @@
@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?)
@@ -88,6 +109,7 @@
@SuppressWarnings("unchecked")
@LinearTime
public void remove(int index) {
+ Assertions.assertNotFrozen(this);
Assertions.assertIndexInRange(index, 0, size());
if (elems != null && elems.length >= 1) {
// TODO: replace with splice using JSNI
@@ -111,6 +133,7 @@
*/
@ConstantTime
public void set(int index, E elem) {
+ Assertions.assertNotFrozen(this);
Assertions.assertIndexInRange(index, 0, size());
if (elems != null) {
elems[index] = elem;
diff --git a/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java b/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java
new file mode 100644
index 0000000..dbc123c
--- /dev/null
+++ b/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests immutable arrays.
+ */
+public class ImmutableArrayTest extends GWTTestCase {
+
+ private boolean assertionsEnabled;
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ public void gwtSetUp() {
+ assertionsEnabled = this.getClass().desiredAssertionStatus();
+ }
+
+ public void testAccessOutOfBounds() {
+ // Do not test undefined behavior with assertions disabled
+ if (!assertionsEnabled) {
+ return;
+ }
+
+ MutableArray<Integer> ma;
+ ImmutableArray<Integer> ia;
+
+ ma = CollectionFactory.createMutableArray();
+ ma.add(1);
+ ma.add(2);
+
+ ia = ma.freeze();
+
+ try {
+ ia.get(ia.size());
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals(("Index " + ia.size() + " was not in the acceptable range [" + 0 + ", " + ia.size()
+ + ")"), e.getMessage());
+ }
+
+ assertEquals(2, ma.size());
+
+ ma = CollectionFactory.createMutableArray();
+ // No elements added
+ ia = ma.freeze();
+
+ try {
+ ia.get(ia.size());
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals("Attempt to get an element from an immutable empty array", e.getMessage());
+ }
+ }
+
+ public void testFreezeEmptyArray() {
+ MutableArray<String> ma = CollectionFactory.createMutableArray();
+ // No elements added
+ ImmutableArray<String> ia = ma.freeze();
+
+ assertEquals(0, ia.size());
+ }
+
+ public void testFreezeMutable() {
+ MutableArray<String> ma = CollectionFactory.createMutableArray();
+ ma.add("pear");
+ ma.add("apple");
+ ImmutableArray<String> ia = ma.freeze();
+
+ assertEquals(2, ia.size());
+ assertEquals("pear", ia.get(0));
+ assertEquals("apple", ia.get(1));
+ }
+
+ public void testFreezeSingleElement() {
+ MutableArray<String> ma = CollectionFactory.createMutableArray();
+ ma.add("pear");
+ ImmutableArray<String> ia = ma.freeze();
+
+ assertEquals(1, ia.size());
+ assertEquals("pear", ia.get(0));
+ }
+
+ public void testModifyFrozenMutable() {
+ // Do not test undefined behavior with assertions disabled
+ if (!assertionsEnabled) {
+ return;
+ }
+
+ MutableArray<String> ma = CollectionFactory.createMutableArray();
+ ma.add("pear");
+ ma.add("apple");
+ ma.freeze();
+
+ try {
+ ma.add("orange");
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals(("This operation is illegal on a frozen collection"), e.getMessage());
+ }
+
+ try {
+ ma.clear();
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals(("This operation is illegal on a frozen collection"), e.getMessage());
+ }
+
+ try {
+ ma.insert(0, "orange");
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals(("This operation is illegal on a frozen collection"), e.getMessage());
+ }
+
+ try {
+ ma.remove(0);
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals(("This operation is illegal on a frozen collection"), e.getMessage());
+ }
+
+ try {
+ ma.set(0, "orange");
+ fail("Expected an assertion failure");
+ } catch (AssertionError e) {
+ assertEquals(("This operation is illegal on a frozen collection"), e.getMessage());
+ }
+ }
+
+ public void testImmutableNoCopy() {
+ MutableArray<String> ma = CollectionFactory.createMutableArray();
+ ma.add("pear");
+ ma.add("apple");
+ ImmutableArrayImpl<String> ia1 = (ImmutableArrayImpl<String>) ma.freeze();
+
+ assertTrue(ma.elems == ia1.elems);
+
+ ImmutableArrayImpl<String> ia2 = (ImmutableArrayImpl<String>) ma.freeze();
+
+ assertTrue(ia1.elems == ia2.elems);
+ }
+
+}
diff --git a/bikeshed/test/com/google/gwt/collections/ObjectArrayTest.java b/bikeshed/test/com/google/gwt/collections/ObjectArrayTest.java
index 312de31..ed63876 100644
--- a/bikeshed/test/com/google/gwt/collections/ObjectArrayTest.java
+++ b/bikeshed/test/com/google/gwt/collections/ObjectArrayTest.java
@@ -20,7 +20,7 @@
import com.google.gwt.junit.client.GWTTestCase;
/**
- * Tests immutable and mutable arrays.
+ * Tests mutable arrays.
*/
public class ObjectArrayTest extends GWTTestCase {