am 9500bd1f: Remove StaleDexCacheError.

* commit '9500bd1ffe150e8836ea649ea356c4d1f3a62018':
  Remove StaleDexCacheError.
diff --git a/benchmarks/src/benchmarks/XmlSerializeBenchmark.java b/benchmarks/src/benchmarks/XmlSerializeBenchmark.java
new file mode 100644
index 0000000..7319490
--- /dev/null
+++ b/benchmarks/src/benchmarks/XmlSerializeBenchmark.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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 benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import org.xmlpull.v1.*;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Random;
+
+
+public class XmlSerializeBenchmark extends SimpleBenchmark {
+
+    @Param( {"0.99 0.7 0.7 0.7 0.7 0.7",
+            "0.999 0.3 0.3 0.95 0.9 0.9"})
+    String datasetAsString;
+
+    @Param( { "854328", "312547"} )
+    int seed;
+
+    double[] dataset;
+    private Constructor<? extends XmlSerializer> kxmlConstructor;
+    private Constructor<? extends XmlSerializer> fastConstructor;
+
+    private void serializeRandomXml(Constructor<? extends XmlSerializer> ctor, long seed)
+            throws Exception {
+        double contChance = dataset[0];
+        double levelUpChance = dataset[1];
+        double levelDownChance = dataset[2];
+        double attributeChance = dataset[3];
+        double writeChance1 = dataset[4];
+        double writeChance2 = dataset[5];
+
+        XmlSerializer serializer = (XmlSerializer) ctor.newInstance();
+
+        CharArrayWriter w = new CharArrayWriter();
+        serializer.setOutput(w);
+        int level = 0;
+        Random r = new Random(seed);
+        char[] toWrite = {'a','b','c','d','s','z'};
+        serializer.startDocument("UTF-8", true);
+        while(r.nextDouble() < contChance) {
+            while(level > 0 && r.nextDouble() < levelUpChance) {
+                serializer.endTag("aaaaaa", "bbbbbb");
+                level--;
+            }
+            while(r.nextDouble() < levelDownChance) {
+                serializer.startTag("aaaaaa", "bbbbbb");
+                level++;
+            }
+            serializer.startTag("aaaaaa", "bbbbbb");
+            level++;
+            while(r.nextDouble() < attributeChance) {
+                serializer.attribute("aaaaaa", "cccccc", "dddddd");
+            }
+            serializer.endTag("aaaaaa", "bbbbbb");
+            level--;
+            while(r.nextDouble() < writeChance1)
+                serializer.text(toWrite, 0, 5);
+            while(r.nextDouble() < writeChance2)
+                serializer.text("Textxtsxtxtxt ");
+        }
+        serializer.endDocument();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected void setUp() throws Exception {
+        kxmlConstructor = (Constructor) Class.forName("org.kxml2.io.KXmlSerializer")
+                .getConstructor();
+        fastConstructor = (Constructor) Class.forName("com.android.internal.util.FastXmlSerializer")
+                .getConstructor();
+        String[] splitted = datasetAsString.split(" ");
+        dataset = new double[splitted.length];
+        for (int i = 0; i < splitted.length; i++) {
+            dataset[i] = Double.valueOf(splitted[i]);
+        }
+    }
+
+    private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor, int reps)
+            throws Exception {
+        for (int i = 0; i < reps; i++) {
+            serializeRandomXml(ctor, seed);
+        }
+    }
+
+    public void timeKxml(int reps) throws Exception {
+        internalTimeSerializer(kxmlConstructor, reps);
+    }
+
+    public void timeFast(int reps) throws Exception {
+        internalTimeSerializer(fastConstructor, reps);
+    }
+
+    public static void main(String[] args) {
+        Runner.main(XmlSerializeBenchmark.class, args);
+    }
+}
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index b1ff1c8..4bfc808 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -19,14 +19,22 @@
 import android.system.ErrnoException;
 import android.system.StructStat;
 import java.io.File;
+import java.io.FilterInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
@@ -410,6 +418,7 @@
         private final DexFile dexFile;
 
         private ZipFile zipFile;
+        private URLStreamHandler urlHandler;
         private boolean initialized;
 
         public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
