Add emulation for java.lang.reflect.Array.

Change-Id: Ie6c4b232d2d4c582cc0b8bfd6d00f86700f03c6d
Review-Link: https://gwt-review.googlesource.com/#/c/19260/
diff --git a/user/super/com/google/gwt/emul/java/lang/reflect/Array.java b/user/super/com/google/gwt/emul/java/lang/reflect/Array.java
new file mode 100644
index 0000000..f9a3a6c
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/lang/reflect/Array.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2017 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 java.lang.reflect;
+
+import static javaemul.internal.InternalPreconditions.checkArgument;
+import static javaemul.internal.InternalPreconditions.checkNotNull;
+
+import javaemul.internal.ArrayHelper;
+
+/**
+ * See <a
+ * href="http://java.sun.com/javase/6/docs/api/java/lang/reflect/Array.html">the
+ * official Java API doc</a> for details.
+ */
+public final class Array {
+
+  public static Object get(Object array, int index) {
+    if (array instanceof boolean[]) {
+      return getBooleanImpl(array, index);
+    } else if (array instanceof byte[]) {
+      return getByteImpl(array, index);
+    } else if (array instanceof char[]) {
+      return getCharImpl(array, index);
+    } else if (array instanceof double[]) {
+      return getDoubleImpl(array, index);
+    } else if (array instanceof float[]) {
+      return getFloatImpl(array, index);
+    } else if (array instanceof int[]) {
+      return getIntImpl(array, index);
+    } else if (array instanceof long[]) {
+      return getLongImpl(array, index);
+    } else if (array instanceof short[]) {
+      return getShortImpl(array, index);
+    } else  {
+      checkArgument(array instanceof Object[]);
+      Object[] typedArray = (Object[]) array;
+      return typedArray[index];
+    }
+  }
+
+  public static boolean getBoolean(Object array, int index) {
+    checkArgument(array instanceof boolean[]);
+    return getBooleanImpl(array, index);
+  }
+
+  private static boolean getBooleanImpl(Object array, int index) {
+    boolean[] typedArray = (boolean[]) array;
+    return typedArray[index];
+  }
+
+  public static byte getByte(Object array, int index) {
+    checkArgument(array instanceof byte[]);
+    return getByteImpl(array, index);
+  }
+
+  private static byte getByteImpl(Object array, int index) {
+    byte[] typedArray = (byte[]) array;
+    return typedArray[index];
+  }
+
+  public static char getChar(Object array, int index) {
+    checkArgument(array instanceof char[]);
+    return getCharImpl(array, index);
+  }
+
+  private static char getCharImpl(Object array, int index) {
+    char[] typedArray = (char[]) array;
+    return typedArray[index];
+  }
+
+  public static double getDouble(Object array, int index) {
+    checkArgument(array instanceof double[]);
+    return getDoubleImpl(array, index);
+  }
+
+  private static double getDoubleImpl(Object array, int index) {
+    double[] typedArray = (double[]) array;
+    return typedArray[index];
+  }
+
+  public static float getFloat(Object array, int index) {
+    checkArgument(array instanceof float[]);
+    return getFloatImpl(array, index);
+  }
+
+  private static float getFloatImpl(Object array, int index) {
+    float[] typedArray = (float[]) array;
+    return typedArray[index];
+  }
+
+  public static int getInt(Object array, int index) {
+    checkArgument(array instanceof int[]);
+    return getIntImpl(array, index);
+  }
+
+  private static int getIntImpl(Object array, int index) {
+    int[] typedArray = (int[]) array;
+    return typedArray[index];
+  }
+
+  public static int getLength(Object array) {
+    checkNotNull(array);
+    return ArrayHelper.getLength(array);
+  }
+
+  public static long getLong(Object array, int index) {
+    checkArgument(array instanceof long[]);
+    return getLongImpl(array, index);
+  }
+
+  private static long getLongImpl(Object array, int index) {
+    long[] typedArray = (long[]) array;
+    return typedArray[index];
+  }
+
+  public static short getShort(Object array, int index) {
+    checkArgument(array instanceof short[]);
+    return getShortImpl(array, index);
+  }
+
+  private static short getShortImpl(Object array, int index) {
+    short[] typedArray = (short[]) array;
+    return typedArray[index];
+  }
+
+  public static void set(Object array, int index, Object value) {
+    if (array instanceof boolean[]) {
+      setBooleanImpl(array, index, (Boolean) value);
+    } else if (array instanceof byte[]) {
+      setByteImpl(array, index, (Byte) value);
+    } else if (array instanceof char[]) {
+      setCharImpl(array, index, (Character) value);
+    } else if (array instanceof double[]) {
+      setDoubleImpl(array, index, (Double) value);
+    } else if (array instanceof float[]) {
+      setFloatImpl(array, index, (Float) value);
+    } else if (array instanceof int[]) {
+      setIntImpl(array, index, (Integer) value);
+    } else if (array instanceof long[]) {
+      setLongImpl(array, index, (Long) value);
+    } else if (array instanceof short[]) {
+      setShortImpl(array, index, (Short) value);
+    } else {
+      checkArgument(array instanceof Object[]);
+      Object[] typedArray = (Object[]) array;
+      typedArray[index] = value;
+    }
+  }
+
+  public static void setBoolean(Object array, int index, boolean value) {
+    checkArgument(array instanceof boolean[]);
+    setBooleanImpl(array, index, value);
+  }
+
+  private static void setBooleanImpl(Object array, int index, boolean value) {
+    boolean[] typedArray = (boolean[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setByte(Object array, int index, byte value) {
+    checkArgument(array instanceof byte[]);
+    setByteImpl(array, index, value);
+  }
+
+  private static void setByteImpl(Object array, int index, byte value) {
+    byte[] typedArray = (byte[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setChar(Object array, int index, char value) {
+    checkArgument(array instanceof char[]);
+    setCharImpl(array, index, value);
+  }
+
+  private static void setCharImpl(Object array, int index, char value) {
+    char[] typedArray = (char[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setDouble(Object array, int index, double value) {
+    checkArgument(array instanceof double[]);
+    setDoubleImpl(array, index, value);
+  }
+
+  private static void setDoubleImpl(Object array,int index, double value) {
+    double[] typedArray = (double[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setFloat(Object array, int index, float value) {
+    checkArgument(array instanceof float[]);
+    setFloatImpl(array, index, value);
+  }
+
+  private static void setFloatImpl(Object array, int index, float value) {
+    float[] typedArray = (float[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setInt(Object array, int index, int value) {
+    checkArgument(array instanceof int[]);
+    setIntImpl(array, index, value);
+  }
+
+  private static void setIntImpl(Object array, int index, int value) {
+    int[] typedArray = (int[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setLong(Object array, int index, long value) {
+    checkArgument(array instanceof long[]);
+    setLongImpl(array, index, value);
+  }
+
+  private static void setLongImpl(Object array, int index, long value) {
+    long[] typedArray = (long[]) array;
+    typedArray[index] = value;
+  }
+
+  public static void setShort(Object array, int index, short value) {
+    checkArgument(array instanceof short[]);
+    setShortImpl(array, index, value);
+  }
+
+  private static void setShortImpl(Object array, int index, short value) {
+    short[] typedArray = (short[]) array;
+    typedArray[index] = value;
+  }
+
+  // There is a good reason to not implement the generic newInstance methods:
+  //
+  // In GWT we would need access from a class literal to the castable type map, which
+  // could be done, but would require some serious work.
+  // But it would also mean that we can not compute all Array types being instantiated anymore and
+  // thus would have to retain more castable type information (bloat).
+  //
+  // In J2CL the problem is slightly different. Most checking is deferred to runtime in J2CL and
+  // we retain constructor functions of leaf types. To ensure that we are not just retaining
+  // all constructors in a program we make sure that access to these always gets inlined with
+  // array creation. By allowing a generic method we would not be able to ensure this anymore
+  // and might cause serious bloat in a program.
+  // A middle ground for J2CL could be to treat this method special in the compiler and fail the
+  // compile if its not called with a compile time constant for the class literal.
+
+  // Not implemented:
+  // public static Object newInstance(Class<?> componentType, int... dimensions)
+  // public static Object newInstance(Class<?> componentType, int length)
+
+  private Array() {
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 483ae7a..155d866 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -41,6 +41,7 @@
 import com.google.gwt.emultest.java.lang.ThrowableStackTraceEmulTest;
 import com.google.gwt.emultest.java.lang.ThrowableTest;
 import com.google.gwt.emultest.java.lang.TypeTest;
+import com.google.gwt.emultest.java.lang.reflect.ArrayTest;
 import com.google.gwt.emultest.java.math.MathContextTest;
 import com.google.gwt.emultest.java.math.MathContextWithObfuscatedEnumsTest;
 import com.google.gwt.emultest.java.math.RoundingModeTest;
@@ -91,6 +92,9 @@
   ThrowableStackTraceEmulTest.class,
   TypeTest.class,
 
+  // java.lang.reflect
+  ArrayTest.class,
+
   //-- java.math
   // BigDecimal is tested in {@link BigDecimalSuite}
   // BigInteger is tested in {@link BigIntegerSuite}
diff --git a/user/test/com/google/gwt/emultest/java/lang/reflect/ArrayTest.java b/user/test/com/google/gwt/emultest/java/lang/reflect/ArrayTest.java
new file mode 100644
index 0000000..420248c
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/lang/reflect/ArrayTest.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2017 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.emultest.java.lang.reflect;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.lang.reflect.Array;
+
+/** Tests for java.lang.reflect.Array. */
+public final class ArrayTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  public void testGet() {
+    try {
+      Array.get(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.get(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals("1", Array.get(new Object[] {"1"}, 0));
+    assertEquals(Boolean.TRUE, Array.get(new boolean[] {true}, 0));
+    assertEquals(new Byte((byte) 1), Array.get(new byte[] {1}, 0));
+    assertEquals(new Character('1'), Array.get(new char[] {'1'}, 0));
+    assertEquals(new Double(1.0d), Array.get(new double[] {1}, 0));
+    assertEquals(new Float(1.0f), Array.get(new float[] {1.0f}, 0));
+    assertEquals(new Integer(1), Array.get(new int[] {1}, 0));
+    assertEquals(new Long(1L), Array.get(new long[] {1}, 0));
+    assertEquals(new Short((short) 1), Array.get(new short[] {1}, 0));
+  }
+
+  public void testGetBoolean() {
+    try {
+      Array.getBoolean(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getBoolean(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getBoolean(new Boolean[] {true}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertTrue(Array.getBoolean(new boolean[] {true}, 0));
+    assertFalse(Array.getBoolean(new boolean[] {false}, 0));
+    assertTrue(Array.getBoolean(new boolean[] {Boolean.TRUE}, 0));
+    assertFalse(Array.getBoolean(new boolean[] {Boolean.FALSE}, 0));
+  }
+
+  public void testGetByte() {
+    try {
+      Array.getByte(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getByte(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getByte(new Byte[] {(byte) 0}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals((byte) 1, Array.getByte(new byte[] {(byte) 1}, 0));
+    assertEquals((byte) 1, Array.getByte(new byte[] {(byte) 1}, 0));
+    assertEquals((byte) 1, Array.getByte(new byte[] {(byte) 1}, 0));
+    assertEquals((byte) 1, Array.getByte(new byte[] {(byte) 1}, 0));
+  }
+
+  public void testGetChar() {
+    try {
+      Array.getChar(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getChar(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getChar(new Character[] {'0'}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals('1', Array.getChar(new char[] {'1'}, 0));
+  }
+
+  public void testGetDouble() {
+    try {
+      Array.getDouble(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getDouble(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getDouble(new Double[] {0d}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals(1.0d, Array.getDouble(new double[] {1.0d}, 0));
+  }
+
+  public void testGetFloat() {
+    try {
+      Array.getFloat(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getFloat(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getFloat(new Float[] {0f}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals(1.0f, Array.getFloat(new float[] {1.0f}, 0));
+  }
+
+  public void testGetInt() {
+    try {
+      Array.getInt(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getInt(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getInt(new Integer[] {0}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals(1, Array.getInt(new int[] {1}, 0));
+  }
+
+  public void testGetLength() {
+    try {
+      Array.getLength(null);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+    assertEquals(0, Array.getLength(new Object[0]));
+    assertEquals(1, Array.getLength(new Object[1]));
+
+    assertEquals(0, Array.getLength(new int[0]));
+    assertEquals(1, Array.getLength(new int[1]));
+  }
+
+  public void testGetLong() {
+    try {
+      Array.getLong(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getLong(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getLong(new Long[] {0L}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals(1L, Array.getLong(new long[] {1L}, 0));
+  }
+
+  public void testGetShort() {
+    try {
+      Array.getShort(null, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getShort(new Object(), 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.getShort(new Short[] {(short) 1}, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    assertEquals((short) 1, Array.getShort(new short[] {(short) 1}, 0));
+  }
+
+  public void testSet() {
+    try {
+      Array.set(null, 0, true);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.set(new Object(), 0, true);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    Object[] objectArray = new Object[1];
+    Array.set(objectArray, 0, "1");
+    assertEquals("1", objectArray[0]);
+
+    boolean[] booleanArray = new boolean[1];
+    Array.set(booleanArray, 0, true);
+    assertTrue(booleanArray[0]);
+
+    byte[] byteArray = new byte[1];
+    Array.set(byteArray, 0, (byte) 1);
+    assertEquals((byte) 1, byteArray[0]);
+
+    char[] charArray = new char[1];
+    Array.set(charArray, 0, 'a');
+    assertEquals('a', charArray[0]);
+
+    double[] doubleArray = new double[1];
+    Array.set(doubleArray, 0, 1.0d);
+    assertEquals(1.0d, doubleArray[0]);
+
+    float[] floatArray = new float[1];
+    Array.set(floatArray, 0, 1.0f);
+    assertEquals(1.0f, floatArray[0]);
+
+    int[] intArray = new int[1];
+    Array.set(intArray, 0, 1);
+    assertEquals(1, intArray[0]);
+
+    long[] longArray = new long[1];
+    Array.set(longArray, 0, 1L);
+    assertEquals(1L, longArray[0]);
+
+    short[] shortArray = new short[1];
+    Array.set(shortArray, 0, (short) 1);
+    assertEquals((short) 1, shortArray[0]);
+  }
+
+  public void testSetBoolean() {
+    try {
+      Array.setBoolean(null, 0, true);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setBoolean(new Object(), 0, true);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    boolean[] array = new boolean[1];
+
+    Array.setBoolean(array, 0, true);
+    assertTrue(array[0]);
+  }
+
+  public void testSetByte() {
+    try {
+      Array.setByte(null, 0, (byte) 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setByte(new Object(), 0, (byte) 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    byte[] array = new byte[1];
+
+    Array.setByte(array, 0, (byte) 1);
+    assertEquals((byte) 1, array[0]);
+  }
+
+  public void testSetChar() {
+    try {
+      Array.setChar(null, 0, 'a');
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setChar(new Object(), 0, 'a');
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    char[] array = new char[1];
+
+    Array.setChar(array, 0, 'a');
+    assertEquals('a', array[0]);
+  }
+
+  public void testSetDouble() {
+    try {
+      Array.setDouble(null, 0, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setDouble(new Object(), 0, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    double[] array = new double[1];
+
+    Array.setDouble(array, 0, 1d);
+    assertEquals(1d, array[0]);
+  }
+
+  public void testSetFloat() {
+    try {
+      Array.setFloat(null, 0, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setFloat(new Object(), 0, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    float[] array = new float[1];
+
+    Array.setFloat(array, 0, 1.0f);
+    assertEquals(1.0f, array[0]);
+  }
+
+  public void testSetInt() {
+    try {
+      Array.setInt(null, 0, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setInt(new Object(), 0, 0);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    int[] array = new int[1];
+
+    Array.setInt(array, 0, 1);
+    assertEquals(1, array[0]);
+  }
+
+  public void testSetLong() {
+    try {
+      Array.setLong(null, 0, 0L);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setLong(new Object(), 0, 0L);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    long[] array = new long[1];
+
+    Array.setLong(array, 0, 1L);
+    assertEquals(1L, array[0]);
+  }
+
+  public void testSetShort() {
+    try {
+      Array.setShort(null, 0, (short) 1);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    try {
+      Array.setShort(new Object(), 0, (short) 1);
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    short[] array = new short[1];
+
+    Array.setShort(array, 0, (short) 1);
+    assertEquals((short) 1, array[0]);
+  }
+}