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));