Add java.lang Java 8 API

This patch does not include new functional methods
and unsigned methods in Number implementations.
Some code and tests adapted from google guava libraries.

Change-Id: I5f2ca3069de73ad8d8801d8e7bca7ebb1ed36e43
diff --git a/user/super/com/google/gwt/emul/java/lang/Byte.java b/user/super/com/google/gwt/emul/java/lang/Byte.java
index 2d9a99b..bc87eed 100644
--- a/user/super/com/google/gwt/emul/java/lang/Byte.java
+++ b/user/super/com/google/gwt/emul/java/lang/Byte.java
@@ -23,6 +23,7 @@
   public static final byte MIN_VALUE = (byte) 0x80;
   public static final byte MAX_VALUE = (byte) 0x7F;
   public static final int SIZE = 8;
+  public static final int BYTES = SIZE / Byte.SIZE;
   public static final Class<Byte> TYPE = byte.class;
 
   /**
@@ -41,11 +42,6 @@
     return Byte.valueOf((byte) __decodeAndValidateInt(s, MIN_VALUE, MAX_VALUE));
   }
 
-  /**
-   * @skip
-   *
-   * Here for shared implementation with Arrays.hashCode
-   */
   public static int hashCode(byte b) {
     return b;
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/Character.java b/user/super/com/google/gwt/emul/java/lang/Character.java
index 5ebeccc..ca0c93d 100644
--- a/user/super/com/google/gwt/emul/java/lang/Character.java
+++ b/user/super/com/google/gwt/emul/java/lang/Character.java
@@ -128,6 +128,7 @@
   public static final int MAX_CODE_POINT = 0x10FFFF;
 
   public static final int SIZE = 16;
+  public static final int BYTES = SIZE / Byte.SIZE;
 
   public static int charCount(int codePoint) {
     return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1;
@@ -217,11 +218,6 @@
     return forDigit(digit);
   }
 
-  /**
-   * @skip
-   *
-   * public for shared implementation with Arrays.hashCode
-   */
   public static int hashCode(char c) {
     return c;
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/Double.java b/user/super/com/google/gwt/emul/java/lang/Double.java
index 390f694..27f1534 100644
--- a/user/super/com/google/gwt/emul/java/lang/Double.java
+++ b/user/super/com/google/gwt/emul/java/lang/Double.java
@@ -37,6 +37,7 @@
   public static final double NEGATIVE_INFINITY = -1d / 0d;
   public static final double POSITIVE_INFINITY = 1d / 0d;
   public static final int SIZE = 64;
+  public static final int BYTES = SIZE / Byte.SIZE;
   public static final Class<Double> TYPE = double.class;
 
   // 2^512, 2^-512
@@ -196,13 +197,14 @@
     return (ihi << 32) | ilo;
   }
 
-  /**
-   * @skip Here for shared implementation with Arrays.hashCode
-   */
   public static int hashCode(double d) {
     return (int) d;
   }
 
+  public static boolean isFinite(double x) {
+    return NEGATIVE_INFINITY < x && x < POSITIVE_INFINITY;
+  }
+
   public static boolean isInfinite(double x) {
     return x == POSITIVE_INFINITY || x == NEGATIVE_INFINITY;
   }
@@ -263,10 +265,22 @@
     return negative ? -d : d;
   }
 
+  public static double max(double a, double b) {
+    return Math.max(a, b);
+  }
+
+  public static double min(double a, double b) {
+    return Math.min(a, b);
+  }
+
   public static double parseDouble(String s) throws NumberFormatException {
     return __parseAndValidateDouble(s);
   }
 
+  public static double sum(double a, double b) {
+    return a + b;
+  }
+
   public static String toString(double b) {
     return String.valueOf(b);
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/Float.java b/user/super/com/google/gwt/emul/java/lang/Float.java
index 5d3647b..565e2df 100644
--- a/user/super/com/google/gwt/emul/java/lang/Float.java
+++ b/user/super/com/google/gwt/emul/java/lang/Float.java
@@ -28,6 +28,7 @@
   public static final float NEGATIVE_INFINITY = -1f / 0f;
   public static final float POSITIVE_INFINITY = 1f / 0f;
   public static final int SIZE = 32;
+  public static final int BYTES = SIZE / Byte.SIZE;
   public static final Class<Float> TYPE = float.class;
 
   private static final long POWER_31_INT = 2147483648L;
@@ -85,7 +86,6 @@
   }
 
   /**
-   * @skip Here for shared implementation with Arrays.hashCode.
    * @param f
    * @return hash value of float (currently just truncated to int)
    */