@@ -442,6 +451,7 @@
 
             try {
                 zipFile = new ZipFile(zip);
+                urlHandler = new ElementURLStreamHandler(zipFile);
             } catch (IOException ioe) {
                 /*
                  * Note: ZipException (a subclass of IOException)
@@ -481,16 +491,114 @@
 
             try {
                 /*
-                 * File.toURL() is compliant with RFC 1738 in
+                 * File.toURI() is compliant with RFC 1738 in
                  * always creating absolute path names. If we
                  * construct the URL by concatenating strings, we
                  * might end up with illegal URLs for relative
                  * names.
                  */
-                return new URL("jar:" + file.toURL() + "!/" + name);
+                URI fileUri = file.toURI();
+                return new URL("jar", null, -1, fileUri.toString() + "!/" + name, urlHandler);
             } catch (MalformedURLException ex) {
                 throw new RuntimeException(ex);
             }
         }
+
+        /**
+         * URLStreamHandler for an Element. Avoids the need to open a .jar file again to read
+         * resources.
+         */
+        private static class ElementURLStreamHandler extends URLStreamHandler {
+
+            private final ZipFile zipFile;
+
+            public ElementURLStreamHandler(ZipFile zipFile) {
+                this.zipFile = zipFile;
+            }
+
+            @Override
+            protected URLConnection openConnection(URL url) throws IOException {
+                return new ElementJarURLConnection(url, zipFile);
+            }
+        }
+
+        /**
+         * A JarURLConnection that is backed by a ZipFile held open by an {@link Element}. For
+         * backwards compatibility it extends JarURLConnection even though it's not actually backed
+         * by a {@link JarFile}.
+         */
+        private static class ElementJarURLConnection extends JarURLConnection {
+
+            private final ZipFile zipFile;
+            private final ZipEntry zipEntry;
+
+            private InputStream jarInput;
+            private boolean closed;
+            private JarFile jarFile;
+
+            public ElementJarURLConnection(URL url, ZipFile zipFile) throws MalformedURLException {
+                super(url);
+                this.zipFile = zipFile;
+                this.zipEntry = zipFile.getEntry(getEntryName());
+                if (zipEntry == null) {
+                    throw new MalformedURLException(
+                            "URL does not correspond to an entry in the zip file. URL=" + url
+                                    + ", zipfile=" + zipFile.getName());
+                }
+            }
+
+            @Override
+            public void connect() {
+                connected = true;
+            }
+
+            @Override
+            public JarFile getJarFile() throws IOException {
+                // This is expensive because we only pretend that we wrap a JarFile.
+                if (jarFile == null) {
+                    jarFile = new JarFile(zipFile.getName());
+                }
+                return jarFile;
+            }
+
+            @Override
+            public InputStream getInputStream() throws IOException {
+                if (closed) {
+                    throw new IllegalStateException("JarURLConnection InputStream has been closed");
+                }
+                connect();
+                if (jarInput != null) {
+                    return jarInput;
+                }
+                return jarInput = new FilterInputStream(zipFile.getInputStream(zipEntry)) {
+                    @Override
+                    public void close() throws IOException {
+                        super.close();
+                        closed = true;
+                    }
+                };
+            }
+
+            /**
+             * Returns the content type of the entry based on the name of the entry. Returns
+             * non-null results ("content/unknown" for unknown types).
+             *
+             * @return the content type
+             */
+            @Override
+            public String getContentType() {
+                String cType = guessContentTypeFromName(getEntryName());
+                if (cType == null) {
+                    cType = "content/unknown";
+                }
+                return cType;
+            }
+
+            @Override
+            public int getContentLength() {
+                connect();
+                return (int) zipEntry.getSize();
+            }
+        }
     }
 }
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index c23deff..a3e9197 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1462,6 +1462,14 @@
   ]
 },
 {
+  description: "OkHttp tests that fail on Wear devices due to a lack of memory",
+  bug: 20055487,
+  names: [
+    "com.squareup.okhttp.internal.spdy.Http20Draft09Test#tooLargeDataFrame",
+    "com.squareup.okhttp.internal.spdy.Spdy3Test#tooLargeDataFrame"
+  ]
+},
+{
   description: "libcore.java.text.DecimalFormatSymbolsTest#test_getInstance_unknown_or_invalid_locale assumes fallback to locale other than en_US_POSIX.",
   bug: 17374604,
   names: [
@@ -1501,5 +1509,13 @@
     "libcore.java.util.zip.Zip64FileTest#testZip64Support_totalLargerThan4G",
     "libcore.java.util.zip.Zip64FileTest#testZip64Support_hugeEntry"
   ]
+},
+{
+  description: "ICU bug in formatting of dates that span month boundaries",
+  result: EXEC_FAILED,
+  bug: 20708022,
+  names: [
+    "libcore.icu.DateIntervalFormatTest#testEndOfDayOnLastDayOfMonth"
+  ]
 }
 ]
