Merge "Implement the Java 6 NetworkInterface/InterfaceAddress functionality." into dalvik-dev
diff --git a/archive/src/main/java/java/util/zip/Deflater.java b/archive/src/main/java/java/util/zip/Deflater.java
index eb49f12..41a337c 100644
--- a/archive/src/main/java/java/util/zip/Deflater.java
+++ b/archive/src/main/java/java/util/zip/Deflater.java
@@ -75,9 +75,38 @@
      */
     public static final int NO_COMPRESSION = 0;
 
-    private static final int Z_NO_FLUSH = 0;
+    /**
+     * Use buffering for best compression.
+     *
+     * @hide
+     * @since 1.7
+     */
+    public static final int NO_FLUSH = 0;
 
-    private static final int Z_FINISH = 4;
+    /**
+     * Flush buffers so recipients can immediately decode the data sent thus
+     * far. This mode may degrade compression.
+     *
+     * @hide
+     * @since 1.7
+     */
+    public static final int SYNC_FLUSH = 2;
+
+    /**
+     * Flush buffers so recipients can immediately decode the data sent thus
+     * far. The compression state is also reset to permit random access and
+     * recovery for clients who have discarded or damaged their own copy. This
+     * mode may degrade compression.
+     *
+     * @hide
+     * @since 1.7
+     */
+    public static final int FULL_FLUSH = 3;
+
+    /**
+     * Flush buffers and mark the end of the datastream.
+     */
+    private static final int FINISH = 4;
 
     // Fill in the JNI id caches
     private static native void oneTimeInitialization();
@@ -90,7 +119,7 @@
         oneTimeInitialization();
     }
 
-    private int flushParm = Z_NO_FLUSH;
+    private int flushParm = NO_FLUSH;
 
     private boolean finished;
 
@@ -175,19 +204,45 @@
      * @return the number of bytes of compressed data written to {@code buf}.
      */
     public synchronized int deflate(byte[] buf, int off, int nbytes) {
+        return deflateImpl(buf, off, nbytes, flushParm);
+    }
+
+    /**
+     * Deflates data (previously passed to {@code setInput}) into a specific
+     * region within the supplied buffer, optionally flushing the input buffer.
+     *
+     * @param buf the buffer to write compressed data to.
+     * @param off the offset within {@code buf} at which to start writing to.
+     * @param nbytes maximum number of bytes of compressed data to be written.
+     * @param flush one of {@link #NO_FLUSH}, {@link #SYNC_FLUSH} or
+     *      {@link #FULL_FLUSH}.
+     * @return the number of compressed bytes written to {@code buf}. If this
+     *      equals {@code nbytes}, the number of bytes of input to be flushed
+     *      may have exceeded the output buffer's capacity. In this case,
+     *      finishing a flush will require the output buffer to be drained
+     *      and additional calls to {@link #deflate} to be made.
+     * @hide
+     * @since 1.7
+     */
+    public synchronized int deflate(byte[] buf, int off, int nbytes, int flush) {
+        if (flush != NO_FLUSH && flush != SYNC_FLUSH && flush != FULL_FLUSH) {
+            throw new IllegalArgumentException();
+        }
+        return deflateImpl(buf, off, nbytes, flush);
+    }
+
+    private synchronized int deflateImpl(
+            byte[] buf, int off, int nbytes, int flush) {
         if (streamHandle == -1) {
             throw new IllegalStateException();
         }
-        // avoid int overflow, check null buf
-        if (off <= buf.length && nbytes >= 0 && off >= 0
-                && buf.length - off >= nbytes) {
-            // put a stub buffer, no effect.
-            if (null == inputBuffer) {
-                setInput(STUB_INPUT_BUFFER);
-            }
-            return deflateImpl(buf, off, nbytes, streamHandle, flushParm);
+        if (off > buf.length || nbytes < 0 || off < 0 || buf.length - off < nbytes) {
+            throw new ArrayIndexOutOfBoundsException();
         }
-        throw new ArrayIndexOutOfBoundsException();
+        if (inputBuffer == null) {
+            setInput(STUB_INPUT_BUFFER);
+        }
+        return deflateImpl(buf, off, nbytes, streamHandle, flush);
     }
 
     private synchronized native int deflateImpl(byte[] buf, int off,
@@ -229,7 +284,7 @@
      * @see #finished
      */
     public synchronized void finish() {
-        flushParm = Z_FINISH;
+        flushParm = FINISH;
     }
 
     /**
@@ -325,7 +380,7 @@
             throw new NullPointerException();
         }
 
-        flushParm = Z_NO_FLUSH;
+        flushParm = NO_FLUSH;
         finished = false;
         resetImpl(streamHandle);
         inputBuffer = null;
diff --git a/archive/src/main/java/java/util/zip/DeflaterOutputStream.java b/archive/src/main/java/java/util/zip/DeflaterOutputStream.java
index 197426f..7ea27fa 100644
--- a/archive/src/main/java/java/util/zip/DeflaterOutputStream.java
+++ b/archive/src/main/java/java/util/zip/DeflaterOutputStream.java
@@ -45,6 +45,8 @@
 
     boolean done = false;
 
+    private final boolean syncFlush;
+
     /**
      * This constructor lets you pass the {@code Deflater} specifying the
      * compression algorithm.
@@ -57,7 +59,7 @@
      *            data.
      */
     public DeflaterOutputStream(OutputStream os, Deflater def) {
-        this(os, def, BUF_SIZE);
+        this(os, def, BUF_SIZE, false);
     }
 
     /**
@@ -71,7 +73,7 @@
      *            is the OutputStream where to write the compressed data to.
      */
     public DeflaterOutputStream(OutputStream os) {
-        this(os, new Deflater());
+        this(os, new Deflater(), BUF_SIZE, false);
     }
 
     /**
@@ -88,6 +90,30 @@
      *            is the size to be used for the internal buffer.
      */
     public DeflaterOutputStream(OutputStream os, Deflater def, int bsize) {
+        this(os, def, bsize, false);
+    }
+
+    /**
+     * @hide
+     * @since 1.7
+     */
+    public DeflaterOutputStream(OutputStream os, boolean syncFlush) {
+        this(os, new Deflater(), BUF_SIZE, syncFlush);
+    }
+
+    /**
+     * @hide
+     * @since 1.7
+     */
+    public DeflaterOutputStream(OutputStream os, Deflater def, boolean syncFlush) {
+        this(os, def, BUF_SIZE, syncFlush);
+    }
+
+    /**
+     * @hide
+     * @since 1.7
+     */
+    public DeflaterOutputStream(OutputStream os, Deflater def, int bsize, boolean syncFlush) {
         super(os);
         if (os == null || def == null) {
             throw new NullPointerException();
@@ -96,6 +122,7 @@
             throw new IllegalArgumentException();
         }
         this.def = def;
+        this.syncFlush = syncFlush;
         buf = new byte[bsize];
     }
 
@@ -192,4 +219,21 @@
             throw new ArrayIndexOutOfBoundsException();
         }
     }