@@ -130,6 +130,10 @@
     return (float) Double.longBitsToDouble(bits64);
   }
 
+  public static boolean isFinite(float x) {
+    return Double.isFinite(x);
+  }
+
   public static boolean isInfinite(float x) {
     return Double.isInfinite(x);
   }
@@ -138,6 +142,14 @@
     return Double.isNaN(x);
   }
 
+  public static float max(float a, float b) {
+    return Math.max(a, b);
+  }
+
+  public static float min(float a, float b) {
+    return Math.min(a, b);
+  }
+
   public static float parseFloat(String s) throws NumberFormatException {
     double doubleValue = __parseAndValidateDouble(s);
     if (doubleValue > Float.MAX_VALUE) {
@@ -148,6 +160,10 @@
     return (float) doubleValue;
   }
 
+  public static float sum(float a, float b) {
+    return a + b;
+  }
+
   public static String toString(float b) {
     return String.valueOf(b);
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/Integer.java b/user/super/com/google/gwt/emul/java/lang/Integer.java
index 948bf75..bda0338 100644
--- a/user/super/com/google/gwt/emul/java/lang/Integer.java
+++ b/user/super/com/google/gwt/emul/java/lang/Integer.java
@@ -23,6 +23,7 @@
   public static final int MAX_VALUE = 0x7fffffff;
   public static final int MIN_VALUE = 0x80000000;
   public static final int SIZE = 32;
+  public static final int BYTES = SIZE / Byte.SIZE;
   public static final Class<Integer> TYPE = int.class;
 
   /**
@@ -71,11 +72,6 @@
     return Integer.valueOf(__decodeAndValidateInt(s, MIN_VALUE, MAX_VALUE));
   }
 
-  /**
-   * @skip
-   * 
-   * Here for shared implementation with Arrays.hashCode
-   */
   public static int hashCode(int i) {
     return i;
   }
@@ -98,6 +94,14 @@
     return i & -i;
   }
 
+  public static int max(int a, int b) {
+    return Math.max(a, b);
+  }
+
+  public static int min(int a, int b) {
+    return Math.min(a, b);
+  }
+
   public static int numberOfLeadingZeros(int i) {
     // Based on Henry S. Warren, Jr: "Hacker's Delight", p. 80.
     if (i < 0) {
@@ -197,6 +201,10 @@
     }
   }
 
+  public static int sum(int a, int b) {
+    return a + b;
+  }
+
   public static String toBinaryString(int value) {
     return toUnsignedRadixString(value, 2);
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/Long.java b/user/super/com/google/gwt/emul/java/lang/Long.java
index 7115944..0654fa4 100644
--- a/user/super/com/google/gwt/emul/java/lang/Long.java
+++ b/user/super/com/google/gwt/emul/java/lang/Long.java
@@ -31,6 +31,7 @@
   public static final long MAX_VALUE = 0x7fffffffffffffffL;
   public static final long MIN_VALUE = 0x8000000000000000L;
   public static final int SIZE = 64;
+  public static final int BYTES = SIZE / Byte.SIZE;
   public static final Class<Long> TYPE = long.class;
 
   public static int bitCount(long i) {
@@ -54,9 +55,6 @@
     return valueOf(decode.payload, decode.radix);
   }
 
-  /**
-   * @skip Here for shared implementation with Arrays.hashCode
-   */
   public static int hashCode(long l) {
     return (int) l;
   }
@@ -74,6 +72,14 @@
     return i & -i;
   }
 
+  public static long max(long a, long b) {
+    return Math.max(a, b);
+  }
+
+  public static long min(long a, long b) {
+    return Math.min(a, b);
+  }
+
   public static int numberOfLeadingZeros(long i) {
     int high = (int) (i >> 32);
     if (high != 0) {
@@ -145,6 +151,10 @@
     }
   }
 
+  public static long sum(long a, long b) {
+    return a + b;
+  }
+
   public static String toBinaryString(long value) {
     return toPowerOfTwoUnsignedString(value, 1);
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/Math.java b/user/super/com/google/gwt/emul/java/lang/Math.java
index fce3019..f996964 100644
--- a/user/super/com/google/gwt/emul/java/lang/Math.java
+++ b/user/super/com/google/gwt/emul/java/lang/Math.java
@@ -71,6 +71,20 @@
     return NativeMath.asin(x);
   }
 
+  public static int addExact(int x, int y) {
+    int r = x + y;
+    // "Hacker's Delight" 2-12 Overflow if both arguments have the opposite sign of the result
+    throwOverflowIf(((x ^ r) & (y ^ r)) < 0);
+    return r;
+  }
+
+  public static long addExact(long x, long y) {
+    long r = x + y;
+    // "Hacker's Delight" 2-12 Overflow if both arguments have the opposite sign of the result
+    throwOverflowIf(((x ^ r) & (y ^ r)) < 0);
+    return r;
+  }
+
   public static double atan(double x) {
     return NativeMath.atan(x);
   }
@@ -107,6 +121,16 @@
     return (Math.exp(x) + Math.exp(-x)) / 2.0;
   }
 