diff --git a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
index 7adad72..6e0bce6 100644
--- a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
+++ b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
@@ -423,4 +423,13 @@
     assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11*HOUR, 13*HOUR, flags));
     assertEquals("2 – 3 PM", formatDateRange(l, utc, 14*HOUR, 15*HOUR, flags));
   }
+
+  // http://b/20708022
+  public void testEndOfDayOnLastDayOfMonth() throws Exception {
+    final ULocale locale = new ULocale("en");
+    final TimeZone timeZone = TimeZone.getTimeZone("UTC");
+
+    assertEquals("April 30, 11:00 PM – May 1, 12:00 AM", formatDateRange(locale, timeZone,
+            1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
+  }
 }
diff --git a/luni/src/test/java/libcore/xml/PullParserTest.java b/luni/src/test/java/libcore/xml/PullParserTest.java
index c59b358..b204c88 100644
--- a/luni/src/test/java/libcore/xml/PullParserTest.java
+++ b/luni/src/test/java/libcore/xml/PullParserTest.java
@@ -766,6 +766,16 @@
         assertRelaxedParseFailure("<!DOCTYPE foo [<!ELEMENT foo EMPTY"); // EOF in read('>')
     }
 
+    public void testWhitespacesAfterDOCTYPE() throws Exception {
+        XmlPullParser parser = newPullParser();
+        String test = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<!DOCTYPE root [\n" +
+            "<!ENTITY dummy \"dummy\">\n" +
+            "]>  \n" +
+            "<root></root>";
+        assertParseSuccess(test, parser);
+    }
+
     private void assertParseFailure(String xml) throws Exception {
         XmlPullParser parser = newPullParser();
         assertParseFailure(xml, parser);
@@ -787,6 +797,12 @@
         }
     }
 
+    private void assertParseSuccess(String xml, XmlPullParser parser) throws Exception {
+        parser.setInput(new StringReader(xml));
+        while (parser.next() != XmlPullParser.END_DOCUMENT) {
+        }
+    }
+
     /**
      * Creates a new pull parser.
      */
diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java
index a90db3b..2e32bf1 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlParser.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java
@@ -604,6 +604,7 @@
         }
 
         read('>');
+        skip();
     }
 
     /**
diff --git a/xml/src/main/java/org/kxml2/io/KXmlSerializer.java b/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
index bfdeece..399462d 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
@@ -27,17 +27,13 @@
 
 public class KXmlSerializer implements XmlSerializer {
 
+    private static final int BUFFER_LEN = 8192;
+    private final char[] mText = new char[BUFFER_LEN];
+    private int mPos;
+
     //    static final String UNDEFINED = ":";
 
-    // BEGIN android-added
-    /** size (in characters) for the write buffer */
-    private static final int WRITE_BUFFER_SIZE = 500;
-    // END android-added
-
-    // BEGIN android-changed
-    // (Guarantee that the writer is always buffered.)
-    private BufferedWriter writer;
-    // END android-changed
+    private Writer writer;
 
     private boolean pending;
     private int auto;
@@ -52,6 +48,41 @@
     private boolean unicode;
     private String encoding;
 
