Introduce ArrayIterable.
Summary:
ArrayIterable is a utility for creating an Iterable for an array
when you do not want to copy the array.
Test Plan: Unit test.
diff --git a/src/com/facebook/buck/util/collect/ArrayIterable.java b/src/com/facebook/buck/util/collect/ArrayIterable.java
new file mode 100644
index 0000000..88f1170
--- /dev/null
+++ b/src/com/facebook/buck/util/collect/ArrayIterable.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012-present Facebook, 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.facebook.buck.util.collect;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * {@link Iterable} of an array object that does not make a copy of the array.
+ * <p>
+ * Designed to be used when it is too expensive to copy an array to a {@link java.util.List} and use
+ * the list as an {@link Iterable} instead.
+ */
+public class ArrayIterable<T> implements Iterable<T> {
+
+ private final T[] array;
+ private final int startIndex;
+ private final int endIndex;
+
+ /**
+ * @param startIndex inclusive
+ * @param endIndex exclusive
+ */
+ private ArrayIterable(T[] array, int startIndex, int endIndex) {
+ // No precondition checks here: they have already been performed in the of() method.
+ this.array = array;
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ }
+
+ public static <T> Iterable<T> of(T[] array) {
+ return of(array, /* startIndex */ 0, /* endIndex */ array.length);
+ }
+
+ public static <T> Iterable<T> of(final T[] array, final int startIndex, int endIndex) {
+ Preconditions.checkNotNull(array);
+ Preconditions.checkPositionIndexes(startIndex, endIndex, array.length);
+
+ // Special-case the empty Iterator with a singleton value.
+ if (startIndex >= endIndex) {
+ // Note that Collections.emptyIterator().remove() throws an IllegalStateException. We prefer
+ // that remove() throws an UnsupportedOperationException for an empty Iterator, so we use
+ // ImmutableList instead.
+ return ImmutableList.<T>of();
+ } else if (endIndex - startIndex == 1) {
+ return new Iterable<T>() {
+ @Override
+ public Iterator<T> iterator() {
+ // This always looks up the element in the array in case the user modifies the contents of
+ // the array outside of this method. That would probably be a bad thing for the user to
+ // do, but this ensures that the behavior is consistent with ArrayIterable.
+ return Iterators.singletonIterator(array[startIndex]);
+ }
+ };
+ } else {
+ return new ArrayIterable<T>(array, startIndex, endIndex);
+ }
+ }
+
+ @SuppressWarnings("unchecked") // (Iterator<T>) ImmutableList.of()
+ @Override
+ public Iterator<T> iterator() {
+
+ return new Iterator<T>() {
+
+ private int index = startIndex;
+
+ @Override
+ public boolean hasNext() {
+ return index < endIndex;
+ }
+
+ @Override
+ public T next() {
+ if (hasNext()) {
+ T element = array[index];
+ index++;
+ return element;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+ }
+
+}
diff --git a/src/com/facebook/buck/util/collect/BUCK b/src/com/facebook/buck/util/collect/BUCK
new file mode 100644
index 0000000..3e41e8a
--- /dev/null
+++ b/src/com/facebook/buck/util/collect/BUCK
@@ -0,0 +1,10 @@
+java_library(
+ name = 'collect',
+ srcs = glob(['*.java']),
+ deps = [
+ '//lib:guava',
+ ],
+ visibility = [
+ 'PUBLIC',
+ ],
+)
diff --git a/test/com/facebook/buck/util/collect/ArrayIterableTest.java b/test/com/facebook/buck/util/collect/ArrayIterableTest.java
new file mode 100644
index 0000000..30f14a0
--- /dev/null
+++ b/test/com/facebook/buck/util/collect/ArrayIterableTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012-present Facebook, 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.facebook.buck.util.collect;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class ArrayIterableTest {
+
+ @Test(expected = NullPointerException.class)
+ public void testSingleParamConstructorRejectsNullArray() {
+ ArrayIterable.of(/* array */ null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testMultiParamConstructorRejectsNullArray() {
+ ArrayIterable.of(/* array */ null, 0, 0);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testMultiParamConstructorRejectsNegativeStartIndex() {
+ ArrayIterable.of(new Object[] {}, -1, 0);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testMultiParamConstructorRejectsStartIndexLargerThanArray() {
+ ArrayIterable.of(new Object[] {}, 2, 0);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testMultiParamConstructorRejectsNegativeEndIndex() {
+ ArrayIterable.of(new Object[] {}, 0, -1);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testMultiParamConstructorRejectsEndIndexLargerThanArray() {
+ ArrayIterable.of(new Object[] {}, 0, 2);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testMultiParamConstructorRejectsStartIndexLargerThanEndIndex() {
+ ArrayIterable.of(new String[] {"a", "b", "c"}, 2, 1);
+ }
+
+ @Test
+ @SuppressWarnings("PMD.EmptyCatchBlock")
+ public void testEmptyArrayIterable() {
+ Iterable<Object> iterable = ArrayIterable.of(new Object[] {});
+ Iterator<Object> iterator = iterable.iterator();
+ assertFalse(iterator.hasNext());
+
+ try {
+ iterator.remove();
+ fail("Should throw UnsupportedOperationException.");
+ } catch (UnsupportedOperationException e) {
+ // OK
+ }
+
+ try {
+ iterator.next();
+ fail("Should throw NoSuchElementException.");
+ } catch (NoSuchElementException e) {
+ // OK
+ }
+ }
+
+ @Test
+ @SuppressWarnings("PMD.EmptyCatchBlock")
+ public void testEmptySingletonIterable() {
+ Iterable<String> iterable = ArrayIterable.of(new String[] { "only" });
+ Iterator<String> iterator = iterable.iterator();
+
+ // First element.
+ assertTrue(iterator.hasNext());
+ assertEquals("only", iterator.next());
+
+ // Second element.
+ assertFalse(iterator.hasNext());
+ try {
+ iterator.next();
+ fail("Should throw NoSuchElementException.");
+ } catch (NoSuchElementException e) {
+ // OK
+ }
+ }
+
+ @Test
+ @SuppressWarnings("PMD.EmptyCatchBlock")
+ public void testMultiElementIterable() {
+ Iterable<String> iterable = ArrayIterable.of(new String[] { "a", "b", "c" });
+ Iterator<String> iterator = iterable.iterator();
+
+ // First element.
+ assertTrue(iterator.hasNext());
+ assertEquals("a", iterator.next());
+
+ // Second element.
+ assertTrue(iterator.hasNext());
+ assertEquals("b", iterator.next());
+
+ // Third element.
+ assertTrue(iterator.hasNext());
+ assertEquals("c", iterator.next());
+
+ try {
+ iterator.remove();
+ fail("Should throw UnsupportedOperationException.");
+ } catch (UnsupportedOperationException e) {
+ // OK
+ }
+
+ assertFalse("Iterator should be exhausted.", iterator.hasNext());
+ try {
+ iterator.next();
+ fail("Should throw NoSuchElementException.");
+ } catch (NoSuchElementException e) {
+ // OK
+ }
+ }
+
+ @Test
+ @SuppressWarnings("PMD.EmptyCatchBlock")
+ public void testMultiElementIterableWithIndexes() {
+ Iterable<String> iterable = ArrayIterable.of(
+ new String[] { "a", "b", "c", "d", "e" },
+ /* startIndex */ 1,
+ /* endIndex */ 4);
+ Iterator<String> iterator = iterable.iterator();
+
+ // First element.
+ assertTrue(iterator.hasNext());
+ assertEquals("b", iterator.next());
+
+ // Second element.
+ assertTrue(iterator.hasNext());
+ assertEquals("c", iterator.next());
+
+ // Third element.
+ assertTrue(iterator.hasNext());
+ assertEquals("d", iterator.next());
+
+ assertFalse("Iterator should be exhausted.", iterator.hasNext());
+ try {
+ iterator.next();
+ fail("Should throw NoSuchElementException.");
+ } catch (NoSuchElementException e) {
+ // OK
+ }
+ }
+}
diff --git a/test/com/facebook/buck/util/collect/BUCK b/test/com/facebook/buck/util/collect/BUCK
new file mode 100644
index 0000000..1e40fbe
--- /dev/null
+++ b/test/com/facebook/buck/util/collect/BUCK
@@ -0,0 +1,11 @@
+java_test(
+ name = 'collect',
+ srcs = glob(['*.java']),
+ source_under_test = [
+ '//src/com/facebook/buck/util/collect:collect',
+ ],
+ deps = [
+ '//lib:junit',
+ '//src/com/facebook/buck/util/collect:collect',
+ ],
+)