+  public static int decrementExact(int x) {
+    throwOverflowIf(x == Integer.MIN_VALUE);
+    return x - 1;
+  }
+
+  public static long decrementExact(long x) {
+    throwOverflowIf(x == Long.MIN_VALUE);
+    return x - 1;
+  }
+
   public static double exp(double x) {
     return NativeMath.exp(x);
   }
@@ -128,10 +152,48 @@
     return NativeMath.floor(x);
   }
 
+  public static int floorDiv(int dividend, int divisor) {
+    throwDivByZeroIf(divisor == 0);
+    int r = dividend / divisor;
+    // if the signs are different and modulo not zero, round down
+    if ((dividend ^ divisor) < 0 && (r * divisor != dividend)) {
+      r--;
+    }
+    return r;
+  }
+
+  public static long floorDiv(long dividend, long divisor) {
+    throwDivByZeroIf(divisor == 0);
+    long r = dividend / divisor;
+    // if the signs are different and modulo not zero, round down
+    if ((dividend ^ divisor) < 0 && (r * divisor != dividend)) {
+      r--;
+    }
+    return r;
+  }
+
+  public static int floorMod(int dividend, int divisor) {
+    return dividend - floorDiv(dividend, divisor) * divisor;
+  }
+
+  public static long floorMod(long dividend, long divisor) {
+    return dividend - floorDiv(dividend, divisor) * divisor;
+  }
+
   public static double hypot(double x, double y) {
     return sqrt(x * x + y * y);
   }
 
+  public static int incrementExact(int x) {
+    throwOverflowIf(x == Integer.MAX_VALUE);
+    return x + 1;
+  }
+
+  public static long incrementExact(long x) {
+    throwOverflowIf(x == Long.MAX_VALUE);
+    return x + 1;
+  }
+
   public static double log(double x) {
     return NativeMath.log(x);
   }
@@ -176,6 +238,29 @@
     return x < y ? x : y;
   }
 
+  public static int multiplyExact(int x, int y) {
+    long r = (long) x * (long) y;
+    int ir = (int) r;
+    throwOverflowIf(ir != r);
+    return ir;
+  }
+
+  public static long multiplyExact(long x, long y) {
+    long r = x * y;
+    throwOverflowIf((x == Long.MIN_VALUE && y == -1) || (y != 0 && (r / y != x)));
+    return r;
+  }
+
+  public static int negateExact(int x) {
+    throwOverflowIf(x == Integer.MIN_VALUE);
+    return -x;
+  }
+
+  public static long negateExact(long x) {
+    throwOverflowIf(x == Long.MIN_VALUE);
+    return -x;
+  }
+
   public static double pow(double x, double exp) {
     return NativeMath.pow(x, exp);
   }
@@ -209,6 +294,22 @@
     return d;
   }-*/;
 