+    private void append(char c) throws IOException {
+        if (mPos >= BUFFER_LEN) {
+            flushBuffer();
+        }
+        mText[mPos++] = c;
+    }
+
+    private void append(String str, int i, int length) throws IOException {
+        while (length > 0) {
+            if (mPos == BUFFER_LEN) {
+                flushBuffer();
+            }
+            int batch = BUFFER_LEN - mPos;
+            if (batch > length) {
+                batch = length;
+            }
+            str.getChars(i, i + batch, mText, mPos);
+            i += batch;
+            length -= batch;
+            mPos += batch;
+        }
+    }
+
+    private void append(String str) throws IOException {
+        append(str, 0, str.length());
+    }
+
+    private final void flushBuffer() throws IOException {
+        if(mPos > 0) {
+            writer.write(mText, 0, mPos);
+            writer.flush();
+            mPos = 0;
+        }
+    }
+
     private final void check(boolean close) throws IOException {
         if (!pending)
             return;
@@ -67,17 +98,16 @@
         indent[depth] = indent[depth - 1];
 
         for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) {
-            writer.write(' ');
-            writer.write("xmlns");
+            append(" xmlns");
             if (!nspStack[i * 2].isEmpty()) {
-                writer.write(':');
-                writer.write(nspStack[i * 2]);
+                append(':');
+                append(nspStack[i * 2]);
             }
             else if (getNamespace().isEmpty() && !nspStack[i * 2 + 1].isEmpty())
                 throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
-            writer.write("=\"");
+            append("=\"");
             writeEscaped(nspStack[i * 2 + 1], '"');
-            writer.write('"');
+            append('"');
         }
 
         if (nspCounts.length <= depth + 1) {
@@ -89,7 +119,11 @@
         nspCounts[depth + 1] = nspCounts[depth];
         //   nspCounts[depth + 2] = nspCounts[depth];
 
-        writer.write(close ? " />" : ">");
+        if (close) {
+            append(" />");
+        } else {
+            append('>');
+        }
     }
 
     private final void writeEscaped(String s, int quot) throws IOException {
@@ -100,22 +134,22 @@
                 case '\r':
                 case '\t':
                     if(quot == -1)
-                        writer.write(c);
+                        append(c);
                     else
-                        writer.write("&#"+((int) c)+';');
+                        append("&#"+((int) c)+';');
                     break;
                 case '&' :
-                    writer.write("&amp;");
+                    append("&amp;");
                     break;
                 case '>' :
-                    writer.write("&gt;");
+                    append("&gt;");
                     break;
                 case '<' :
-                    writer.write("&lt;");
+                    append("&lt;");
                     break;
                 default:
                     if (c == quot) {
-                        writer.write(c == '"' ? "&quot;" : "&apos;");
+                        append(c == '"' ? "&quot;" : "&apos;");
                         break;
                     }
                     // BEGIN android-changed: refuse to output invalid characters
@@ -128,9 +162,9 @@
                     boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
                     if (allowedInXml) {
                         if (unicode || c < 127) {
-                            writer.write(c);
+                            append(c);
                         } else {
-                            writer.write("&#" + ((int) c) + ";");
+                            append("&#" + ((int) c) + ";");
                         }
                     } else if (Character.isHighSurrogate(c) && i < s.length() - 1) {
                         writeSurrogate(c, s.charAt(i + 1));
@@ -157,9 +191,9 @@
         }*/
 
     public void docdecl(String dd) throws IOException {
-        writer.write("<!DOCTYPE");
-        writer.write(dd);
-        writer.write(">");
+        append("<!DOCTYPE");
+        append(dd);
+        append('>');
     }
 
     public void endDocument() throws IOException {
@@ -171,9 +205,9 @@
 
     public void entityRef(String name) throws IOException {
         check(false);
-        writer.write('&');
-        writer.write(name);
-        writer.write(';');
+        append('&');
+        append(name);
+        append(';');
     }
 
     public boolean getFeature(String name) {
@@ -301,14 +335,7 @@
     }
 
     public void setOutput(Writer writer) {
-        // BEGIN android-changed
-        // Guarantee that the writer is always buffered.
-        if (writer instanceof BufferedWriter) {
-            this.writer = (BufferedWriter) writer;
-        } else {
-            this.writer = new BufferedWriter(writer, WRITE_BUFFER_SIZE);
-        }
-        // END android-changed
+        this.writer = writer;
 
         // elementStack = new String[12]; //nsp/prefix/name
         //nspCounts = new int[4];
@@ -343,7 +370,7 @@
     }
 
     public void startDocument(String encoding, Boolean standalone) throws IOException {
-        writer.write("<?xml version='1.0' ");
+        append("<?xml version='1.0' ");
 
         if (encoding != null) {
             this.encoding = encoding;
@@ -353,18 +380,17 @@
         }
 
         if (this.encoding != null) {
-            writer.write("encoding='");
-            writer.write(this.encoding);
-            writer.write("' ");
+            append("encoding='");
+            append(this.encoding);
+            append("' ");
         }
 
         if (standalone != null) {
-            writer.write("standalone='");
-            writer.write(
-                standalone.booleanValue() ? "yes" : "no");
-            writer.write("' ");
+            append("standalone='");
+            append(standalone.booleanValue() ? "yes" : "no");
+            append("' ");
         }
-        writer.write("?>");
+        append("?>");
     }
 
     public XmlSerializer startTag(String namespace, String name)
@@ -375,9 +401,9 @@
         //            namespace = "";
 
         if (indent[depth]) {
-            writer.write("\r\n");
+            append("\r\n");
             for (int i = 0; i < depth; i++)
-                writer.write("  ");
+                append("  ");
         }
 
         int esp = depth * 3;
@@ -407,13 +433,13 @@
         elementStack[esp++] = prefix;
         elementStack[esp] = name;
 
-        writer.write('<');
+        append('<');
         if (!prefix.isEmpty()) {
-            writer.write(prefix);
-            writer.write(':');
+            append(prefix);
+            append(':');
         }
 
-        writer.write(name);
+        append(name);
 
         pending = true;
 
@@ -457,24 +483,24 @@
                 }
                 */
 
-        writer.write(' ');
+        append(' ');
         if (!prefix.isEmpty()) {
-            writer.write(prefix);
-            writer.write(':');
+            append(prefix);
+            append(':');
         }
-        writer.write(name);
-        writer.write('=');
+        append(name);
+        append('=');
         char q = value.indexOf('"') == -1 ? '"' : '\'';
-        writer.write(q);
+        append(q);
         writeEscaped(value, q);
-        writer.write(q);
+        append(q);
 
         return this;
     }
 
     public void flush() throws IOException {
         check(false);
-        writer.flush();
+        flushBuffer();
     }
     /*
         public void close() throws IOException {
@@ -503,19 +529,19 @@
         }
         else {
             if (indent[depth + 1]) {
-                writer.write("\r\n");
+                append("\r\n");
                 for (int i = 0; i < depth; i++)
-                    writer.write("  ");
+                    append("  ");
             }
 
-            writer.write("</");
+            append("</");
             String prefix = elementStack[depth * 3 + 1];
             if (!prefix.isEmpty()) {
-                writer.write(prefix);
-                writer.write(':');
+                append(prefix);
+                append(':');
             }
-            writer.write(name);
-            writer.write('>');
+            append(name);
+            append('>');
         }
 
         nspCounts[depth + 1] = nspCounts[depth];
@@ -552,24 +578,24 @@
         // BEGIN android-changed: ]]> is not allowed within a CDATA,
         // so break and start a new one when necessary.
         data = data.replace("]]>", "]]]]><![CDATA[>");
-        writer.write("<![CDATA[");
+        append("<![CDATA[");
         for (int i = 0; i < data.length(); ++i) {
             char ch = data.charAt(i);
             boolean allowedInCdata = (ch >= 0x20 && ch <= 0xd7ff) ||
                     (ch == '\t' || ch == '\n' || ch == '\r') ||
                     (ch >= 0xe000 && ch <= 0xfffd);
             if (allowedInCdata) {
-                writer.write(ch);
+                append(ch);
             } else if (Character.isHighSurrogate(ch) && i < data.length() - 1) {
                 // Character entities aren't valid in CDATA, so break out for this.
-                writer.write("]]>");
+                append("]]>");
                 writeSurrogate(ch, data.charAt(++i));
-                writer.write("<![CDATA[");
+                append("<![CDATA[");
             } else {
                 reportInvalidCharacter(ch);
             }
         }
-        writer.write("]]>");
+        append("]]>");
         // END android-changed
     }
 
@@ -583,22 +609,22 @@
         // seems likely to upset anything expecting modified UTF-8 rather than "real" UTF-8. It seems more
         // conservative in a Java environment to use an entity reference instead.
         int codePoint = Character.toCodePoint(high, low);
-        writer.write("&#" + codePoint + ";");
+        append("&#" + codePoint + ";");
     }
     // END android-added
 
     public void comment(String comment) throws IOException {
         check(false);
-        writer.write("<!--");
-        writer.write(comment);
-        writer.write("-->");
+        append("<!--");
+        append(comment);
+        append("-->");
     }
 
     public void processingInstruction(String pi)
         throws IOException {
         check(false);
-        writer.write("<?");
-        writer.write(pi);
-        writer.write("?>");
+        append("<?");
+        append(pi);
+        append("?>");
     }
 }