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',
+  ],
+)