Merge "Fix regression in Vector.listIterator.add() introduced in N."
diff --git a/benchmarks/src/benchmarks/regression/SimpleDateFormatBenchmark.java b/benchmarks/src/benchmarks/regression/SimpleDateFormatBenchmark.java
new file mode 100644
index 0000000..b9becc7
--- /dev/null
+++ b/benchmarks/src/benchmarks/regression/SimpleDateFormatBenchmark.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 benchmarks.regression;
+
+import android.icu.text.TimeZoneNames;
+
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Benchmark for java.text.SimpleDateFormat. This tests common formatting, parsing and creation
+ * operations with a specific focus on TimeZone handling.
+ */
+public class SimpleDateFormatBenchmark {
+    public void time_createFormatWithTimeZone(int reps) {
+        for (int i = 0; i < reps; i++) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd z");
+        }
+    }
+
+    public void time_parseWithTimeZoneShort(int reps) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd z");
+        for (int i = 0; i < reps; i++) {
+            sdf.parse("2000.01.01 PST");
+        }
+    }
+
+    public void time_parseWithTimeZoneLong(int reps) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd zzzz");
+        for (int i = 0; i < reps; i++) {
+            sdf.parse("2000.01.01 Pacific Standard Time");
+        }
+    }
+
+    public void time_parseWithoutTimeZone(int reps) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
+        for (int i = 0; i < reps; i++) {
+            sdf.parse("2000.01.01");
+        }
+    }
+
+    public void time_createAndParseWithTimeZoneShort(int reps) throws ParseException {
+        for (int i = 0; i < reps; i++) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd z");
+            sdf.parse("2000.01.01 PST");
+        }
+    }
+
+    public void time_createAndParseWithTimeZoneLong(int reps) throws ParseException {
+        for (int i = 0; i < reps; i++) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd zzzz");
+            sdf.parse("2000.01.01 Pacific Standard Time");
+        }
+    }
+
+    public void time_formatWithTimeZoneShort(int reps) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd z");
+        for (int i = 0; i < reps; i++) {
+            sdf.format(new Date());
+        }
+    }
+
+    public void time_formatWithTimeZoneLong(int reps) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd zzzz");
+        for (int i = 0; i < reps; i++) {
+            sdf.format(new Date());
+        }
+    }
+
+    /**
+     * Times first-time execution to measure effects of initial loading of data that's lost in
+     * full caliper benchmarks.
+     */
+    public static void main(String[] args) throws ParseException {
+        long start, end;
+
+        Locale locale = Locale.GERMAN;
+        start = System.nanoTime();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd zzzz", locale);
+        end = System.nanoTime();
+        System.out.printf("Creating first SDF: %,d ns\n", end-start);
+
+        // N code had special cases for currently-set and for default timezone. We want to measure
+        // the generic case.
+        sdf.setTimeZone(TimeZone.getTimeZone("Hongkong"));
+
+        start = System.nanoTime();
+        sdf.parse("2000.1.1 Kubanische Normalzeit");
+        end = System.nanoTime();
+        System.out.printf("First parse: %,d ns\n", end-start);
+
+        start = System.nanoTime();
+        sdf.format(new Date());
+        end = System.nanoTime();
+        System.out.printf("First format: %,d ns\n", end-start);
+    }
+}
diff --git a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
index 699024c..a8764c2 100644
--- a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
+++ b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
@@ -222,9 +222,11 @@
                 Collections.class, "sort", List.class, Comparator.class);
 
         // Java 8 language addition: default interface method.