+
+    /**
+     * Flushes the underlying stream. This flushes only the bytes that can be
+     * compressed at the highest level.
+     *
+     * <p>For deflater output streams constructed with Java 7's
+     * {@code syncFlush} parameter set to true (not yet available on Android),
+     * this first flushes all outstanding data so that it may be immediately
+     * read by its recipient. Doing so may degrade compression.
+     */
+    @Override public void flush() throws IOException {
+        if (syncFlush) {
+            int count = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH);
+            out.write(buf, 0, count);
+        }
+        out.flush();
+    }
 }
diff --git a/archive/src/test/java/java/util/zip/AllTests.java b/archive/src/test/java/java/util/zip/AllTests.java
new file mode 100644
index 0000000..df1d8de
--- /dev/null
+++ b/archive/src/test/java/java/util/zip/AllTests.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 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 java.util.zip;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AllTests {
+    public static Test suite() {
+        TestSuite suite = new TestSuite();
+        suite.addTestSuite(DeflaterOutputStreamTest.class);
+        suite.addTestSuite(DeflaterTest.class);
+        return suite;
+    }
+}
diff --git a/archive/src/test/java/java/util/zip/DeflaterOutputStreamTest.java b/archive/src/test/java/java/util/zip/DeflaterOutputStreamTest.java
new file mode 100644
index 0000000..5d155fe
--- /dev/null
+++ b/archive/src/test/java/java/util/zip/DeflaterOutputStreamTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 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 java.util.zip;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class DeflaterOutputStreamTest extends TestCase {
+
+    public void testSyncFlushEnabled() throws Exception {
+        InflaterInputStream in = createInflaterStream(true);
+        assertEquals(1, in.read());
+        assertEquals(2, in.read());
+        assertEquals(3, in.read());
+    }
+
+    public void testSyncFlushDisabled() throws Exception {
+        InflaterInputStream in = createInflaterStream(false);
+        try {
+            in.read();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    /**
+     * Creates an optionally-flushing deflater stream, writes some bytes to it,
+     * and flushes it. Returns an inflater stream that reads this deflater's
+     * output.
+     *
+     * <p>These bytes are written on a separate thread so that when the inflater
+     * stream is read, that read will fail when no bytes are available. Failing
+     * takes 3 seconds, co-ordinated by PipedInputStream's 'broken pipe'
+     * timeout. The 3 second delay is unfortunate but seems to be the easiest
+     * way demonstrate that data is unavailable. Ie. other techniques will cause
+     * the dry read to block indefinitely.
+     */
+    private InflaterInputStream createInflaterStream(final boolean flushing)
+            throws Exception {
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        final PipedOutputStream pout = new PipedOutputStream();
+        PipedInputStream pin = new PipedInputStream(pout);
+
+        executor.submit(new Callable<Void>() {
+            public Void call() throws Exception {
+                OutputStream out = new DeflaterOutputStream(pout, flushing);
+                out.write(1);
+                out.write(2);
+                out.write(3);
+                out.flush();
+                return null;
+            }
+        }).get();
+        executor.shutdown();
+
+        return new InflaterInputStream(pin);
+    }
+}
diff --git a/archive/src/test/java/java/util/zip/DeflaterTest.java b/archive/src/test/java/java/util/zip/DeflaterTest.java
new file mode 100644
index 0000000..d8ce66e
--- /dev/null
+++ b/archive/src/test/java/java/util/zip/DeflaterTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 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 java.util.zip;
+
+import junit.framework.TestCase;
+
+public class DeflaterTest extends TestCase {
+
+    private byte[] compressed = new byte[32];
+    private byte[] decompressed = new byte[20];
+    private Deflater deflater = new Deflater();
+    private Inflater inflater = new Inflater();
+    private int totalDeflated = 0;
+    private int totalInflated = 0;
+
+    public void testDeflate() throws DataFormatException {
+        deflater.setInput(new byte[] { 1, 2, 3 });
+        deflateInflate(Deflater.NO_FLUSH);
+        assertTrue(totalInflated < 3);
+        assertEquals(0, decompressed[2]); // the 3rd byte shouldn't have been flushed yet
+
+        deflater.setInput(new byte[] { 4, 5, 6 });
+        deflateInflate(Deflater.SYNC_FLUSH);
+        assertEquals(6, totalInflated);
+        assertDecompressed(1, 2, 3, 4, 5, 6);
+        assertEquals(0, inflater.inflate(decompressed));
+
+        deflater.setInput(new byte[] { 7, 8, 9 });
+        deflateInflate(Deflater.FULL_FLUSH);
+        assertEquals(9, totalInflated);
+        assertDecompressed(1, 2, 3, 4, 5, 6, 7, 8, 9);
+        assertEquals(0, inflater.inflate(decompressed));
+        inflater = new Inflater(true); // safe because we did a FULL_FLUSH
+
+        deflater.setInput(new byte[] { 10, 11, 12 });
+        deflateInflate(Deflater.SYNC_FLUSH);
+        assertEquals(12, totalInflated);
+        assertDecompressed(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
+        assertEquals(0, inflater.inflate(decompressed));
+    }
+
+    private void deflateInflate(int flush) throws DataFormatException {
+        int lastDeflated = deflater.deflate(compressed, totalDeflated,
+                compressed.length - totalDeflated, flush);
+        assertTrue(inflater.needsInput());
+        inflater.setInput(compressed, totalDeflated, lastDeflated);
+        totalDeflated += lastDeflated;
+        totalInflated += inflater.inflate(decompressed, totalInflated,
+                decompressed.length - totalInflated);
+    }
+
+    private void assertDecompressed(int... expected) {
+        for (int i = 0; i < decompressed.length; i++) {
+            int expectedValue = i < expected.length ? expected[i] : 0;
+            assertEquals(expectedValue, decompressed[i]);
+        }
+    }
+}
diff --git a/dalvik/src/main/java/dalvik/bytecode/Opcodes.java b/dalvik/src/main/java/dalvik/bytecode/Opcodes.java
index 33795ba..845ace2 100644
--- a/dalvik/src/main/java/dalvik/bytecode/Opcodes.java
+++ b/dalvik/src/main/java/dalvik/bytecode/Opcodes.java
@@ -273,13 +273,18 @@
     int OP_SHR_INT_LIT8                 = 0xe1;
     int OP_USHR_INT_LIT8                = 0xe2;
 
-    /* e3-eb unused */
+    /* e3-e7 unused */
 
     /*
      * The rest of these are either generated by dexopt for optimized
      * code, or inserted by the VM at runtime.  They are never generated
      * by "dx".
      */
+    int OP_IGET_WIDE_VOLATILE           = 0xe8;
+    int OP_IPUT_WIDE_VOLATILE           = 0xe9;
+    int OP_SGET_WIDE_VOLATILE           = 0xea;
+    int OP_SPUT_WIDE_VOLATILE           = 0xeb;
+
     int OP_BREAKPOINT                   = 0xec;
     int OP_THROW_VERIFICATION_ERROR     = 0xed;
 
diff --git a/icu/src/main/java/com/ibm/icu4jni/util/Resources.java b/icu/src/main/java/com/ibm/icu4jni/util/Resources.java
index 53be6ab..4a91d62 100644
--- a/icu/src/main/java/com/ibm/icu4jni/util/Resources.java
+++ b/icu/src/main/java/com/ibm/icu4jni/util/Resources.java
@@ -18,12 +18,12 @@
 
 import java.util.Arrays;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.ListResourceBundle;
 import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 import java.util.TimeZone;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.logging.Logger;
 
 /**
@@ -33,8 +33,8 @@
  */
 public class Resources {
     // A cache for the locale-specific data.
-    private static final ConcurrentHashMap<String, LocaleData> localeDataCache =
-            new ConcurrentHashMap<String, LocaleData>();
+    private static final HashMap<String, LocaleData> localeDataCache =
+            new HashMap<String, LocaleData>();
 
     /**
      * Cache for ISO language names.
@@ -64,13 +64,21 @@
             locale = Locale.getDefault();
         }
         String localeName = locale.toString();
-        LocaleData localeData = localeDataCache.get(localeName);
-        if (localeData != null) {
-            return localeData;
+        synchronized (localeDataCache) {
+            LocaleData localeData = localeDataCache.get(localeName);
+            if (localeData != null) {
+                return localeData;
+            }
         }
-        localeData = makeLocaleData(locale);
-        boolean absent = (localeDataCache.putIfAbsent(localeName, localeData) == null);
-        return absent ? localeData : localeDataCache.get(localeName);
+        LocaleData newLocaleData = makeLocaleData(locale);
+        synchronized (localeDataCache) {
+            LocaleData localeData = localeDataCache.get(localeName);
+            if (localeData != null) {
+                return localeData;
+            }
+            localeDataCache.put(localeName, newLocaleData);
+            return newLocaleData;
+        }
     }
 
     private static LocaleData makeLocaleData(Locale locale) {
diff --git a/luni/src/main/java/java/io/File.java b/luni/src/main/java/java/io/File.java
index 553206d..2910f91 100644
--- a/luni/src/main/java/java/io/File.java
+++ b/luni/src/main/java/java/io/File.java
@@ -200,7 +200,8 @@
         // Keep a copy of the cleaned-up string path.
         this.path = fixSlashes(dirtyPath);
         // Cache the UTF-8 bytes we need for the JNI.
-        if (isAbsolute()) {
+        // TODO: we shouldn't do this caching at all; the RI demonstrably doesn't.
+        if (path.length() > 0 && path.charAt(0) == separatorChar) { // http://b/2486943
             this.pathBytes = newCString(path);
             return;
         }
diff --git a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
index 12c3dd9..1a8f3a4 100644
--- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
+++ b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
@@ -331,38 +331,70 @@
     return byteArrayToInetAddress(env, byteArray);
 }
 
-/**
- * Converts an IPv4 address to an IPv4-mapped IPv6 address if fd is an IPv6
- * socket.
- * @param fd the socket.
- * @param sin_ss the address.
- * @param sin6_ss scratch space where we can store the mapped address if necessary.
- * @param mapUnspecified if true, convert 0.0.0.0 to ::ffff:0:0; if false, to ::
- * @return either sin_ss or sin6_ss, depending on which the caller should use.
- */
-static const sockaddr* convertIpv4ToMapped(int fd,
-        const sockaddr_storage* sin_ss, sockaddr_storage* sin6_ss, bool mapUnspecified) {
-    // We need to map if we have an IPv4 address but an IPv6 socket.
-    bool needsMapping = (sin_ss->ss_family == AF_INET && getSocketAddressFamily(fd) == AF_INET6);
-    if (!needsMapping) {
-        return reinterpret_cast<const sockaddr*>(sin_ss);
+// Handles translating between IPv4 and IPv6 addresses so -- where possible --
+// we can use either class of address with either an IPv4 or IPv6 socket.
+class CompatibleSocketAddress {
+public:
+    // Constructs an address corresponding to 'ss' that's compatible with 'fd'.
+    CompatibleSocketAddress(int fd, const sockaddr_storage& ss, bool mapUnspecified) {
+        const int desiredFamily = getSocketAddressFamily(fd);
+        if (ss.ss_family == AF_INET6) {
+            if (desiredFamily == AF_INET6) {
+                // Nothing to do.
+                mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
+            } else {
+                sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&mTmp);
+                const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(&ss);
+                memset(sin, 0, sizeof(*sin));
+                sin->sin_family = AF_INET;
+                sin->sin_port = sin6->sin6_port;
+                if (IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr)) {
+                    // We have an IPv6-mapped IPv4 address, but need plain old IPv4.
+                    // Unmap the mapped address in ss into an IPv6 address in mTmp.
+                    memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
+                    mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
+                } else if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
+                    // Translate the IPv6 loopback address to the IPv4 one.
+                    sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+                    mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
+                } else {
+                    // We can't help you. We return what you gave us, and assume you'll
+                    // get a sensible error when you use the address.
+                    mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
+                }
+            }
+        } else /* ss.ss_family == AF_INET */ {
+            if (desiredFamily == AF_INET) {
+                // Nothing to do.
+                mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
+            } else {
+                // We have IPv4 and need IPv6.
+                // Map the IPv4 address in ss into an IPv6 address in mTmp.
+                const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
+                sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&mTmp);
+                memset(sin6, 0, sizeof(*sin6));
+                sin6->sin6_family = AF_INET6;
+                sin6->sin6_port = sin->sin_port;
+                // TODO: mapUnspecified was introduced because kernels < 2.6.31 don't allow
+                // you to bind to ::ffff:0.0.0.0. When we move to something >= 2.6.31, we
+                // should make the code behave as if mapUnspecified were always true, and
+                // remove the parameter.
+                if (sin->sin_addr.s_addr != 0 || mapUnspecified) {
+                    memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
+                }
+                memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
+                mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
+            }
+        }
     }
-    // Map the IPv4 address in sin_ss into an IPv6 address in sin6_ss.
-    const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(sin_ss);
-    sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(sin6_ss);
-    memset(sin6, 0, sizeof(*sin6));
-    sin6->sin6_family = AF_INET6;
-    sin6->sin6_port = sin->sin_port;
-    // TODO: mapUnspecified was introduced because kernels < 2.6.31 don't allow
-    // you to bind to ::ffff:0.0.0.0. When we move to something >= 2.6.31, we
-    // should make the code behave as if mapUnspecified were always true, and
-    // remove the parameter.
-    if (sin->sin_addr.s_addr != 0 || mapUnspecified) {
-        memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
+    // Returns a pointer to an address compatible with the socket.
+    const sockaddr* get() const {
+        return mCompatibleAddress;
     }
-    memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
-    return reinterpret_cast<const sockaddr*>(sin6_ss);
-}
+private:
+    const sockaddr* mCompatibleAddress;
+    sockaddr_storage mTmp;
+};
 
 /**
  * Converts an InetAddress object and port number to a native address structure.
@@ -926,9 +958,8 @@
  * @param socketAddress the address to connect to
  */
 static int doConnect(int fd, const sockaddr_storage* socketAddress) {
-    sockaddr_storage tmp;
-    const sockaddr* realAddress = convertIpv4ToMapped(fd, socketAddress, &tmp, true);
-    return TEMP_FAILURE_RETRY(connect(fd, realAddress, sizeof(sockaddr_storage)));
+    const CompatibleSocketAddress compatibleAddress(fd, *socketAddress, true);
+    return TEMP_FAILURE_RETRY(connect(fd, compatibleAddress.get(), sizeof(sockaddr_storage)));
 }
 
 /**
@@ -1442,12 +1473,12 @@
         return -1;
     }
 
-    int sock;
-    sock = socket(PF_INET6, type, 0);
-    if (sock < 0 && errno == EAFNOSUPPORT) {
+    // Try IPv6 but fall back to IPv4...
+    int sock = socket(PF_INET6, type, 0);
+    if (sock == -1 && errno == EAFNOSUPPORT) {
         sock = socket(PF_INET, type, 0);
     }
-    if (sock < 0) {
+    if (sock == -1) {
         jniThrowSocketException(env, errno);
         return sock;
     }
@@ -1774,9 +1805,8 @@
         return;
     }
 
-    sockaddr_storage tmp;
-    const sockaddr* realAddress = convertIpv4ToMapped(fd, &socketAddress, &tmp, false);
-    int rc = TEMP_FAILURE_RETRY(bind(fd, realAddress, sizeof(sockaddr_storage)));
+    const CompatibleSocketAddress compatibleAddress(fd, socketAddress, false);
+    int rc = TEMP_FAILURE_RETRY(bind(fd, compatibleAddress.get(), sizeof(sockaddr_storage)));
     if (rc == -1) {
         jniThrowBindException(env, errno);
     }
@@ -1936,12 +1966,14 @@
         return;
     }
 
-    sockaddr_storage sockAddr;
-    memset(&sockAddr, 0, sizeof(sockAddr));
-    sockAddr.ss_family = AF_UNSPEC;
-
-    int result = doConnect(fd, &sockAddr);
-    if (result < 0) {
+    // To disconnect a datagram socket, we connect to a bogus address with
+    // the family AF_UNSPEC.
+    sockaddr_storage ss;
+    memset(&ss, 0, sizeof(ss));
+    ss.ss_family = AF_UNSPEC;
+    const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss);
+    int rc = TEMP_FAILURE_RETRY(connect(fd, sa, sizeof(ss)));
+    if (rc == -1) {
         jniThrowSocketException(env, errno);
     }
 }
diff --git a/luni/src/test/java/java/io/FileTest.java b/luni/src/test/java/java/io/FileTest.java
index d7ffaac..0518c26 100644
--- a/luni/src/test/java/java/io/FileTest.java
+++ b/luni/src/test/java/java/io/FileTest.java
@@ -115,4 +115,21 @@
         assertEquals(new File(cwd), f.getCanonicalFile());
         assertEquals(cwd, f.getCanonicalPath());
     }
+
+    // http://b/2486943 - between eclair and froyo, we added a call to
+    // isAbsolute from the File constructor, potentially breaking subclasses.
+    public void test_subclassing() throws Exception {
+        class MyFile extends File {
+            private String field;
+            MyFile(String s) {
+                super(s);
+                field = "";
+            }
+            @Override public boolean isAbsolute() {
+                field.length();
+                return super.isAbsolute();
+            }
+        }
+        new MyFile("");
+    }
 }
diff --git a/luni/src/test/java/java/lang/AllTests.java b/luni/src/test/java/java/lang/AllTests.java
index 38da14a..d8b7ade 100644
--- a/luni/src/test/java/java/lang/AllTests.java
+++ b/luni/src/test/java/java/lang/AllTests.java
@@ -24,6 +24,7 @@
         TestSuite suite = new TestSuite();
         suite.addTestSuite(java.lang.FloatTest.class);
         suite.addTestSuite(java.lang.ProcessBuilderTest.class);
+        suite.addTestSuite(SystemTest.class);
         return suite;
     }
 }
diff --git a/luni/src/test/java/java/lang/SystemTest.java b/luni/src/test/java/java/lang/SystemTest.java
new file mode 100644
index 0000000..b6fc5de
--- /dev/null
+++ b/luni/src/test/java/java/lang/SystemTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 java.lang;
+
+import junit.framework.TestCase;
+
+public class SystemTest extends TestCase {
+
+    public void testArrayCopyTargetNotArray() {
+        try {
+            System.arraycopy(new char[5], 0, "Hello", 0, 3);
+            fail();
+        } catch (ArrayStoreException e) {
+            assertEquals("source and destination must be arrays, but were "
+                    + "[C and Ljava/lang/String;", e.getMessage());
+        }
+    }
+
+    public void testArrayCopySourceNotArray() {
+        try {
+            System.arraycopy("Hello", 0, new char[5], 0, 3);
+            fail();
+        } catch (ArrayStoreException e) {
+            assertEquals("source and destination must be arrays, but were "
+                    + "Ljava/lang/String; and [C", e.getMessage());
+        }
+    }
+
+    public void testArrayCopyArrayTypeMismatch() {
+        try {
+            System.arraycopy(new char[5], 0, new Object[5], 0, 3);
+            fail();
+        } catch (ArrayStoreException e) {
+            assertEquals("source and destination arrays are incompatible: "
+                    + "[C and [Ljava/lang/Object;", e.getMessage());
+        }
+    }
+
+    public void testArrayCopyElementTypeMismatch() {
+        try {
+            System.arraycopy(new Object[] { null, 5, "hello" }, 0,
+                    new Integer[] { 1, 2, 3, null, null }, 0, 3);
+            fail();
+        } catch (ArrayStoreException e) {
+            assertEquals("source[2] of type Ljava/lang/String; cannot be "
+                    + "stored in destination array of type [Ljava/lang/Integer;",
+                    e.getMessage());
+        }
+    }
+}
diff --git a/text/src/main/java/java/text/Normalizer.java b/text/src/main/java/java/text/Normalizer.java
index c395715..cc9819c 100644
--- a/text/src/main/java/java/text/Normalizer.java
+++ b/text/src/main/java/java/text/Normalizer.java
@@ -78,4 +78,6 @@
     public static String normalize(CharSequence src, Form form) {
         return NativeNormalizer.normalize(src, form);
     }
+
+    private Normalizer() {}
 }
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
index 782bb39..b76c42f 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
@@ -575,27 +575,31 @@
                     byte[] tmpLength = new byte[2];
 //FIXME 1_byte==0x00                    
                     if (cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) {
-                        tmp = rsakey.getModulus().toByteArray();
+                        tmp = ServerKeyExchange.toUnsignedByteArray(rsakey.getModulus());
                         tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
                         tmpLength[1] = (byte) (tmp.length & 0xFF);
                         ds.update(tmpLength);
                         ds.update(tmp);
-                        tmp = rsakey.getPublicExponent().toByteArray();
+                        tmp = ServerKeyExchange.toUnsignedByteArray(rsakey.getPublicExponent());
                         tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
                         tmpLength[1] = (byte) (tmp.length & 0xFF);
+                        ds.update(tmpLength);
                         ds.update(tmp);
                     } else {
-                        tmp = dhkeySpec.getP().toByteArray();
+                        tmp = ServerKeyExchange.toUnsignedByteArray(dhkeySpec.getP());
                         tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
                         tmpLength[1] = (byte) (tmp.length & 0xFF);
+                        ds.update(tmpLength);
                         ds.update(tmp);
-                        tmp = dhkeySpec.getG().toByteArray();
+                        tmp = ServerKeyExchange.toUnsignedByteArray(dhkeySpec.getG());
                         tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
                         tmpLength[1] = (byte) (tmp.length & 0xFF);
+                        ds.update(tmpLength);
                         ds.update(tmp);
-                        tmp = dhkeySpec.getY().toByteArray();
+                        tmp = ServerKeyExchange.toUnsignedByteArray(dhkeySpec.getY());
                         tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
                         tmpLength[1] = (byte) (tmp.length & 0xFF);
+                        ds.update(tmpLength);
                         ds.update(tmp);
                     }
                     hash = ds.sign();
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java
index 446b7b4..af056a3 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java
@@ -65,22 +65,9 @@
         this.par3 = par3;
         this.hash = hash;
 
-        byte[] bb = this.par1.toByteArray();
-        if (bb[0] == 0) {
-// XXX check for par1 == 0 or bb.length > 1
-            bytes1 = new byte[bb.length - 1];
-            System.arraycopy(bb, 1, bytes1, 0, bytes1.length);
-        } else {
-            bytes1 = bb;
-        }
-
-        bb = this.par2.toByteArray();
-        if (bb[0] == 0) {
-            bytes2 = new byte[bb.length - 1];
-            System.arraycopy(bb, 1, bytes2, 0, bytes2.length);
-        } else {
-            bytes2 = bb;
-        }
+        bytes1 = toUnsignedByteArray(this.par1);
+   
+        bytes2 = toUnsignedByteArray(this.par2);
 
         length = 4 + bytes1.length + bytes2.length;
         if (hash != null) {
@@ -90,15 +77,28 @@
             bytes3 = null;
             return;
         }
-        bb = this.par3.toByteArray();
-        if (bb[0] == 0) {
-            bytes3 = new byte[bb.length - 1];
-            System.arraycopy(bb, 1, bytes3, 0, bytes3.length);
-        } else {
-            bytes3 = bb;
-        }
+        bytes3 = toUnsignedByteArray(this.par3);
         length += 2 + bytes3.length;
     }
+    
+    /**
+     * Remove first byte if 0. Needed because BigInteger.toByteArray() sometimes
+     * returns a zero prefix.
+     */
+    public static byte[] toUnsignedByteArray(BigInteger bi) {
+        if (bi == null) {
+            return null;
+        }
+        byte[] bb = bi.toByteArray();
+        // bb is not null, and has at least 1 byte - ZERO is represented as [0]
+        if (bb[0] == 0) {
+            byte[] noZero = new byte[bb.length - 1];
+            System.arraycopy(bb, 1, noZero, 0, noZero.length);
+            return noZero;
+        } else {
+            return bb;
+        }
+    }
 
     /**
      * Creates inbound message
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
index 991a707..b662a13 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
@@ -46,7 +46,7 @@
 
     public DocumentType createDocumentType(String qualifiedName,
             String publicId, String systemId) throws DOMException {
-        return new DocumentTypeImpl(this, qualifiedName, publicId, systemId);
+        return new DocumentTypeImpl(null, qualifiedName, publicId, systemId);
     }
 
     public boolean hasFeature(String feature, String version) {
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
index b009128..b1802a6 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
@@ -33,6 +33,12 @@
 import org.w3c.dom.NodeList;
 import org.w3c.dom.ProcessingInstruction;
 import org.w3c.dom.Text;
+import org.w3c.dom.UserDataHandler;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * Provides a straightforward implementation of the corresponding W3C DOM
@@ -60,14 +66,26 @@
     private boolean xmlStandalone = false;
     private boolean strictErrorChecking = true;
 
+    /**
+     * A lazily initialized map of user data values for this document's own
+     * nodes. The map is weak because the document may live longer than its
+     * nodes.
+     *
+     * <p>Attaching user data directly to the corresponding node would cost a
+     * field per node. Under the assumption that user data is rarely needed, we
+     * attach user data to the document to save those fields. Xerces also takes
+     * this approach.
+     */
+    private WeakHashMap<Node, Map<String, UserData>> nodeToUserData;
+
     public DocumentImpl(DOMImplementationImpl impl, String namespaceURI,
             String qualifiedName, DocumentType doctype, String inputEncoding) {
         super(null);
 
         this.domImplementation = impl;
         this.inputEncoding = inputEncoding;
-        // this.document = this;
-        
+        this.document = this;
+
         if (doctype != null) {
             appendChild(doctype);
         }
@@ -113,8 +131,6 @@
      * @return The new node.
      */
     Node cloneNode(Node node, boolean deep) throws DOMException {
-        // TODO: callback the UserDataHandler with a NODE_CLONED event
-
         Node target;
         
         switch (node.getNodeType()) {
@@ -191,10 +207,12 @@
                 target.appendChild(child);
             }
         }
-        
+
+        notifyUserDataHandlers(UserDataHandler.NODE_CLONED, node, target);
+
         return target;
     }
-    
+
     public AttrImpl createAttribute(String name) throws DOMException {
         return new AttrImpl(this, name);
     }
@@ -382,4 +400,48 @@
         // TODO: callback the UserDataHandler with a NODE_RENAMED event
         throw new UnsupportedOperationException(); // TODO
     }
+
+    /**
+     * Returns a map with the user data objects attached to the specified node.
+     * This map is readable and writable.
+     */
+    Map<String, UserData> getUserDataMap(Node node) {
+        if (nodeToUserData == null) {
+            nodeToUserData = new WeakHashMap<Node, Map<String, UserData>>();
+        }
+        Map<String, UserData> userDataMap = nodeToUserData.get(node);
+        if (userDataMap == null) {
+            userDataMap = new HashMap<String, UserData>();
+            nodeToUserData.put(node, userDataMap);
+        }
+        return userDataMap;
+    }
+
+    /**
+     * Returns a map with the user data objects attached to the specified node.
+     * The returned map may be read-only.
+     */
+    Map<String, UserData> getUserDataMapForRead(Node node) {
+        if (nodeToUserData == null) {
+            return Collections.emptyMap();
+        }
+        Map<String, UserData> userDataMap = nodeToUserData.get(node);
+        return userDataMap == null
+                ? Collections.<String, UserData>emptyMap()
+                : userDataMap;
+    }
+
+    /**
+     * Calls {@link UserDataHandler#handle} on each of the source node's
+     * value/handler pairs.
+     */
+    private void notifyUserDataHandlers(short operation, Node src, Node dst) {
+        for (Map.Entry<String, UserData> entry : getUserDataMapForRead(src).entrySet()) {
+            UserData userData = entry.getValue();
+            if (userData.handler != null) {
+                userData.handler.handle(
+                        operation, entry.getKey(), userData.value, src, dst);
+            }
+        }
+    }
 }
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentTypeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentTypeImpl.java
index 67947b7..b0e8eda 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentTypeImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentTypeImpl.java
@@ -31,7 +31,7 @@
  * the DOM implementation can easily access them while maintaining the DOM tree
  * structure.
  */