+  public static int subtractExact(int x, int y) {
+    int r = x - y;
+    // "Hacker's Delight" Overflow if the arguments have different signs and
+    // the sign of the result is different than the sign of x
+    throwOverflowIf(((x ^ y) & (x ^ r)) < 0);
+    return r;
+  }
+
+  public static long subtractExact(long x, long y) {
+    long r = x - y;
+    // "Hacker's Delight" Overflow if the arguments have different signs and
+    // the sign of the result is different than the sign of x
+    throwOverflowIf(((x ^ y) & (x ^ r)) < 0);
+    return r;
+  }
+
   public static double scalb(double d, int scaleFactor) {
     if (scaleFactor >= 31 || scaleFactor <= -31) {
       return d * Math.pow(2, scaleFactor);
@@ -268,10 +369,28 @@
     return x * PI_UNDER_180;
   }
 
+  public static int toIntExact(long x) {
+    int ix = (int) x;
+    throwOverflowIf(ix != x);
+    return ix;
+  }
+
   public static double toRadians(double x) {
     return x * PI_OVER_180;
   }
 
+  private static void throwDivByZeroIf(boolean condition) {
+    if (condition) {
+      throw new ArithmeticException("div by zero");
+    }
+  }
+
+  private static void throwOverflowIf(boolean condition) {
+    if (condition) {
+      throw new ArithmeticException("overflow");
+    }
+  }
+
   @JsType(isNative = true, name = "Math", namespace = JsPackage.GLOBAL)
   private static class NativeMath {
     public static double LOG10E;
diff --git a/user/super/com/google/gwt/emul/java/lang/Runnable.java b/user/super/com/google/gwt/emul/java/lang/Runnable.java
index 31e49e5..4a51e4b 100644
--- a/user/super/com/google/gwt/emul/java/lang/Runnable.java
+++ b/user/super/com/google/gwt/emul/java/lang/Runnable.java
@@ -16,15 +16,16 @@
 package java.lang;
 
 /**
- * Encapsulates an action for later execution. <a
- * href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Runnable.html">[Sun
- * docs]</a>
- * 
+ * Encapsulates an action for later execution.
+ * See <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html">
+ * the official Java API doc</a> for details.
+ *
  * <p>
  * This interface is provided only for JRE compatibility. GWT does not support
  * multithreading.
  * </p>
  */
+@FunctionalInterface
 public interface Runnable {
   void run();
 }
diff --git a/user/super/com/google/gwt/emul/java/lang/Short.java b/user/super/com/google/gwt/emul/java/lang/Short.java
index 6b03f9c..c42d03c 100644
--- a/user/super/com/google/gwt/emul/java/lang/Short.java
+++ b/user/super/com/google/gwt/emul/java/lang/Short.java
@@ -23,6 +23,7 @@
   public static final short MIN_VALUE = (short) 0x8000;
   public static final short MAX_VALUE = (short) 0x7fff;
   public static final int SIZE = 16;
+  public static final int BYTES = SIZE / Byte.SIZE;
   public static final Class<Short> TYPE = short.class;
 
   /**
@@ -41,9 +42,6 @@
     return Short.valueOf((short) __decodeAndValidateInt(s, MIN_VALUE, MAX_VALUE));
   }
 
-  /**
-   * @skip Here for shared implementation with Arrays.hashCode
-   */
   public static int hashCode(short s) {
     return s;
   }
diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java
index 2a31e81..5e106c2 100644
--- a/user/super/com/google/gwt/emul/java/lang/String.java
+++ b/user/super/com/google/gwt/emul/java/lang/String.java
@@ -16,6 +16,7 @@
 
 package java.lang;
 
+import static javaemul.internal.InternalPreconditions.checkNotNull;
 import static javaemul.internal.InternalPreconditions.checkStringBounds;
 
 import java.io.Serializable;
@@ -24,6 +25,7 @@
 import java.nio.charset.UnsupportedCharsetException;
 import java.util.Comparator;
 import java.util.Locale;
+import java.util.StringJoiner;
 
 import javaemul.internal.ArrayHelper;
 import javaemul.internal.EmulatedCharset;
@@ -110,6 +112,26 @@
     return valueOf(v, offset, count);
   }
 
+  public static String join(CharSequence delimiter, CharSequence... elements) {
+    checkNotNull(delimiter, "delimiter");
+    checkNotNull(elements, "elements");
+    StringJoiner joiner = new StringJoiner(delimiter);
+    for (CharSequence e : elements) {
+      joiner.add(e);
+    }
+    return joiner.toString();
+  }
+
+  public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements) {
+    checkNotNull(delimiter, "delimiter");
+    checkNotNull(elements, "elements");
+    StringJoiner joiner = new StringJoiner(delimiter);
+    for (CharSequence e : elements) {
+      joiner.add(e);
+    }
+    return joiner.toString();
+  }
+
   public static String valueOf(boolean x) {
     return "" + x;
   }