-        checkToString(
-                "public default java.util.function.Function java.util.function.Function.compose(java.util.function.Function)",
-                Function.class, "compose", Function.class);
+        // Commented until http://b/28666126 is complete: toString() for default methods not
+        // implemented yet.
+        // checkToString(
+        //        "public default java.util.function.Function java.util.function.Function.compose(java.util.function.Function)",
+        //        Function.class, "compose", Function.class);
         // Java 8 language addition: static interface method.
         checkToString(
                 "public static java.util.function.Function java.util.function.Function.identity()",
@@ -260,9 +262,11 @@
 
 
         // Java 8 language addition: default interface method.
-        checkToGenericString(
-                "public default <V> java.util.function.Function<V, R> java.util.function.Function.compose(java.util.function.Function<? super V, ? extends T>)",
-                Function.class, "compose", Function.class);
+        // Commented until http://b/28666126 is complete: toGenericString() for default methods not
+        // implemented yet.
+        // checkToGenericString(
+        //        "public default <V> java.util.function.Function<V, R> java.util.function.Function.compose(java.util.function.Function<? super V, ? extends T>)",
+        //        Function.class, "compose", Function.class);
         // Java 8 language addition: static interface method.
         checkToGenericString(
                 "public static <T> java.util.function.Function<T, T> java.util.function.Function.identity()",
diff --git a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
index eb3c8fe..faf4213 100644
--- a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
@@ -20,6 +20,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.lang.reflect.Field;
 import java.text.DateFormatSymbols;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -165,7 +166,7 @@
     public void test_setZoneStrings_checks_dimensions() throws Exception {
         DateFormatSymbols dfs = DateFormatSymbols.getInstance();
         String[][] zoneStrings = dfs.getZoneStrings();
-        zoneStrings[0] = new String[] { "id_only "};
+        zoneStrings[0] = new String[] { "id_only " };
         try {
             dfs.setZoneStrings(zoneStrings);
             fail("No IllegalArgumentException when setting incorrect zoneStrings");
@@ -173,4 +174,31 @@
             // expected
         }
     }
+
+    public void test_zoneStrings_are_lazy() throws Exception {
+        DateFormatSymbols dfs = DateFormatSymbols.getInstance();
+
+        assertFalse("Newly created DFS should have no zoneStrings", hasZoneStringsFieldValue(dfs));
+        dfs.hashCode();
+        assertFalse("hashCode() should not need zoneStrings", hasZoneStringsFieldValue(dfs));
+        DateFormatSymbols otherDfs = DateFormatSymbols.getInstance();
+        dfs.equals(otherDfs);
+        assertFalse("equals() should usually not need zoneStrings", hasZoneStringsFieldValue(dfs));
+        otherDfs.getZoneStrings();
+        assertTrue("getZoneStrings() needs zoneStrings", hasZoneStringsFieldValue(otherDfs));
+        otherDfs.setZoneStrings(otherDfs.getZoneStrings());
+        dfs.equals(otherDfs);
+        assertTrue("equals() needs zoneStrings when other object has user-provided values",
+                hasZoneStringsFieldValue(dfs));
+    }
+
+    /**
+     * Return {@code true} iff {@code dfs} has a non-null {@code zoneStrings}. This introspection is
+     * necessary, because as a lazy field it having a value should not otherwise be observable.
+     */
+    private static boolean hasZoneStringsFieldValue(DateFormatSymbols dfs) throws Exception {
+        Field field = DateFormatSymbols.class.getDeclaredField("zoneStrings");
+        field.setAccessible(true);
+        return field.get(dfs) != null;
+    }
 }
diff --git a/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java b/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
index a001104..d9656a0 100644
--- a/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
@@ -453,6 +453,20 @@
         assertEquals(tz, df.getTimeZone());
     }
 