-public class DocumentTypeImpl extends LeafNodeImpl implements DocumentType {
+public final class DocumentTypeImpl extends LeafNodeImpl implements DocumentType {
 
     private String qualifiedName;
 
@@ -39,9 +39,9 @@
 
     private String systemId;
 
-    DocumentTypeImpl(DOMImplementationImpl impl, String qualifiedName,
+    public DocumentTypeImpl(DocumentImpl document, String qualifiedName,
             String publicId, String systemId) {
-        super(null);
+        super(document);
 
         if (qualifiedName == null || "".equals(qualifiedName)) {
             throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
index df1383d..e272a3e 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
@@ -315,7 +315,7 @@
     public Attr setAttributeNode(Attr newAttr) throws DOMException {
         AttrImpl newAttrImpl = (AttrImpl) newAttr;
         
-        if (newAttrImpl.document != this.getOwnerDocument()) {
+        if (newAttrImpl.document != this.document) {
             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
         }
 
@@ -340,7 +340,7 @@
     public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
         AttrImpl newAttrImpl = (AttrImpl) newAttr;
 
-        if (newAttrImpl.document != this.getOwnerDocument()) {
+        if (newAttrImpl.document != this.document) {
             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
         }
 
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java
index fa75e21..bd4affb 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java
@@ -43,7 +43,7 @@
     // Maintained by LeafNodeImpl and ElementImpl.
     List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>();
 
-    public InnerNodeImpl(DocumentImpl document) {
+    protected InnerNodeImpl(DocumentImpl document) {
         super(document);
     }
 
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/NamedNodeMapImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/NamedNodeMapImpl.java
index 0952d83..6eb8efa 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/NamedNodeMapImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/NamedNodeMapImpl.java
@@ -121,7 +121,7 @@
         
         // All nodes in the map must belong to the same document.
         if (list.size() != 0) {
-            Document document = list.get(0).getOwnerDocument();
+            Document document = list.get(0).document;
 
             if (document != null && arg.getOwnerDocument() != document) {
                 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
@@ -151,7 +151,7 @@
         
         // All nodes in the map must belong to the same document.
         if (list.size() != 0) {
-            Document document = list.get(0).getOwnerDocument();
+            Document document = list.get(0).document;
 
             if (document != null && arg.getOwnerDocument() != document) {
                 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
index 24ed102..8376689 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
@@ -29,6 +29,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A straightforward implementation of the corresponding W3C DOM node.
@@ -45,7 +46,6 @@
 
     private static final NodeList EMPTY_LIST = new NodeListImpl();
     
-    // Maintained by InnerNodeImpl and ElementImpl.
     DocumentImpl document;
 
     NodeImpl(DocumentImpl document) {
@@ -98,8 +98,8 @@
         return null;
     }
 
-    public Document getOwnerDocument() {
-        return document;
+    public final Document getOwnerDocument() {
+        return document == this ? null : document;
     }
 
     public Node getParentNode() {
@@ -308,7 +308,7 @@
                 }
                 // create a text node to hold the given content
                 if (textContent != null && textContent.length() != 0) {
-                    appendChild(getOwnerDocument().createTextNode(textContent));
+                    appendChild(document.createTextNode(textContent));
                 }
                 return;
 
@@ -585,12 +585,32 @@
         return isSupported(feature, version) ? this : null;
     }
 
-    public Object setUserData(String key, Object data,
-            UserDataHandler handler) {
-        throw new UnsupportedOperationException(); // TODO
+    public final Object setUserData(String key, Object data, UserDataHandler handler) {
+        if (key == null) {
+            throw new NullPointerException();
+        }
+        Map<String, UserData> map = document.getUserDataMap(this);
+        UserData previous = data == null
+                ? map.remove(key)
+                : map.put(key, new UserData(data, handler));
+        return previous != null ? previous.value : null;
     }
 
-    public Object getUserData(String key) {
-        throw new UnsupportedOperationException(); // TODO
+    public final Object getUserData(String key) {
+        if (key == null) {
+            throw new NullPointerException();
+        }
+        Map<String, UserData> map = document.getUserDataMapForRead(this);
+        UserData userData = map.get(key);
+        return userData != null ? userData.value : null;
+    }
+
+    static class UserData {
+        final Object value;
+        final UserDataHandler handler;
+        UserData(Object value, UserDataHandler handler) {
+            this.value = value;
+            this.handler = handler;
+        }
     }
 }
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java
index d39dff2..7b61b02 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java
@@ -47,7 +47,7 @@
     }
 
     public final Text splitText(int offset) throws DOMException {
-        Text newText = getOwnerDocument().createTextNode(
+        Text newText = document.createTextNode(
                 substringData(offset, getLength() - offset));
         deleteData(0, offset);
 
diff --git a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
index 4b273fe..f3956f8 100644
--- a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
@@ -25,6 +25,7 @@
 
 import org.apache.harmony.xml.dom.CDATASectionImpl;
 import org.apache.harmony.xml.dom.DocumentImpl;
+import org.apache.harmony.xml.dom.DocumentTypeImpl;
 import org.apache.harmony.xml.dom.TextImpl;
 import org.kxml2.io.KXmlParser;
 import org.w3c.dom.Attr;
@@ -242,8 +243,8 @@
                     if (sysid != null && sysid.length() >= 2 && sysid.startsWith("\"") && sysid.endsWith("\"")) {
                         sysid = sysid.substring(1, sysid.length() - 1);
                     }
-                    
-                    document.appendChild(dom.createDocumentType(name, pubid, sysid));
+
+                    document.appendChild(new DocumentTypeImpl(document, name, pubid, sysid));
                 }
                 
             } else if (token == XmlPullParser.COMMENT) {
diff --git a/xml/src/test/java/tests/xml/DomTest.java b/xml/src/test/java/tests/xml/DomTest.java
index 69e8b37..0bb27dc 100644
--- a/xml/src/test/java/tests/xml/DomTest.java
+++ b/xml/src/test/java/tests/xml/DomTest.java
@@ -31,6 +31,7 @@
 import org.w3c.dom.Notation;
 import org.w3c.dom.ProcessingInstruction;
 import org.w3c.dom.Text;
+import org.w3c.dom.UserDataHandler;
 import org.xml.sax.InputSource;
 
 import javax.xml.parsers.DocumentBuilder;
@@ -44,7 +45,11 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+
+import static org.w3c.dom.UserDataHandler.NODE_CLONED;
 
 /**
  * Construct a DOM and then interrogate it.
@@ -87,6 +92,7 @@
     private Element name;
     private Attr standard;
     private Attr deluxe;
+    private Text waffles;
     private Element description;
     private Text descriptionText1;
     private CDATASection descriptionText2;
@@ -128,6 +134,7 @@
         name = (Element) item.getChildNodes().item(1);
         standard = name.getAttributeNode("a:standard");
         deluxe = name.getAttributeNode("deluxe");
+        waffles = (Text) name.getChildNodes().item(0);
         description = (Element) item.getChildNodes().item(3);
         descriptionText1 = (Text) description.getChildNodes().item(0);
         descriptionText2 = (CDATASection) description.getChildNodes().item(1);
@@ -153,7 +160,7 @@
         }
 
         allNodes.addAll(Arrays.asList(document, doctype, menu, item, itemXmlns,
-                itemXmlnsA, name, standard, deluxe, description,
+                itemXmlnsA, name, standard, deluxe, waffles, description,
                 descriptionText1, descriptionText2, descriptionText3, option1,
                 option2, option2Reference, wafflemaker, nutrition, vitamins,
                 vitaminsXmlnsA, comment, vitaminc, vitamincText));
@@ -740,6 +747,87 @@
         assertEquals(expected, domToString(document));
     }
 
+    public void testUserDataAttachments() {
+        Object a = new Object();
+        Object b = new Object();
+        for (Node node : allNodes) {
+            node.setUserData("a", a, null);
+            node.setUserData("b", b, null);
+        }
+        for (Node node : allNodes) {
+            assertSame(a, node.getUserData("a"));
+            assertSame(b, node.getUserData("b"));
+            assertEquals(null, node.getUserData("c"));
+            assertEquals(null, node.getUserData("A"));
+        }
+    }
+
+    public void testUserDataRejectsNullKey() {
+        try {
+            menu.setUserData(null, "apple", null);
+            fail();
+        } catch (NullPointerException e) {
+        }
+        try {
+            menu.getUserData(null);
+            fail();
+        } catch (NullPointerException e) {
+        }
+    }
+
+    /**
+     * A shallow clone requires cloning the attributes but not the child nodes.
+     */
+    public void testUserDataHandlerNotifiedOfShallowClones() {
+        RecordingHandler handler = new RecordingHandler();
+        name.setUserData("a", "apple", handler);
+        name.setUserData("b", "banana", handler);
+        standard.setUserData("c", "cat", handler);
+        waffles.setUserData("d", "dog", handler);
+
+        Element clonedName = (Element) name.cloneNode(false);
+        Attr clonedStandard = clonedName.getAttributeNode("a:standard");
+
+        Set<String> expected = new HashSet<String>();
+        expected.add(notification(NODE_CLONED, "a", "apple", name, clonedName));
+        expected.add(notification(NODE_CLONED, "b", "banana", name, clonedName));
+        expected.add(notification(NODE_CLONED, "c", "cat", standard, clonedStandard));
+        assertEquals(expected, handler.calls);
+    }
+
+    /**
+     * A deep clone requires cloning both the attributes and the child nodes.
+     */
+    public void testUserDataHandlerNotifiedOfDeepClones() {
+        RecordingHandler handler = new RecordingHandler();
+        name.setUserData("a", "apple", handler);
+        name.setUserData("b", "banana", handler);
+        standard.setUserData("c", "cat", handler);
+        waffles.setUserData("d", "dog", handler);
+
+        Element clonedName = (Element) name.cloneNode(true);
+        Attr clonedStandard = clonedName.getAttributeNode("a:standard");
+        Text clonedWaffles = (Text) clonedName.getChildNodes().item(0);
+
+        Set<String> expected = new HashSet<String>();
+        expected.add(notification(NODE_CLONED, "a", "apple", name, clonedName));
+        expected.add(notification(NODE_CLONED, "b", "banana", name, clonedName));
+        expected.add(notification(NODE_CLONED, "c", "cat", standard, clonedStandard));
+        expected.add(notification(NODE_CLONED, "d", "dog", waffles, clonedWaffles));
+        assertEquals(expected, handler.calls);
+    }
+
+    private class RecordingHandler implements UserDataHandler {
+        final Set<String> calls = new HashSet<String>();
+        public void handle(short operation, String key, Object data, Node src, Node dst) {
+            calls.add(notification(operation, key, data, src, dst));
+        }
+    }
+
+    private String notification(short operation, String key, Object data, Node src, Node dst) {
+        return "op:" + operation + " key:" + key + " data:" + data + " src:" + src + " dst:" + dst;
+    }
+
     private String domToString(Document document) throws TransformerException {
         StringWriter writer = new StringWriter();
         transformer.transform(new DOMSource(document), new StreamResult(writer));