diff --git a/user/super/com/google/gwt/emul/java/math/BigInteger.java b/user/super/com/google/gwt/emul/java/math/BigInteger.java
index 545b861..840cb3c 100644
--- a/user/super/com/google/gwt/emul/java/math/BigInteger.java
+++ b/user/super/com/google/gwt/emul/java/math/BigInteger.java
@@ -615,7 +615,7 @@
    *         does not fit in a {@code byte}.
    */
   public byte byteValueExact() {
-    if (numberLength <= 1 && bitLength() <= 7) {
+    if (numberLength <= 1 && bitLength() < Byte.SIZE) {
       return byteValue();
     }
     throw new ArithmeticException("out of byte range");
@@ -923,7 +923,7 @@
    *         does not fit in an {@code int}.
    */
   public int intValueExact() {
-    if (numberLength <= 1 && bitLength() <= 31) {
+    if (numberLength <= 1 && bitLength() < Integer.SIZE) {
       return intValue();
     }
     throw new ArithmeticException("out of int range");
@@ -965,7 +965,7 @@
    *         does not fit in a {@code long}.
    */
   public long longValueExact() {
-    if (numberLength <= 2 && bitLength() <= 63) {
+    if (numberLength <= 2 && bitLength() < Long.SIZE) {
       return longValue();
     }
     throw new ArithmeticException("out of long range");
@@ -1304,7 +1304,7 @@
    *         does not fit in a {@code short}.
    */
   public short shortValueExact() {
-    if (numberLength <= 1 && bitLength() <= 15) {
+    if (numberLength <= 1 && bitLength() < Short.SIZE) {
       return shortValue();
     }
     throw new ArithmeticException("out of short range");
diff --git a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
index 7fb15e5..8d84e2d 100644
--- a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
@@ -164,6 +164,38 @@
     assertFalse(Double.isInfinite(Double.NaN));
   }
 
+  public void testIsFinite() {
+    final double[] nonfiniteNumbers = {
+        Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN,
+    };
+    for (double value : nonfiniteNumbers) {
+      assertFalse(Double.isFinite(value));
+    }
+
+    final double[] finiteNumbers = {
+        -Double.MAX_VALUE, Double.MAX_VALUE, Double.MIN_VALUE,
+        -1.0, -0.5, -0.1, -0.0, 0.0, 0.1, 0.5, 1.0,
+    };
+    for (double value : finiteNumbers) {
+      assertTrue(Double.isFinite(value));
+    }
+  }
+
+  public void testIsInfinite() {
+    assertTrue(Double.isInfinite(Double.NEGATIVE_INFINITY));
+    assertTrue(Double.isInfinite(Double.POSITIVE_INFINITY));
+
+    assertFalse(Double.isInfinite(Double.NaN));
+
+    final double[] finiteNumbers = {
+        -Double.MAX_VALUE, Double.MAX_VALUE, Double.MIN_VALUE,
+        -1.0, -0.5, -0.1, -0.0, 0.0, 0.1, 0.5, 1.0,
+    };
+    for (double value : finiteNumbers) {
+      assertFalse(Double.isInfinite(value));
+    }
+  }
+
   public void testParse() {
     assertTrue(0 == Double.parseDouble("0"));
     assertTrue(100 == Double.parseDouble("1e2"));
diff --git a/user/test/com/google/gwt/emultest/java/lang/FloatTest.java b/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
index f12efcd..87dc276 100644
--- a/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
@@ -97,6 +97,38 @@
     assertFalse(Float.isInfinite(Float.NaN));
   }
 
+  public void testIsFinite() {
+    final float[] nonfiniteNumbers = {
+        Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NaN,
+    };
+    for (float value : nonfiniteNumbers) {
+      assertFalse(Float.isFinite(value));
+    }
+
+    final float[] finiteNumbers = {
+        -Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE,
+        -1.0f, -0.5f, -0.1f, -0.0f, 0.0f, 0.1f, 0.5f, 1.0f,
+    };
+    for (float value : finiteNumbers) {
+      assertTrue(Float.isFinite(value));
+    }
+  }
+
+  public void testIsInfinite() {
+    assertTrue(Float.isInfinite(Float.NEGATIVE_INFINITY));
+    assertTrue(Float.isInfinite(Float.POSITIVE_INFINITY));
+
+    assertFalse(Float.isInfinite(Float.NaN));
+
+    final float[] finiteNumbers = {
+        -Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE,
+        -1.0f, -0.5f, -0.1f, -0.0f, 0.0f, 0.1f, 0.5f, 1.0f,
+    };
+    for (float value : finiteNumbers) {
+      assertFalse(Float.isInfinite(value));
+    }
+  }
+
   public void testParse() {
     /*
      * Note: we must use appropriate deltas for a somewhat subtle reason.
diff --git a/user/test/com/google/gwt/emultest/java/lang/MathTest.java b/user/test/com/google/gwt/emultest/java/lang/MathTest.java
index 55d295a..9b8bb5d 100644
--- a/user/test/com/google/gwt/emultest/java/lang/MathTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/MathTest.java
@@ -18,6 +18,9 @@
 
 import com.google.gwt.junit.client.GWTTestCase;
 
+import java.math.BigInteger;
+import java.util.ArrayList;
+
 /**
  * Tests for JRE emulation of java.lang.Math.
  *
@@ -25,6 +28,9 @@
  */
 public class MathTest extends GWTTestCase {
 
+  private static final Integer[] ALL_INTEGER_CANDIDATES = getAllIntegerCandidates();
+  private static final Long[] ALL_LONG_CANDIDATES = getAllLongCandidates();
+
   private static boolean isNegativeZero(double x) {
     return Double.doubleToLongBits(-0.0) == Double.doubleToLongBits(x);
   }
@@ -58,6 +64,36 @@
     assertTrue(Double.isNaN(v));
   }
 
+  public void testAddExact() {
+    for (int a : ALL_INTEGER_CANDIDATES) {
+      for (int b : ALL_INTEGER_CANDIDATES) {
+        BigInteger expectedResult = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+        boolean expectedSuccess = fitsInInt(expectedResult);
+        try {
+          assertEquals(a + b, Math.addExact(a, b));
+          assertTrue(expectedSuccess);
+        } catch (ArithmeticException e) {
+          assertFalse(expectedSuccess);
+        }
+      }
+    }
+  }
+
+  public void testAddExactLongs() {
+    for (long a : ALL_LONG_CANDIDATES) {
+      for (long b : ALL_LONG_CANDIDATES) {
+        BigInteger expectedResult = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+        boolean expectedSuccess = fitsInLong(expectedResult);
+        try {
+          assertEquals(a + b, Math.addExact(a, b));
+          assertTrue(expectedSuccess);
+        } catch (ArithmeticException e) {
+          assertFalse(expectedSuccess);
+        }
+      }
+    }
+  }
+
   public void testCbrt() {
     double v = Math.cbrt(1000.0);
     assertEquals(10.0, v, 1e-7);
@@ -97,6 +133,120 @@
     assertEquals(Double.POSITIVE_INFINITY, v);
   }
 
+  public void testDecrementExact() {
+    for (int a : ALL_INTEGER_CANDIDATES) {
+      BigInteger expectedResult = BigInteger.valueOf(a).subtract(BigInteger.ONE);
+      boolean expectedSuccess = fitsInInt(expectedResult);
+      try {
+        assertEquals(a - 1, Math.decrementExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
+  public void testDecrementExactLong() {
+    for (long a : ALL_LONG_CANDIDATES) {
+      BigInteger expectedResult = BigInteger.valueOf(a).subtract(BigInteger.ONE);
+      boolean expectedSuccess = fitsInLong(expectedResult);
+      try {
+        assertEquals(a - 1, Math.decrementExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
+  public void testFloorDiv() {
+    assertEquals(0, Math.floorDiv(0, 1));
+    assertEquals(1, Math.floorDiv(4, 3));
+    assertEquals(-2, Math.floorDiv(4, -3));
+    assertEquals(-2, Math.floorDiv(-4, 3));
+    assertEquals(1, Math.floorDiv(-4, -3));
+
+    // special case
+    assertEquals(Integer.MIN_VALUE, Math.floorDiv(Integer.MIN_VALUE, -1));
+
+    try {
+      Math.floorDiv(1, 0);
+      fail();
+    } catch (ArithmeticException expected) {
+    }
+  }
+
+  public void testFloorDivLongs() {
+    assertEquals(0L, Math.floorDiv(0L, 1L));
+    assertEquals(1L, Math.floorDiv(4L, 3L));
+    assertEquals(-2L, Math.floorDiv(4L, -3L));
+    assertEquals(-2L, Math.floorDiv(-4L, 3L));
+    assertEquals(1L, Math.floorDiv(-4L, -3L));
+
+    // special case
+    assertEquals(Long.MIN_VALUE, Math.floorDiv(Long.MIN_VALUE, -1));
+
+    try {
+      Math.floorDiv(1L, 0L);
+      fail();
+    } catch (ArithmeticException expected) {
+    }
+  }
+
+  public void testFloorMod() {
+    assertEquals(0, Math.floorMod(0, 1));
+    assertEquals(1, Math.floorMod(4, 3));
+    assertEquals(-2, Math.floorMod(4, -3));
+    assertEquals(2, Math.floorMod(-4, 3));
+    assertEquals(-1, Math.floorMod(-4, -3));
+
+    try {
+      Math.floorMod(1, 0);
+      fail();
+    } catch (ArithmeticException expected) {
+    }
+  }
+
+  public void testFloorModLongs() {
+    assertEquals(0L, Math.floorMod(0L, 1L));
+    assertEquals(1L, Math.floorMod(4L, 3L));
+    assertEquals(-2L, Math.floorMod(4L, -3L));
+    assertEquals(2L, Math.floorMod(-4L, 3L));
+    assertEquals(-1L, Math.floorMod(-4L, -3L));
+
+    try {
+      Math.floorMod(1L, 0L);
+      fail();
+    } catch (ArithmeticException expected) {
+    }
+  }
+
+  public void testIncrementExact() {
+    for (int a : ALL_INTEGER_CANDIDATES) {
+      BigInteger expectedResult = BigInteger.valueOf(a).add(BigInteger.ONE);
+      boolean expectedSuccess = fitsInInt(expectedResult);
+      try {
+        assertEquals(a + 1, Math.incrementExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
+  public void testIncrementExactLong() {
+    for (long a : ALL_LONG_CANDIDATES) {
+      BigInteger expectedResult = BigInteger.valueOf(a).add(BigInteger.ONE);
+      boolean expectedSuccess = fitsInLong(expectedResult);
+      try {
+        assertEquals(a + 1, Math.incrementExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
   public void testMax() {
     assertEquals(2d, Math.max(1d, 2d));
     assertEquals(2d, Math.max(2d, 1d));
@@ -171,6 +321,62 @@
     assertEquals(3.0, v, 1e-15);
   }
 
+  public void testMultiplyExact() {
+    for (int a : ALL_INTEGER_CANDIDATES) {
+      for (int b : ALL_INTEGER_CANDIDATES) {
+        BigInteger expectedResult = BigInteger.valueOf(a).multiply(BigInteger.valueOf(b));
+        boolean expectedSuccess = fitsInInt(expectedResult);
+        try {
+          assertEquals(a * b, Math.multiplyExact(a, b));
+          assertTrue(expectedSuccess);
+        } catch (ArithmeticException e) {
+          assertFalse(expectedSuccess);
+        }
+      }
+    }
+  }
+
+  public void testMultiplyExactLongs() {
+    for (long a : ALL_LONG_CANDIDATES) {
+      for (long b : ALL_LONG_CANDIDATES) {
+        BigInteger expectedResult = BigInteger.valueOf(a).multiply(BigInteger.valueOf(b));
+        boolean expectedSuccess = fitsInLong(expectedResult);
+        try {
+          assertEquals(a * b, Math.multiplyExact(a, b));
+          assertTrue(expectedSuccess);
+        } catch (ArithmeticException e) {
+          assertFalse(expectedSuccess);
+        }
+      }
+    }
+  }
+
+  public void testNegateExact() {
+    for (int a : ALL_INTEGER_CANDIDATES) {
+      BigInteger expectedResult = BigInteger.valueOf(a).negate();
+      boolean expectedSuccess = fitsInInt(expectedResult);
+      try {
+        assertEquals(-a, Math.negateExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
+  public void testNegateExactLong() {
+    for (long a : ALL_LONG_CANDIDATES) {
+      BigInteger expectedResult = BigInteger.valueOf(a).negate();
+      boolean expectedSuccess = fitsInLong(expectedResult);
+      try {
+        assertEquals(-a, Math.negateExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
   public void testSin() {
     double v = Math.sin(0.0);
     assertEquals(0.0, v, 1e-7);
@@ -254,4 +460,97 @@
     assertEquals(4294967296.0f, Math.scalb(1f, 32));
     assertEquals(2.3283064e-10f, Math.scalb(1f, -32), 1e-7f);
   }
+
+  public void testSubtractExact() {
+    for (int a : ALL_INTEGER_CANDIDATES) {
+      for (int b : ALL_INTEGER_CANDIDATES) {
+        BigInteger expectedResult = BigInteger.valueOf(a).subtract(BigInteger.valueOf(b));
+        boolean expectedSuccess = fitsInInt(expectedResult);
+        try {
+          assertEquals(a - b, Math.subtractExact(a, b));
+          assertTrue(expectedSuccess);
+        } catch (ArithmeticException e) {
+          assertFalse(expectedSuccess);
+        }
+      }
+    }
+  }
+
+  public void testSubtractExactLongs() {
+    for (long a : ALL_LONG_CANDIDATES) {
+      for (long b : ALL_LONG_CANDIDATES) {
+        BigInteger expectedResult = BigInteger.valueOf(a).subtract(BigInteger.valueOf(b));
+        boolean expectedSuccess = fitsInLong(expectedResult);
+        try {
+          assertEquals(a - b, Math.subtractExact(a, b));
+          assertTrue(expectedSuccess);
+        } catch (ArithmeticException e) {
+          assertFalse(expectedSuccess);
+        }
+      }
+    }
+  }
+
+  public void testToIntExact() {
+    final long[] longs = {0, -1, 1, Integer.MIN_VALUE, Integer.MAX_VALUE,
+        Integer.MIN_VALUE - 1L, Integer.MAX_VALUE + 1L, Long.MIN_VALUE, Long.MAX_VALUE};
+    for (long a : longs) {
+      boolean expectedSuccess = (int) a == a;
+      try {
+        assertEquals((int) a, Math.toIntExact(a));
+        assertTrue(expectedSuccess);
+      } catch (ArithmeticException e) {
+        assertFalse(expectedSuccess);
+      }
+    }
+  }
+
+  private static boolean fitsInInt(BigInteger big) {
+    return big.bitLength() < Integer.SIZE;
+  }
+
+  private static boolean fitsInLong(BigInteger big) {
+    return big.bitLength() < Long.SIZE;
+  }
+
+  private static Integer[] getAllIntegerCandidates() {
+    ArrayList<Integer> candidates = new ArrayList<Integer>();
+    candidates.add(0);
+    candidates.add(-1);
+    candidates.add(1);
+    candidates.add(Integer.MAX_VALUE / 2);
+    candidates.add(Integer.MAX_VALUE / 2 - 1);
+    candidates.add(Integer.MAX_VALUE / 2 + 1);
+    candidates.add(Integer.MIN_VALUE / 2);
+    candidates.add(Integer.MIN_VALUE / 2 - 1);
+    candidates.add(Integer.MIN_VALUE / 2 + 1);
+    candidates.add(Integer.MAX_VALUE - 1);
+    candidates.add(Integer.MAX_VALUE);
+    candidates.add(Integer.MIN_VALUE + 1);
+    candidates.add(Integer.MIN_VALUE);
+    return candidates.toArray(new Integer[candidates.size()]);
+  }
+
+  private static Long[] getAllLongCandidates() {
+    ArrayList<Long> candidates = new ArrayList<Long>();
+
+    for (Integer x : getAllIntegerCandidates()) {
+      candidates.add(x.longValue());
+    }
+
+    candidates.add(Long.MAX_VALUE / 2);
+    candidates.add(Long.MAX_VALUE / 2 - 1);
+    candidates.add(Long.MAX_VALUE / 2 + 1);
+    candidates.add(Long.MIN_VALUE / 2);
+    candidates.add(Long.MIN_VALUE / 2 - 1);
+    candidates.add(Long.MIN_VALUE / 2 + 1);
+    candidates.add(Integer.MAX_VALUE + 1L);
+    candidates.add(Long.MAX_VALUE - 1L);
+    candidates.add(Long.MAX_VALUE);
+    candidates.add(Integer.MIN_VALUE - 1L);
+    candidates.add(Long.MIN_VALUE + 1L);
+    candidates.add(Long.MIN_VALUE);
+
+    return candidates.toArray(new Long[candidates.size()]);
+  }
 }
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringTest.java b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
index b9f06aa..e897f9d 100644
--- a/user/test/com/google/gwt/emultest/java/lang/StringTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
@@ -20,6 +20,7 @@
 
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.util.Arrays;
 import java.util.Locale;
 
 /**
@@ -460,6 +461,24 @@
     assertSame("interns are not the same reference", s1.intern(), s2.intern());
   }
 
+  public void testJoin() {
+    assertEquals("", String.join("", ""));
+    assertEquals("", String.join(",", ""));
+    assertEquals("", String.join(",", Arrays.<String>asList()));
+
+    assertEquals("a", String.join("", "a"));
+    assertEquals("a", String.join(",", "a"));
+    assertEquals("a", String.join(",", Arrays.asList("a")));
+
+    assertEquals("ab", String.join("", "a", "b"));
+    assertEquals("a,b", String.join(",", "a", "b"));
+    assertEquals("a,b", String.join(",", Arrays.asList("a", "b")));
+
+    assertEquals("abc", String.join("", "a", "b", "c"));
+    assertEquals("a,b,c", String.join(",", "a", "b", "c"));
+    assertEquals("a,b,c", String.join(",", Arrays.asList("a", "b", "c")));
+  }
+
   public void testLastIndexOf() {
     String x = "abcdeabcdef";
     assertEquals(9, x.lastIndexOf("e"));