+    public void testZoneStringsUsedForParsingWhenPresent() throws ParseException {
+        DateFormatSymbols symbols = DateFormatSymbols.getInstance(Locale.ENGLISH);
+        String[][] zoneStrings = symbols.getZoneStrings();
+        TimeZone tz = TimeZone.getTimeZone(zoneStrings[0][0]);
+        zoneStrings[0][1] = "CustomTimeZone";
+        symbols.setZoneStrings(zoneStrings);
+
+        SimpleDateFormat sdf = new SimpleDateFormat("dd MM yyyy HH:mm zzz", symbols);
+
+        Date gmtDate = sdf.parse("1 1 2000 12:00 GMT");
+        Date customDate = sdf.parse("1 1 2000 12:00 CustomTimeZone");
+        assertEquals(tz.getOffset(gmtDate.getTime()), customDate.getTime() - gmtDate.getTime());
+    }
+
     public void testTimeZoneFormattingRespectsSetZoneStrings() throws ParseException {
         DateFormatSymbols symbols = DateFormatSymbols.getInstance(Locale.ENGLISH);
         String[][] zoneStrings = symbols.getZoneStrings();
diff --git a/ojluni/src/main/java/java/text/DateFormatSymbols.java b/ojluni/src/main/java/java/text/DateFormatSymbols.java
index 95626af..c617a18 100644
--- a/ojluni/src/main/java/java/text/DateFormatSymbols.java
+++ b/ojluni/src/main/java/java/text/DateFormatSymbols.java
@@ -47,6 +47,7 @@
 import java.text.spi.DateFormatSymbolsProvider;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -485,6 +486,7 @@
      */
     public void setEras(String[] newEras) {
         eras = Arrays.copyOf(newEras, newEras.length);
+        cachedHashCode = 0;
     }
 
     /**
@@ -501,6 +503,7 @@
      */
     public void setMonths(String[] newMonths) {
         months = Arrays.copyOf(newMonths, newMonths.length);
+        cachedHashCode = 0;
     }
 
     /**
@@ -517,6 +520,7 @@
      */
     public void setShortMonths(String[] newShortMonths) {
         shortMonths = Arrays.copyOf(newShortMonths, newShortMonths.length);
+        cachedHashCode = 0;
     }
 
     /**
@@ -536,6 +540,7 @@
      */
     public void setWeekdays(String[] newWeekdays) {
         weekdays = Arrays.copyOf(newWeekdays, newWeekdays.length);
+        cachedHashCode = 0;
     }
 
     /**
@@ -555,6 +560,7 @@
      */
     public void setShortWeekdays(String[] newShortWeekdays) {
         shortWeekdays = Arrays.copyOf(newShortWeekdays, newShortWeekdays.length);
+        cachedHashCode = 0;
     }
 
     /**
@@ -571,6 +577,7 @@
      */
     public void setAmPmStrings(String[] newAmpms) {
         ampms = Arrays.copyOf(newAmpms, newAmpms.length);
+        cachedHashCode = 0;
     }
 
     /**
@@ -655,6 +662,8 @@
         }
         zoneStrings = aCopy;
         isZoneStringsSet = true;
+        // Android changed: don't include zone strings in hashCode to avoid populating it.
+        // cachedHashCode = 0;
     }
 
     /**
@@ -673,6 +682,7 @@
     public void setLocalPatternChars(String newLocalPatternChars) {
         // Call toString() to throw an NPE in case the argument is null
         localPatternChars = newLocalPatternChars.toString();
+        cachedHashCode = 0;
     }
 
     String[] getTinyMonths() {
@@ -728,11 +738,22 @@
      */
     @Override
     public int hashCode() {
-        int hashcode = 0;
-        String[][] zoneStrings = getZoneStringsWrapper();
-        for (int index = 0; index < zoneStrings[0].length; ++index)
-            hashcode ^= zoneStrings[0][index].hashCode();
-        return hashcode;
+        int hashCode = cachedHashCode;
+        if (hashCode == 0) {
+            hashCode = 5;
+            hashCode = 11 * hashCode + Arrays.hashCode(eras);
+            hashCode = 11 * hashCode + Arrays.hashCode(months);
+            hashCode = 11 * hashCode + Arrays.hashCode(shortMonths);
+            hashCode = 11 * hashCode + Arrays.hashCode(weekdays);
+            hashCode = 11 * hashCode + Arrays.hashCode(shortWeekdays);
+            hashCode = 11 * hashCode + Arrays.hashCode(ampms);
+            // Android changed: Don't include zone strings in hashCode to avoid populating it.
+            // hashCode = 11 * hashCode + Arrays.deepHashCode(getZoneStringsWrapper());
+            hashCode = 11 * hashCode + Objects.hashCode(localPatternChars);
+            cachedHashCode = hashCode;
+        }
+
+        return hashCode;
     }
 
     /**
@@ -743,7 +764,7 @@
         if (this == obj) return true;
         if (obj == null || getClass() != obj.getClass()) return false;
         DateFormatSymbols that = (DateFormatSymbols) obj;
-        return (Arrays.equals(eras, that.eras)
+        if (!(Arrays.equals(eras, that.eras)
                 && Arrays.equals(months, that.months)
                 && Arrays.equals(shortMonths, that.shortMonths)
                 && Arrays.equals(tinyMonths, that.tinyMonths)
@@ -757,11 +778,17 @@
                 && Arrays.equals(shortStandAloneWeekdays, that.shortStandAloneWeekdays)
                 && Arrays.equals(tinyStandAloneWeekdays, that.tinyStandAloneWeekdays)
                 && Arrays.equals(ampms, that.ampms)
-                && Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper())
                 && ((localPatternChars != null
                   && localPatternChars.equals(that.localPatternChars))
                  || (localPatternChars == null
-                  && that.localPatternChars == null)));
+                  && that.localPatternChars == null)))) {
+            return false;
+        }
+        // Android changed: Avoid populating zoneStrings just for the comparison.
+        if (!isZoneStringsSet && !that.isZoneStringsSet && Objects.equals(locale, that.locale)) {
+            return true;
+        }
+        return Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper());
     }
 
     // =======================privates===============================
@@ -779,6 +806,11 @@
 
     private transient int lastZoneIndex = 0;
 
+    /**
+     * Cached hash code
+     */
+    transient volatile int cachedHashCode = 0;
+
     private void initializeData(Locale desiredLocale) {
         locale = desiredLocale;
 
@@ -936,6 +968,7 @@
             dst.zoneStrings = null;
         }
         dst.localPatternChars = src.localPatternChars;
+        dst.cachedHashCode = 0;
 
         dst.tinyMonths = src.tinyMonths;
         dst.tinyWeekdays = src.tinyWeekdays;
diff --git a/ojluni/src/main/java/sun/nio/cs/StreamDecoder.java b/ojluni/src/main/java/sun/nio/cs/StreamDecoder.java
index 9873f60..03a9506 100644
--- a/ojluni/src/main/java/sun/nio/cs/StreamDecoder.java
+++ b/ojluni/src/main/java/sun/nio/cs/StreamDecoder.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -114,6 +114,7 @@
         return read0();
     }
 
+    @SuppressWarnings("fallthrough")
     private int read0() throws IOException {
         synchronized (lock) {
 
diff --git a/ojluni/src/main/java/sun/nio/cs/ThreadLocalCoders.java b/ojluni/src/main/java/sun/nio/cs/ThreadLocalCoders.java
index 18d3393..b7ecd6b 100644
--- a/ojluni/src/main/java/sun/nio/cs/ThreadLocalCoders.java
+++ b/ojluni/src/main/java/sun/nio/cs/ThreadLocalCoders.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,7 +26,6 @@
 
 package sun.nio.cs;
 
-import java.nio.*;
 import java.nio.charset.*;
 
 
@@ -41,7 +40,7 @@
     private static abstract class Cache {
 
         // Thread-local reference to array of cached objects, in LRU order
-        private ThreadLocal cache = new ThreadLocal();
+        private ThreadLocal<Object[]> cache = new ThreadLocal<>();
         private final int size;
 
         Cache(int size) {
@@ -60,7 +59,7 @@
         abstract boolean hasName(Object ob, Object name);
 
         Object forName(Object name) {
-            Object[] oa = (Object[])cache.get();
+            Object[] oa = cache.get();
             if (oa == null) {
                 oa = new Object[size];
                 cache.set(oa);