Fix URLTest#testAtSignInUserInfo failure am: 7e6977749c am: 47ee0f9e11 am: d24900bf47
am: aba0708c7d
Change-Id: Ibd29c496b3b1faadf53ad8349f59b22b08c105c6
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index 51c2a94..9082209 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -1537,8 +1537,13 @@
sockaddr* from = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
socklen_t* fromLength = (javaInetSocketAddress != NULL) ? &sl : 0;
jint recvCount = NET_FAILURE_RETRY(env, ssize_t, recvfrom, javaFd, bytes.get() + byteOffset, byteCount, flags, from, fromLength);
- if (recvCount > 0) {
- fillInetSocketAddress(env, javaInetSocketAddress, ss);
+ if (recvCount >= 0) {
+ // The socket may have performed orderly shutdown and recvCount would return 0 (see man 2
+ // recvfrom), in which case ss.ss_family == AF_UNIX and fillInetSocketAddress would fail.
+ // Don't fill in the address if recvfrom didn't succeed. http://b/33483694
+ if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
+ fillInetSocketAddress(env, javaInetSocketAddress, ss);
+ }
}
return recvCount;
}
diff --git a/luni/src/test/java/libcore/java/net/InetAddressTest.java b/luni/src/test/java/libcore/java/net/InetAddressTest.java
index 600196b..4f922eb 100644
--- a/luni/src/test/java/libcore/java/net/InetAddressTest.java
+++ b/luni/src/test/java/libcore/java/net/InetAddressTest.java
@@ -186,10 +186,8 @@
public void test_isReachable_neverThrows() throws Exception {
InetAddress inetAddress = InetAddress.getByName("www.google.com");
- final NetworkInterface netIf;
- try {
- netIf = NetworkInterface.getByName("dummy0");
- } catch (SocketException e) {
+ final NetworkInterface netIf = NetworkInterface.getByName("dummy0");
+ if (netIf == null) {
System.logI("Skipping test_isReachable_neverThrows because dummy0 isn't available");
return;
}
diff --git a/luni/src/test/java/libcore/java/net/OldSocketTest.java b/luni/src/test/java/libcore/java/net/OldSocketTest.java
index 4254516..0fae672 100644
--- a/luni/src/test/java/libcore/java/net/OldSocketTest.java
+++ b/luni/src/test/java/libcore/java/net/OldSocketTest.java
@@ -1983,10 +1983,14 @@
+ e.toString());
}
}
+ }
+ // Calling sendUrgentData on a closed socket should not allocate a new impl and leak resources.
+ // Bug: 31818400
+ public void test_sendUrgentDataI_leaky() throws IOException {
+ Socket theSocket = new Socket();
+ theSocket.close();
try {
- Socket theSocket = new Socket();
- theSocket.close();
theSocket.sendUrgentData(0);
fail("IOException was not thrown.");
} catch(IOException ioe) {
@@ -1994,6 +1998,19 @@
}
}
+ // Calling getTrafficClass on a closed socket should not allocate a new impl and leak resources.
+ // Bug: 31818400
+ public void test_getTrafficClass_leaky() throws IOException {
+ Socket theSocket = new Socket();
+ theSocket.close();
+ try {
+ theSocket.getTrafficClass();
+ fail();
+ } catch (IOException ioe) {
+ //expected
+ }
+ }
+
public void test_setPerformancePreference_Int_Int_Int() throws Exception {
Socket theSocket = new Socket();
theSocket.setPerformancePreferences(1, 1, 1);
diff --git a/luni/src/test/java/libcore/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java
index 3ef549b..9063e27 100644
--- a/luni/src/test/java/libcore/java/nio/BufferTest.java
+++ b/luni/src/test/java/libcore/java/nio/BufferTest.java
@@ -1365,4 +1365,105 @@
d.put(1, (double)1);
b.limit(0); d.put(1, (double)1);
}
+
+ // http://b/32655865
+ public void test_ByteBufferAsXBuffer_ByteOrder() {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(10);
+ // Fill a ByteBuffer with different bytes that make it easy to tell byte ordering issues.
+ for (int i = 0; i < 10; i++) {
+ byteBuffer.put((byte)i);
+ }
+ byteBuffer.rewind();
+ // Obtain a big-endian and little-endian copy of the source array.
+ ByteBuffer bigEndian = byteBuffer.duplicate().order(ByteOrder.BIG_ENDIAN);
+ ByteBuffer littleEndian = byteBuffer.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+
+ // Check each type longer than a byte to confirm the ordering differs.
+ // asXBuffer.
+ assertFalse(bigEndian.asShortBuffer().get() == littleEndian.asShortBuffer().get());
+ assertFalse(bigEndian.asIntBuffer().get() == littleEndian.asIntBuffer().get());
+ assertFalse(bigEndian.asLongBuffer().get() == littleEndian.asLongBuffer().get());
+ assertFalse(bigEndian.asDoubleBuffer().get() == littleEndian.asDoubleBuffer().get());
+ assertFalse(bigEndian.asCharBuffer().get() == littleEndian.asCharBuffer().get());
+ assertFalse(bigEndian.asFloatBuffer().get() == littleEndian.asFloatBuffer().get());
+
+ // asXBuffer().asReadOnlyBuffer()
+ assertFalse(bigEndian.asShortBuffer().asReadOnlyBuffer().get() ==
+ littleEndian.asShortBuffer().asReadOnlyBuffer().get());
+ assertFalse(bigEndian.asIntBuffer().asReadOnlyBuffer().get() ==
+ littleEndian.asIntBuffer().asReadOnlyBuffer().get());
+ assertFalse(bigEndian.asLongBuffer().asReadOnlyBuffer().get() ==
+ littleEndian.asLongBuffer().asReadOnlyBuffer().get());
+ assertFalse(bigEndian.asDoubleBuffer().asReadOnlyBuffer().get() ==
+ littleEndian.asDoubleBuffer().asReadOnlyBuffer().get());
+ assertFalse(bigEndian.asCharBuffer().asReadOnlyBuffer().get() ==
+ littleEndian.asCharBuffer().asReadOnlyBuffer().get());
+ assertFalse(bigEndian.asFloatBuffer().asReadOnlyBuffer().get() ==
+ littleEndian.asFloatBuffer().asReadOnlyBuffer().get());
+
+ // asXBuffer().duplicate()
+ assertFalse(bigEndian.asShortBuffer().duplicate().get() ==
+ littleEndian.asShortBuffer().duplicate().get());
+ assertFalse(bigEndian.asIntBuffer().duplicate().get() ==
+ littleEndian.asIntBuffer().duplicate().get());
+ assertFalse(bigEndian.asLongBuffer().duplicate().get() ==
+ littleEndian.asLongBuffer().duplicate().get());
+ assertFalse(bigEndian.asDoubleBuffer().duplicate().get() ==
+ littleEndian.asDoubleBuffer().duplicate().get());
+ assertFalse(bigEndian.asCharBuffer().duplicate().get() ==
+ littleEndian.asCharBuffer().duplicate().get());
+ assertFalse(bigEndian.asFloatBuffer().duplicate().get() ==
+ littleEndian.asFloatBuffer().duplicate().get());
+
+ // asXBuffer().slice()
+ assertFalse(bigEndian.asShortBuffer().slice().get() ==
+ littleEndian.asShortBuffer().slice().get());
+ assertFalse(bigEndian.asIntBuffer().slice().get() ==
+ littleEndian.asIntBuffer().slice().get());
+ assertFalse(bigEndian.asLongBuffer().slice().get() ==
+ littleEndian.asLongBuffer().slice().get());
+ assertFalse(bigEndian.asDoubleBuffer().slice().get() ==
+ littleEndian.asDoubleBuffer().slice().get());
+ assertFalse(bigEndian.asCharBuffer().slice().get() ==
+ littleEndian.asCharBuffer().slice().get());
+ assertFalse(bigEndian.asFloatBuffer().slice().get() ==
+ littleEndian.asFloatBuffer().slice().get());
+ }
+
+ // http://b/32655865
+ public void test_ByteBufferAsXBuffer_ByteOrder_2() {
+ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);
+ byteBuffer.order(ByteOrder.BIG_ENDIAN);
+ // Fill a ByteBuffer with different bytes that make it easy to tell byte ordering issues.
+ for (int i = 0; i < 10; i++) {
+ byteBuffer.put((byte)i);
+ }
+ byteBuffer.rewind();
+
+ // Create BIG_ENDIAN views of the buffer.
+ ShortBuffer sb_be = byteBuffer.asShortBuffer();
+ LongBuffer lb_be = byteBuffer.asLongBuffer();
+ IntBuffer ib_be = byteBuffer.asIntBuffer();
+ DoubleBuffer db_be = byteBuffer.asDoubleBuffer();
+ CharBuffer cb_be = byteBuffer.asCharBuffer();
+ FloatBuffer fb_be = byteBuffer.asFloatBuffer();
+
+ // Change the order of the underlying buffer.
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ // Create LITTLE_ENDIAN views of the buffer.
+ ShortBuffer sb_le = byteBuffer.asShortBuffer();
+ LongBuffer lb_le = byteBuffer.asLongBuffer();
+ IntBuffer ib_le = byteBuffer.asIntBuffer();
+ DoubleBuffer db_le = byteBuffer.asDoubleBuffer();
+ CharBuffer cb_le = byteBuffer.asCharBuffer();
+ FloatBuffer fb_le = byteBuffer.asFloatBuffer();
+
+ assertFalse(sb_be.get() == sb_le.get());
+ assertFalse(lb_be.get() == lb_le.get());
+ assertFalse(ib_be.get() == ib_le.get());
+ assertFalse(db_be.get() == db_le.get());
+ assertFalse(cb_be.get() == cb_le.get());
+ assertFalse(fb_be.get() == fb_le.get());
+ }
}
diff --git a/luni/src/test/java/libcore/java/util/FormatterTest.java b/luni/src/test/java/libcore/java/util/FormatterTest.java
index e87fee5..aed9ca0 100644
--- a/luni/src/test/java/libcore/java/util/FormatterTest.java
+++ b/luni/src/test/java/libcore/java/util/FormatterTest.java
@@ -17,7 +17,11 @@
package libcore.java.util;
import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
import java.util.Calendar;
+import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
@@ -183,4 +187,24 @@
assertEquals(expected, String.format(Locale.US, "%t" + pattern, c));
assertEquals(expected, String.format(Locale.US, "%T" + pattern, c));
}
+
+ // http://b/33245708: Some locales have a group separator != '\0' but a default decimal format
+ // pattern without grouping (e.g. a group size of zero). This would throw divide by zero when
+ // working out where to place the separator.
+ public void testGroupingSizeZero() {
+ Locale localeWithoutGrouping = new Locale("hy");
+ DecimalFormat decimalFormat =
+ (DecimalFormat) NumberFormat.getInstance(localeWithoutGrouping);
+
+ // Confirm the locale is still a good example: it has a group separator, but no grouping in
+ // the default decimal format.
+ assertEquals(0, decimalFormat.getGroupingSize());
+ assertFalse(decimalFormat.isGroupingUsed());
+ DecimalFormatSymbols symbols = decimalFormat.getDecimalFormatSymbols();
+ assertTrue(symbols.getGroupingSeparator() != '\0');
+
+ Formatter formatter = new Formatter(localeWithoutGrouping);
+ formatter.format("%,d", 123456789);
+ // No exception expected
+ }
}
diff --git a/ojluni/src/main/java/java/lang/System.java b/ojluni/src/main/java/java/lang/System.java
index 03b6bd2..a300205 100755
--- a/ojluni/src/main/java/java/lang/System.java
+++ b/ojluni/src/main/java/java/lang/System.java
@@ -976,7 +976,8 @@
// is prioritized over the properties in ICUConfig.properties. The issue with using
// that is that it doesn't play well with jarjar and it needs complicated build rules
// to change its default value.
- p.put("android.icu.impl.ICUBinary.dataPath", getenv("ANDROID_ROOT") + "/usr/icu");
+ String icuDataPath = generateIcuDataPath();
+ p.put("android.icu.impl.ICUBinary.dataPath", icuDataPath);
parsePropertyAssignments(p, specialProperties());
@@ -1002,6 +1003,37 @@
return p;
}
+ private static String generateIcuDataPath() {
+ StringBuilder icuDataPathBuilder = new StringBuilder();
+ // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
+ String dataIcuDataPath = getEnvironmentPath("ANDROID_DATA", "/misc/zoneinfo/current/icu");
+ if (dataIcuDataPath != null) {
+ icuDataPathBuilder.append(dataIcuDataPath);
+ }
+
+ // ICU should always look in ANDROID_ROOT.
+ String systemIcuDataPath = getEnvironmentPath("ANDROID_ROOT", "/usr/icu");
+ if (systemIcuDataPath != null) {
+ if (icuDataPathBuilder.length() > 0) {
+ icuDataPathBuilder.append(":");
+ }
+ icuDataPathBuilder.append(systemIcuDataPath);
+ }
+ return icuDataPathBuilder.toString();
+ }
+
+ /**
+ * Creates a path by combining the value of an environment variable with a relative path.
+ * Returns {@code null} if the environment variable is not set.
+ */
+ private static String getEnvironmentPath(String environmentVariable, String path) {
+ String variable = getenv(environmentVariable);
+ if (variable == null) {
+ return null;
+ }
+ return variable + path;
+ }
+
private static Properties initProperties() {
Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
setDefaultChangeableProperties(p);
diff --git a/ojluni/src/main/java/java/net/Socket.java b/ojluni/src/main/java/java/net/Socket.java
index 96a7355..48c87a1 100755
--- a/ojluni/src/main/java/java/net/Socket.java
+++ b/ojluni/src/main/java/java/net/Socket.java
@@ -1052,6 +1052,13 @@
* @since 1.4
*/
public void sendUrgentData (int data) throws IOException {
+ // Android-changed: If the socket is closed, sendUrgentData should not create a new impl.
+ // Fail early to avoid leaking resources.
+ // http://b/31818400
+ if (isClosed()) {
+ throw new SocketException("Socket is closed");
+ }
+
if (!getImpl().supportsUrgentData ()) {
throw new SocketException ("Urgent data not supported");
}
@@ -1375,6 +1382,11 @@
* @see #setTrafficClass(int)
*/
public int getTrafficClass() throws SocketException {
+ // Android-changed: throw SocketException if the socket is already closed. http://b/31818400
+ if (isClosed()) {
+ throw new SocketException("Socket is closed");
+ }
+
return ((Integer) (getImpl().getOption(SocketOptions.IP_TOS))).intValue();
}
diff --git a/ojluni/src/main/java/java/nio/ByteBuffer.java b/ojluni/src/main/java/java/nio/ByteBuffer.java
index 523bbda..1e50922 100644
--- a/ojluni/src/main/java/java/nio/ByteBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBuffer.java
@@ -555,14 +555,14 @@
// isDirect() doesn't imply !hasArray(), ByteBuffer.allocateDirect allocated buffer will
// have a backing, non-gc-movable byte array. JNI allocated direct byte buffers WILL NOT
// have a backing array.
- final Object srcObject = src.isDirect() ? src : src.array();
+ final Object srcObject = src.isDirect() ? src : src.hb;
int srcOffset = src.position();
if (!src.isDirect()) {
srcOffset += src.offset;
}
final ByteBuffer dst = this;
- final Object dstObject = dst.isDirect() ? dst : dst.array();
+ final Object dstObject = dst.isDirect() ? dst : dst.hb;
int dstOffset = dst.position();
if (!dst.isDirect()) {
dstOffset += dst.offset;
diff --git a/ojluni/src/main/java/java/nio/ByteBufferAsCharBuffer.java b/ojluni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
index 0b2c18b..9dbb8a6 100644
--- a/ojluni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
@@ -37,7 +37,7 @@
int mark, int pos, int lim, int cap,
int off, ByteOrder order) {
super(mark, pos, lim, cap);
- this.bb = bb;
+ this.bb = bb.duplicate();
this.isReadOnly = bb.isReadOnly;
// There are only two possibilities for the type of ByteBuffer "bb", viz, DirectByteBuffer and
// HeapByteBuffer. We only have to initialize the field when bb is an instance of
@@ -49,6 +49,7 @@
if (bb instanceof DirectByteBuffer) {
this.address = bb.address + off;
}
+ this.bb.order(order);
this.order = order;
offset = off;
}
diff --git a/ojluni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java b/ojluni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
index 8ffbac5..ff4a6bf 100644
--- a/ojluni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
@@ -38,7 +38,7 @@
int mark, int pos, int lim, int cap,
int off, ByteOrder order) {
super(mark, pos, lim, cap);
- this.bb = bb;
+ this.bb = bb.duplicate();
this.isReadOnly = bb.isReadOnly;
// There are only two possibilities for the type of ByteBuffer "bb", viz, DirectByteBuffer and
// HeapByteBuffer. We only have to initialize the field when bb is an instance of
@@ -50,6 +50,7 @@
if (bb instanceof DirectByteBuffer) {
this.address = bb.address + off;
}
+ this.bb.order(order);
this.order = order;
offset = off;
}
diff --git a/ojluni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java b/ojluni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
index dd16388..8e4e2e8 100644
--- a/ojluni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
@@ -37,7 +37,7 @@
int mark, int pos, int lim, int cap,
int off, ByteOrder order) {
super(mark, pos, lim, cap);
- this.bb = bb;
+ this.bb = bb.duplicate();
this.isReadOnly = bb.isReadOnly;
// There are only two possibilities for the type of ByteBuffer "bb", viz, DirectByteBuffer and
// HeapByteBuffer. We only have to initialize the field when bb is an instance of
@@ -49,6 +49,7 @@
if (bb instanceof DirectByteBuffer) {
this.address = bb.address + off;
}
+ this.bb.order(order);
this.order = order;
offset = off;
}
diff --git a/ojluni/src/main/java/java/nio/ByteBufferAsIntBuffer.java b/ojluni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
index 48eff4b..e340426 100644
--- a/ojluni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
@@ -37,7 +37,7 @@
int mark, int pos, int lim, int cap,
int off, ByteOrder order) {
super(mark, pos, lim, cap);
- this.bb = bb;
+ this.bb = bb.duplicate();
this.isReadOnly = bb.isReadOnly;
// There are only two possibilities for the type of ByteBuffer "bb", viz, DirectByteBuffer and
// HeapByteBuffer. We only have to initialize the field when bb is an instance of
@@ -49,6 +49,7 @@
if (bb instanceof DirectByteBuffer) {
this.address = bb.address + off;
}
+ this.bb.order(order);
this.order = order;
offset = off;
}
diff --git a/ojluni/src/main/java/java/nio/ByteBufferAsLongBuffer.java b/ojluni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
index b2d377b..70f59c9 100644
--- a/ojluni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
@@ -37,7 +37,7 @@
int mark, int pos, int lim, int cap,
int off, ByteOrder order) {
super(mark, pos, lim, cap);
- this.bb = bb;
+ this.bb = bb.duplicate();
this.isReadOnly = bb.isReadOnly;
// There are only two possibilities for the type of ByteBuffer "bb", viz, DirectByteBuffer and
// HeapByteBuffer. We only have to initialize the field when bb is an instance of
@@ -49,6 +49,7 @@
if (bb instanceof DirectByteBuffer) {
this.address = bb.address + off;
}
+ this.bb.order(order);
this.order = order;
offset = off;
}
diff --git a/ojluni/src/main/java/java/nio/ByteBufferAsShortBuffer.java b/ojluni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
index 6527308..5178a7e0 100644
--- a/ojluni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
@@ -37,7 +37,7 @@
int mark, int pos, int lim, int cap,
int off, ByteOrder order) {
super(mark, pos, lim, cap);
- this.bb = bb;
+ this.bb = bb.duplicate();
this.isReadOnly = bb.isReadOnly;
// There are only two possibilities for the type of ByteBuffer "bb", viz, DirectByteBuffer and
// HeapByteBuffer. We only have to initialize the field when bb is an instance of
@@ -49,6 +49,7 @@
if (bb instanceof DirectByteBuffer) {
this.address = bb.address + off;
}
+ this.bb.order(order);
this.order = order;
offset = off;
}
diff --git a/ojluni/src/main/java/java/util/Formatter.java b/ojluni/src/main/java/java/util/Formatter.java
index 9a4e234..c656e9c 100755
--- a/ojluni/src/main/java/java/util/Formatter.java
+++ b/ojluni/src/main/java/java/util/Formatter.java
@@ -4193,6 +4193,14 @@
grpSep = dfs.getGroupingSeparator();
DecimalFormat df = (DecimalFormat) NumberFormat.getIntegerInstance(l);
grpSize = df.getGroupingSize();
+ // Android-changed: http://b/33245708 : Some locales have a group separator but
+ // also patterns without groups. If we do not clear the group separator in these
+ // cases a divide by zero is thrown when determining where to place the
+ // separators.
+ if (!df.isGroupingUsed() || df.getGroupingSize() == 0) {
+ grpSep = '\0';
+ }
+ // Android-changed: end http://b/33245708.
}
}
diff --git a/ojluni/src/main/java/java/util/LinkedHashMap.java b/ojluni/src/main/java/java/util/LinkedHashMap.java
index 0396e8c..2a24a2e 100755
--- a/ojluni/src/main/java/java/util/LinkedHashMap.java
+++ b/ojluni/src/main/java/java/util/LinkedHashMap.java
@@ -33,6 +33,7 @@
import java.util.function.Consumer;
import java.util.function.BiConsumer;
+// Android-added: Note about spliterator order b/33945212 in Android N
/**
* <p>Hash table and linked list implementation of the <tt>Map</tt> interface,
* with predictable iteration order. This implementation differs from
@@ -140,6 +141,23 @@
* returned by all of this class's collection view methods are
* <em><a href="Spliterator.html#binding">late-binding</a></em>,
* <em>fail-fast</em>, and additionally report {@link Spliterator#ORDERED}.
+ * <em>Note</em>: The implementation of these spliterators in Android Nougat
+ * (API levels 24 and 25) uses the wrong order (inconsistent with the
+ * iterators, which use the correct order), despite reporting
+ * {@link Spliterator#ORDERED}. You may use the following code fragments
+ * to obtain a correctly ordered Spliterator on API level 24 and 25:
+ * <ul>
+ * <li>For a Collection view {@code c = lhm.keySet()},
+ * {@code c = lhm.keySet()} or {@code c = lhm.values()}, use
+ * {@code java.util.Spliterators.spliterator(c, c.spliterator().characteristics())}
+ * instead of {@code c.spliterator()}.
+ * <li>Instead of {@code lhm.stream()} or {@code lhm.parallelStream()}, use
+ * {@code java.util.stream.StreamSupport.stream(spliterator, false)}
+ * to construct a (nonparallel) {@link java.util.stream.Stream} from
+ * such a {@code Spliterator}.
+ * </ul>
+ * Note that these workarounds are only suggested where {@code lhm} is a
+ * {@code LinkedHashMap}.
*
* <p>This class is a member of the
* <a href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/collections/index.html">