From cd65f6f9739f4bf09fa9e14433acc203ec3c907f Mon Sep 17 00:00:00 2001 From: Xue-Lei Andrew Fan Date: Thu, 29 Aug 2013 18:58:18 -0700 Subject: [PATCH 01/75] 8023881: IDN.USE_STD3_ASCII_RULES option is too strict to use Unicode in IDN.toASCII Reviewed-by: michaelm --- jdk/src/share/classes/java/net/IDN.java | 40 +++++----- jdk/test/java/net/IDN/UseSTD3ASCIIRules.java | 80 ++++++++++++++++++++ 2 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 jdk/test/java/net/IDN/UseSTD3ASCIIRules.java diff --git a/jdk/src/share/classes/java/net/IDN.java b/jdk/src/share/classes/java/net/IDN.java index ed2f3a38159..34642b9824c 100644 --- a/jdk/src/share/classes/java/net/IDN.java +++ b/jdk/src/share/classes/java/net/IDN.java @@ -292,13 +292,17 @@ public final class IDN { if (useSTD3ASCIIRules) { for (int i = 0; i < dest.length(); i++) { int c = dest.charAt(i); - if (!isLDHChar(c)) { - throw new IllegalArgumentException("Contains non-LDH characters"); + if (isNonLDHAsciiCodePoint(c)) { + throw new IllegalArgumentException( + "Contains non-LDH ASCII characters"); } } - if (dest.charAt(0) == '-' || dest.charAt(dest.length() - 1) == '-') { - throw new IllegalArgumentException("Has leading or trailing hyphen"); + if (dest.charAt(0) == '-' || + dest.charAt(dest.length() - 1) == '-') { + + throw new IllegalArgumentException( + "Has leading or trailing hyphen"); } } @@ -401,26 +405,20 @@ public final class IDN { // // LDH stands for "letter/digit/hyphen", with characters restricted to the // 26-letter Latin alphabet , the digits <0-9>, and the hyphen - // <-> - // non-LDH = 0..0x2C, 0x2E..0x2F, 0x3A..0x40, 0x56..0x60, 0x7B..0x7F + // <->. + // Non LDH refers to characters in the ASCII range, but which are not + // letters, digits or the hypen. // - private static boolean isLDHChar(int ch){ - // high runner case - if(ch > 0x007A){ - return false; - } - //['-' '0'..'9' 'A'..'Z' 'a'..'z'] - if((ch == 0x002D) || - (0x0030 <= ch && ch <= 0x0039) || - (0x0041 <= ch && ch <= 0x005A) || - (0x0061 <= ch && ch <= 0x007A) - ){ - return true; - } - return false; + // non-LDH = 0..0x2C, 0x2E..0x2F, 0x3A..0x40, 0x5B..0x60, 0x7B..0x7F + // + private static boolean isNonLDHAsciiCodePoint(int ch){ + return (0x0000 <= ch && ch <= 0x002C) || + (0x002E <= ch && ch <= 0x002F) || + (0x003A <= ch && ch <= 0x0040) || + (0x005B <= ch && ch <= 0x0060) || + (0x007B <= ch && ch <= 0x007F); } - // // search dots in a string and return the index of that character; // or if there is no dots, return the length of input string diff --git a/jdk/test/java/net/IDN/UseSTD3ASCIIRules.java b/jdk/test/java/net/IDN/UseSTD3ASCIIRules.java new file mode 100644 index 00000000000..0afc4a912ca --- /dev/null +++ b/jdk/test/java/net/IDN/UseSTD3ASCIIRules.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8023881 + * @summary IDN.USE_STD3_ASCII_RULES option is too strict to use Unicode + * in IDN.toASCII + */ + +import java.net.*; + +public class UseSTD3ASCIIRules { + + public static void main(String[] args) throws Exception { + // Per Section 4.1, RFC 3490, if the UseSTD3ASCIIRules flag is set, + // then perform these checks: + // + // (a) Verify the absence of non-LDH ASCII code points; that is, the + // absence of 0..2C, 2E..2F, 3A..40, 5B..60, and 7B..7F. + // + // (b) Verify the absence of leading and trailing hyphen-minus; that + // is, the absence of U+002D at the beginning and end of the + // sequence. + String[] illegalNames = { + "www.example.com-", + "-www.example.com", + "-www.example.com-", + "www.ex\u002Cmple.com", + "www.ex\u007Bmple.com", + "www.ex\u007Fmple.com" + }; + + String[] legalNames = { + "www.ex-ample.com", + "www.ex\u002Dmple.com", // www.ex-mple.com + "www.ex\u007Ample.com", // www.exzmple.com + "www.ex\u3042mple.com", // www.xn--exmple-j43e.com + "www.\u3042\u3044\u3046.com", // www.xn--l8jeg.com + "www.\u793A\u4F8B.com" // www.xn--fsq092h.com + }; + + for (String name : illegalNames) { + try { + System.out.println("Convering illegal IDN: " + name); + IDN.toASCII(name, IDN.USE_STD3_ASCII_RULES); + throw new Exception( + "Expected to get IllegalArgumentException for " + name); + } catch (IllegalArgumentException iae) { + // That's the right behavior. + } + } + + for (String name : legalNames) { + System.out.println("Convering legal IDN: " + name); + System.out.println("\tThe ACE form is: " + + IDN.toASCII(name, IDN.USE_STD3_ASCII_RULES)); + } + } +} From dbca0a2b8590e59b65e4bb9adfff1d92d96f3757 Mon Sep 17 00:00:00 2001 From: Shanliang Jiang Date: Fri, 30 Aug 2013 12:49:41 +0200 Subject: [PATCH 02/75] 6566891: RMIConnector: map value referencing map key in WeakHashMap prevents map entry to be removed Reviewed-by: egahlin, jbachorik, dfuchs, dholmes --- .../management/remote/rmi/RMIConnector.java | 33 +++-- .../RMIConnectorInternalMapTest.java | 122 ++++++++++++++++++ .../RMIConnectorNullSubjectConnTest.java | 105 +++++++++++++++ 3 files changed, 250 insertions(+), 10 deletions(-) create mode 100644 jdk/test/javax/management/remote/mandatory/connection/RMIConnectorInternalMapTest.java create mode 100644 jdk/test/javax/management/remote/mandatory/connection/RMIConnectorNullSubjectConnTest.java diff --git a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java index 53e6754e6ed..4868b94b2ff 100644 --- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java +++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java @@ -405,14 +405,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable throw new IOException("Not connected"); } - MBeanServerConnection rmbsc = rmbscMap.get(delegationSubject); - if (rmbsc != null) { - return rmbsc; - } - - rmbsc = new RemoteMBeanServerConnection(delegationSubject); - rmbscMap.put(delegationSubject, rmbsc); - return rmbsc; + return getConnectionWithSubject(delegationSubject); } public void @@ -1831,7 +1824,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable // Initialization of transient variables. private void initTransients() { - rmbscMap = new WeakHashMap(); + rmbscMap = new WeakHashMap>(); connected = false; terminated = false; @@ -2011,6 +2004,25 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable private final ClassLoader loader; } + private MBeanServerConnection getConnectionWithSubject(Subject delegationSubject) { + MBeanServerConnection conn = null; + + if (delegationSubject == null) { + if (nullSubjectConnRef == null + || (conn = nullSubjectConnRef.get()) == null) { + conn = new RemoteMBeanServerConnection(null); + nullSubjectConnRef = new WeakReference(conn); + } + } else { + WeakReference wr = rmbscMap.get(delegationSubject); + if (wr == null || (conn = wr.get()) == null) { + conn = new RemoteMBeanServerConnection(delegationSubject); + rmbscMap.put(delegationSubject, new WeakReference(conn)); + } + } + return conn; + } + /* The following section of code avoids a class loading problem with RMI. The problem is that an RMI stub, when deserializing @@ -2551,7 +2563,8 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable private transient long clientNotifSeqNo = 0; - private transient WeakHashMap rmbscMap; + private transient WeakHashMap> rmbscMap; + private transient WeakReference nullSubjectConnRef = null; private transient RMINotifClient rmiNotifClient; // = new RMINotifClient(new Integer(0)); diff --git a/jdk/test/javax/management/remote/mandatory/connection/RMIConnectorInternalMapTest.java b/jdk/test/javax/management/remote/mandatory/connection/RMIConnectorInternalMapTest.java new file mode 100644 index 00000000000..86efed134f3 --- /dev/null +++ b/jdk/test/javax/management/remote/mandatory/connection/RMIConnectorInternalMapTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.management.ManagementFactory; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Map; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXPrincipal; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnector; +import javax.security.auth.Subject; + +/* + * @test + * @bug 6566891 + * @summary Check no memory leak on RMIConnector's rmbscMap + * @author Shanliang JIANG + * @run clean RMIConnectorInternalMapTest + * @run build RMIConnectorInternalMapTest + * @run main RMIConnectorInternalMapTest + */ + +public class RMIConnectorInternalMapTest { + public static void main(String[] args) throws Exception { + System.out.println("---RMIConnectorInternalMapTest starting..."); + + JMXConnectorServer connectorServer = null; + JMXConnector connectorClient = null; + + try { + MBeanServer mserver = ManagementFactory.getPlatformMBeanServer(); + JMXServiceURL serverURL = new JMXServiceURL("rmi", "localhost", 0); + connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(serverURL, null, mserver); + connectorServer.start(); + + JMXServiceURL serverAddr = connectorServer.getAddress(); + connectorClient = JMXConnectorFactory.connect(serverAddr, null); + connectorClient.connect(); + + Field rmbscMapField = RMIConnector.class.getDeclaredField("rmbscMap"); + rmbscMapField.setAccessible(true); + Map> map = + (Map>) rmbscMapField.get(connectorClient); + if (map != null && !map.isEmpty()) { // failed + throw new RuntimeException("RMIConnector's rmbscMap must be empty at the initial time."); + } + + Subject delegationSubject = + new Subject(true, + Collections.singleton(new JMXPrincipal("delegate")), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + MBeanServerConnection mbsc1 = + connectorClient.getMBeanServerConnection(delegationSubject); + MBeanServerConnection mbsc2 = + connectorClient.getMBeanServerConnection(delegationSubject); + + if (mbsc1 == null) { + throw new RuntimeException("Got null connection."); + } + if (mbsc1 != mbsc2) { + throw new RuntimeException("Not got same connection with a same subject."); + } + + map = (Map>) rmbscMapField.get(connectorClient); + if (map == null || map.isEmpty()) { // failed + throw new RuntimeException("RMIConnector's rmbscMap has wrong size " + + "after creating a delegated connection."); + } + + delegationSubject = null; + mbsc1 = null; + mbsc2 = null; + + int i = 0; + while (!map.isEmpty() && i++ < 60) { + System.gc(); + Thread.sleep(100); + } + System.out.println("---GC times: " + i); + + if (!map.isEmpty()) { + throw new RuntimeException("Failed to clean RMIConnector's rmbscMap"); + } else { + System.out.println("---RMIConnectorInternalMapTest: PASSED!"); + } + } finally { + try { + connectorClient.close(); + connectorServer.stop(); + } catch (Exception e) { + } + } + } +} diff --git a/jdk/test/javax/management/remote/mandatory/connection/RMIConnectorNullSubjectConnTest.java b/jdk/test/javax/management/remote/mandatory/connection/RMIConnectorNullSubjectConnTest.java new file mode 100644 index 00000000000..7b5224e9b92 --- /dev/null +++ b/jdk/test/javax/management/remote/mandatory/connection/RMIConnectorNullSubjectConnTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.management.ManagementFactory; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnector; + +/* + * @test + * @bug 6566891 + * @summary Check no memory leak on RMIConnector's nullSubjectConn + * @author Shanliang JIANG + * @run clean RMIConnectorNullSubjectConnTest + * @run build RMIConnectorNullSubjectConnTest + * @run main RMIConnectorNullSubjectConnTest + */ + +public class RMIConnectorNullSubjectConnTest { + public static void main(String[] args) throws Exception { + System.out.println("---RMIConnectorNullSubjectConnTest starting..."); + + JMXConnectorServer connectorServer = null; + JMXConnector connectorClient = null; + + try { + MBeanServer mserver = ManagementFactory.getPlatformMBeanServer(); + JMXServiceURL serverURL = new JMXServiceURL("rmi", "localhost", 0); + connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(serverURL, null, mserver); + connectorServer.start(); + + JMXServiceURL serverAddr = connectorServer.getAddress(); + connectorClient = JMXConnectorFactory.connect(serverAddr, null); + connectorClient.connect(); + + Field nullSubjectConnField = RMIConnector.class.getDeclaredField("nullSubjectConnRef"); + nullSubjectConnField.setAccessible(true); + + WeakReference weak = + (WeakReference)nullSubjectConnField.get(connectorClient); + + if (weak != null && weak.get() != null) { + throw new RuntimeException("nullSubjectConnRef must be null at initial time."); + } + + MBeanServerConnection conn1 = connectorClient.getMBeanServerConnection(null); + MBeanServerConnection conn2 = connectorClient.getMBeanServerConnection(null); + if (conn1 == null) { + throw new RuntimeException("A connection with null subject should not be null."); + } else if (conn1 != conn2) { + throw new RuntimeException("The 2 connections with null subject are not equal."); + } + + conn1 = null; + conn2 = null; + int i = 1; + do { + System.gc(); + Thread.sleep(100); + weak = (WeakReference)nullSubjectConnField.get(connectorClient); + } while ((weak != null && weak.get() != null) && i++ < 60); + + System.out.println("---GC times: " + i); + + if (weak != null && weak.get() != null) { + throw new RuntimeException("Failed to clean RMIConnector's nullSubjectConn"); + } else { + System.out.println("---RMIConnectorNullSubjectConnTest: PASSED!"); + } + } finally { + try { + connectorClient.close(); + connectorServer.stop(); + } catch (Exception e) { + } + } + } +} From d6854baa4e31c6d02fa25ca51e6d8dd1b539e6ea Mon Sep 17 00:00:00 2001 From: Dan Xu Date: Fri, 30 Aug 2013 16:45:45 -0700 Subject: [PATCH 03/75] 8023765: Improve MaxPathLength.java testcase and reduce its test load 7160013: java/io/File/MaxPathLength.java fails Reviewed-by: alanb --- jdk/test/ProblemList.txt | 7 --- jdk/test/java/io/File/MaxPathLength.java | 67 ++++++++++++++---------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/jdk/test/ProblemList.txt b/jdk/test/ProblemList.txt index 1f594fe3d3b..26c4eb54a55 100644 --- a/jdk/test/ProblemList.txt +++ b/jdk/test/ProblemList.txt @@ -205,13 +205,6 @@ sun/net/www/http/HttpClient/ProxyTest.java generic-all ############################################################################ -# jdk_io - -# 7160013 -#java/io/File/MaxPathLength.java windows-all - -############################################################################ - # jdk_nio # 6963118 diff --git a/jdk/test/java/io/File/MaxPathLength.java b/jdk/test/java/io/File/MaxPathLength.java index 7ec379cf1d1..9fd6183f36d 100644 --- a/jdk/test/java/io/File/MaxPathLength.java +++ b/jdk/test/java/io/File/MaxPathLength.java @@ -22,7 +22,7 @@ */ /* @test - @bug 4759207 4403166 4165006 4403166 6182812 6274272 + @bug 4759207 4403166 4165006 4403166 6182812 6274272 7160013 @summary Test to see if win32 path length can be greater than 260 */ @@ -37,6 +37,10 @@ public class MaxPathLength { "areallylongfilenamethatsforsur"; private static boolean isWindows = false; + private final static int MAX_LENGTH = 256; + + private static int counter = 0; + public static void main(String[] args) throws Exception { String osName = System.getProperty("os.name"); if (osName.startsWith("Windows")) { @@ -45,33 +49,39 @@ public class MaxPathLength { for (int i = 4; i < 7; i++) { String name = fileName; - while (name.length() < 256) { + while (name.length() < MAX_LENGTH) { testLongPath (i, name, false); testLongPath (i, name, true); - name += "A"; + name = getNextName(name); } } // test long paths on windows + // And these long pathes cannot be handled on Solaris and Mac platforms if (isWindows) { String name = fileName; - while (name.length() < 256) { + while (name.length() < MAX_LENGTH) { testLongPath (20, name, false); testLongPath (20, name, true); - name += "A"; + name = getNextName(name); } } } + private static String getNextName(String fName) { + return (fName.length() < MAX_LENGTH/2) ? fName + fName + : fName + "A"; + } + static void testLongPath(int max, String fn, boolean tryAbsolute) throws Exception { String[] created = new String[max]; String pathString = "."; for (int i = 0; i < max -1; i++) { - pathString = pathString + pathComponent; + pathString = pathString + pathComponent + (counter++); created[max - 1 -i] = pathString; - } + File dirFile = new File(pathString); File f = new File(pathString + sep + fn); @@ -88,9 +98,10 @@ public class MaxPathLength { System.err.println("Warning: Test directory structure exists already!"); return; } - Files.createDirectories(dirFile.toPath()); try { + Files.createDirectories(dirFile.toPath()); + if (tryAbsolute) dirFile = new File(dirFile.getCanonicalPath()); if (!dirFile.isDirectory()) @@ -99,6 +110,7 @@ public class MaxPathLength { if (!f.createNewFile()) { throw new RuntimeException ("File.createNewFile() failed"); } + if (!f.exists()) throw new RuntimeException ("File.exists() failed"); if (!f.isFile()) @@ -107,11 +119,14 @@ public class MaxPathLength { throw new RuntimeException ("File.canRead() failed"); if (!f.canWrite()) throw new RuntimeException ("File.canWrite() failed"); + if (!f.delete()) throw new RuntimeException ("File.delete() failed"); + FileOutputStream fos = new FileOutputStream(f); fos.write(1); fos.close(); + if (f.length() != 1) throw new RuntimeException ("File.length() failed"); long time = System.currentTimeMillis(); @@ -148,30 +163,26 @@ public class MaxPathLength { throw new RuntimeException ("File.renameTo() failed for lenth=" + abPath.length()); } - return; + } else { + if (!nf.canRead()) + throw new RuntimeException ("Renamed file is not readable"); + if (!nf.canWrite()) + throw new RuntimeException ("Renamed file is not writable"); + if (nf.length() != 1) + throw new RuntimeException ("Renamed file's size is not correct"); + if (!nf.renameTo(f)) { + created[0] = nf.getPath(); + } + /* add a script to test these two if we got a regression later + if (!f.setReadOnly()) + throw new RuntimeException ("File.setReadOnly() failed"); + f.deleteOnExit(); + */ } - if (!nf.canRead()) - throw new RuntimeException ("Renamed file is not readable"); - if (!nf.canWrite()) - throw new RuntimeException ("Renamed file is not writable"); - if (nf.length() != 1) - throw new RuntimeException ("Renamed file's size is not correct"); - nf.renameTo(f); - /* add a script to test these two if we got a regression later - if (!f.setReadOnly()) - throw new RuntimeException ("File.setReadOnly() failed"); - f.deleteOnExit(); - */ } finally { // Clean up for (int i = 0; i < max; i++) { - pathString = created[i]; - // Only works with completex canonical paths - File df = new File(pathString); - pathString = df.getCanonicalPath(); - df = new File(pathString); - if (!df.delete()) - System.out.printf("Delete failed->%s\n", pathString); + Files.deleteIfExists((new File(created[i])).toPath()); } } } From 1cff90b3353b16370bd7784ab1f2dd52367d3d1f Mon Sep 17 00:00:00 2001 From: Xue-Lei Andrew Fan Date: Sun, 1 Sep 2013 20:00:03 -0700 Subject: [PATCH 04/75] 8024068: sun/security/ssl/javax/net/ssl/ServerName/IllegalSNIName.java fails Reviewed-by: weijun --- .../security/ssl/javax/net/ssl/ServerName/IllegalSNIName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/IllegalSNIName.java b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/IllegalSNIName.java index 4d5460739cd..a6a6912c0aa 100644 --- a/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/IllegalSNIName.java +++ b/jdk/test/sun/security/ssl/javax/net/ssl/ServerName/IllegalSNIName.java @@ -34,7 +34,7 @@ public class IllegalSNIName { public static void main(String[] args) throws Exception { String[] illegalNames = { - "example\u3003\u3002com", + "example\u3002\u3002com", "example..com", "com\u3002", "com.", From ff0317b09805c47be8bf6a09cc07150c2dcea4e6 Mon Sep 17 00:00:00 2001 From: Chris Hegarty Date: Mon, 2 Sep 2013 14:02:35 +0100 Subject: [PATCH 05/75] 8024103: AtomicLongArray getAndAccumulate/accumulateAndGet have int type for new value arg Reviewed-by: alanb, psandoz --- .../classes/java/util/concurrent/atomic/AtomicLongArray.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdk/src/share/classes/java/util/concurrent/atomic/AtomicLongArray.java b/jdk/src/share/classes/java/util/concurrent/atomic/AtomicLongArray.java index bf7aa6ecca8..28174a586ff 100644 --- a/jdk/src/share/classes/java/util/concurrent/atomic/AtomicLongArray.java +++ b/jdk/src/share/classes/java/util/concurrent/atomic/AtomicLongArray.java @@ -303,7 +303,7 @@ public class AtomicLongArray implements java.io.Serializable { * @return the previous value * @since 1.8 */ - public final long getAndAccumulate(int i, int x, + public final long getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction) { long offset = checkedByteOffset(i); long prev, next; @@ -329,7 +329,7 @@ public class AtomicLongArray implements java.io.Serializable { * @return the updated value * @since 1.8 */ - public final long accumulateAndGet(int i, int x, + public final long accumulateAndGet(int i, long x, LongBinaryOperator accumulatorFunction) { long offset = checkedByteOffset(i); long prev, next; From b81e7785d1fd24ed0833d7b14b04a9383734cfcd Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 2 Sep 2013 18:28:50 +0200 Subject: [PATCH 06/75] 8016127: NLS: logging.properties translatability recommendation 8024131: Issues with cached localizedLevelName in java.util.logging.Level This fix updates logging.properties resource bundles to follow internationalization guidelines. It also fixes a caching issue with localizedLevelName. The regression test that was added needs both fixes to pass. Reviewed-by: mchung, alanb --- .../classes/java/util/logging/Level.java | 72 +++++++++++- .../util/logging/resources/logging.properties | 18 +-- .../logging/resources/logging_de.properties | 18 +-- .../logging/resources/logging_es.properties | 18 +-- .../logging/resources/logging_fr.properties | 18 +-- .../logging/resources/logging_it.properties | 18 +-- .../logging/resources/logging_ja.properties | 12 +- .../logging/resources/logging_ko.properties | 18 +-- .../resources/logging_pt_BR.properties | 18 +-- .../logging/resources/logging_sv.properties | 18 +-- .../resources/logging_zh_CN.properties | 18 +-- .../resources/logging_zh_TW.properties | 12 +- .../java/util/logging/LocalizedLevelName.java | 103 ++++++++++++++++++ 13 files changed, 262 insertions(+), 99 deletions(-) create mode 100644 jdk/test/java/util/logging/LocalizedLevelName.java diff --git a/jdk/src/share/classes/java/util/logging/Level.java b/jdk/src/share/classes/java/util/logging/Level.java index 6847518ce02..936925624e4 100644 --- a/jdk/src/share/classes/java/util/logging/Level.java +++ b/jdk/src/share/classes/java/util/logging/Level.java @@ -27,6 +27,7 @@ package java.util.logging; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; @@ -63,7 +64,7 @@ import java.util.ResourceBundle; */ public class Level implements java.io.Serializable { - private static String defaultBundle = "sun.util.logging.resources.logging"; + private static final String defaultBundle = "sun.util.logging.resources.logging"; /** * @serial The non-localized name of the level. @@ -81,7 +82,8 @@ public class Level implements java.io.Serializable { private final String resourceBundleName; // localized level name - private String localizedLevelName; + private transient String localizedLevelName; + private transient Locale cachedLocale; /** * OFF is a special level that can be used to turn off logging. @@ -209,6 +211,7 @@ public class Level implements java.io.Serializable { this.value = value; this.resourceBundleName = resourceBundleName; this.localizedLevelName = resourceBundleName == null ? name : null; + this.cachedLocale = null; KnownLevel.add(this); } @@ -250,17 +253,71 @@ public class Level implements java.io.Serializable { return this.name; } - final synchronized String getLocalizedLevelName() { + private String computeLocalizedLevelName(Locale newLocale) { + ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName, newLocale); + final String localizedName = rb.getString(name); + + final boolean isDefaultBundle = defaultBundle.equals(resourceBundleName); + if (!isDefaultBundle) return localizedName; + + // This is a trick to determine whether the name has been translated + // or not. If it has not been translated, we need to use Locale.ROOT + // when calling toUpperCase(). + final Locale rbLocale = rb.getLocale(); + final Locale locale = + Locale.ROOT.equals(rbLocale) + || name.equals(localizedName.toUpperCase(Locale.ROOT)) + ? Locale.ROOT : rbLocale; + + // ALL CAPS in a resource bundle's message indicates no translation + // needed per Oracle translation guideline. To workaround this + // in Oracle JDK implementation, convert the localized level name + // to uppercase for compatibility reason. + return Locale.ROOT.equals(locale) ? name : localizedName.toUpperCase(locale); + } + + // Avoid looking up the localizedLevelName twice if we already + // have it. + final String getCachedLocalizedLevelName() { + if (localizedLevelName != null) { - return localizedLevelName; + if (cachedLocale != null) { + if (cachedLocale.equals(Locale.getDefault())) { + // OK: our cached value was looked up with the same + // locale. We can use it. + return localizedLevelName; + } + } } + if (resourceBundleName == null) { + // No resource bundle: just use the name. + return name; + } + + // We need to compute the localized name. + // Either because it's the first time, or because our cached + // value is for a different locale. Just return null. + return null; + } + + final synchronized String getLocalizedLevelName() { + + // See if we have a cached localized name + final String cachedLocalizedName = getCachedLocalizedLevelName(); + if (cachedLocalizedName != null) { + return cachedLocalizedName; + } + + // No cached localized name or cache invalid. + // Need to compute the localized name. + final Locale newLocale = Locale.getDefault(); try { - ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName); - localizedLevelName = rb.getString(name); + localizedLevelName = computeLocalizedLevelName(newLocale); } catch (Exception ex) { localizedLevelName = name; } + cachedLocale = newLocale; return localizedLevelName; } @@ -318,6 +375,7 @@ public class Level implements java.io.Serializable { * * @return the non-localized name of the Level, for example "INFO". */ + @Override public final String toString() { return name; } @@ -420,6 +478,7 @@ public class Level implements java.io.Serializable { * Compare two objects for value equality. * @return true if and only if the two objects have the same level value. */ + @Override public boolean equals(Object ox) { try { Level lx = (Level)ox; @@ -433,6 +492,7 @@ public class Level implements java.io.Serializable { * Generate a hashcode. * @return a hashcode based on the level value */ + @Override public int hashCode() { return this.value; } diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging.properties b/jdk/src/share/classes/sun/util/logging/resources/logging.properties index da17c47f8fd..248b4d7fcbe 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=All # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Severe # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Warning # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Info # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Config # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Fine # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Finer # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=Finest # The following ALL CAPS words should be translated. -OFF=OFF +OFF=Off diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_de.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_de.properties index da17c47f8fd..1aa8ef4e22c 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_de.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_de.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=Alle # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Schwerwiegend # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Warnung # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Information # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Konfiguration # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Fein # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Feiner # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=Am feinsten # The following ALL CAPS words should be translated. -OFF=OFF +OFF=Deaktiviert diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_es.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_es.properties index da17c47f8fd..90de2e88238 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_es.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_es.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=Todo # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Grave # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Advertencia # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Informaci\u00F3n # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Configurar # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Detallado # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Muy Detallado # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=M\u00E1s Detallado # The following ALL CAPS words should be translated. -OFF=OFF +OFF=Desactivado diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_fr.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_fr.properties index da17c47f8fd..af34d6fa414 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_fr.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_fr.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=Tout # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Grave # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Avertissement # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Infos # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Config # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Pr\u00E9cis # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Plus pr\u00E9cis # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=Le plus pr\u00E9cis # The following ALL CAPS words should be translated. -OFF=OFF +OFF=D\u00E9sactiv\u00E9 diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_it.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_it.properties index da17c47f8fd..73a3b5c59cf 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_it.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_it.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=Tutto # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Grave # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Avvertenza # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Informazioni # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Configurazione # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Buono # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Migliore # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=Ottimale # The following ALL CAPS words should be translated. -OFF=OFF +OFF=Non attivo diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_ja.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_ja.properties index 980c33549c5..60358d1c86e 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_ja.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_ja.properties @@ -29,18 +29,18 @@ # The following ALL CAPS words should be translated. ALL=\u3059\u3079\u3066 # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=\u91CD\u5927 # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=\u8B66\u544A # The following ALL CAPS words should be translated. INFO=\u60C5\u5831 # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= \u69CB\u6210 # The following ALL CAPS words should be translated. -FINE=\u8A73\u7D30\u30EC\u30D9\u30EB(\u4F4E) +FINE=\u666E\u901A # The following ALL CAPS words should be translated. -FINER=FINER +FINER=\u8A73\u7D30 # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=\u6700\u3082\u8A73\u7D30 # The following ALL CAPS words should be translated. OFF=\u30AA\u30D5 diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_ko.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_ko.properties index da17c47f8fd..6d5dc551e67 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_ko.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_ko.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=\uBAA8\uB450 # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=\uC2EC\uAC01 # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=\uACBD\uACE0 # The following ALL CAPS words should be translated. -INFO=INFO +INFO=\uC815\uBCF4 # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= \uAD6C\uC131 # The following ALL CAPS words should be translated. -FINE=FINE +FINE=\uBBF8\uC138 # The following ALL CAPS words should be translated. -FINER=FINER +FINER=\uBCF4\uB2E4 \uBBF8\uC138 # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=\uAC00\uC7A5 \uBBF8\uC138 # The following ALL CAPS words should be translated. -OFF=OFF +OFF=\uD574\uC81C diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_pt_BR.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_pt_BR.properties index da17c47f8fd..29229f2c7c1 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_pt_BR.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_pt_BR.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=Tudo # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Grave # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Advert\u00EAncia # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Informa\u00E7\u00F5es # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Configura\u00E7\u00E3o # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Detalhado # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Mais Detalhado # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=O Mais Detalhado # The following ALL CAPS words should be translated. -OFF=OFF +OFF=Desativado diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_sv.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_sv.properties index 4c8dd1d2dcb..b7607863ffb 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_sv.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_sv.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALLA +ALL=Alla # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=Allvarlig # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=Varning # The following ALL CAPS words should be translated. -INFO=INFO +INFO=Info # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= Konfig # The following ALL CAPS words should be translated. -FINE=FINE +FINE=Fin # The following ALL CAPS words should be translated. -FINER=FINER +FINER=Finare # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=Finaste # The following ALL CAPS words should be translated. -OFF=OFF +OFF=Av diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_zh_CN.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_zh_CN.properties index da17c47f8fd..67dd2b8b50a 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_zh_CN.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_zh_CN.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=ALL +ALL=\u5168\u90E8 # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=\u4E25\u91CD # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=\u8B66\u544A # The following ALL CAPS words should be translated. -INFO=INFO +INFO=\u4FE1\u606F # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= \u914D\u7F6E # The following ALL CAPS words should be translated. -FINE=FINE +FINE=\u8BE6\u7EC6 # The following ALL CAPS words should be translated. -FINER=FINER +FINER=\u8F83\u8BE6\u7EC6 # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=\u975E\u5E38\u8BE6\u7EC6 # The following ALL CAPS words should be translated. -OFF=OFF +OFF=\u7981\u7528 diff --git a/jdk/src/share/classes/sun/util/logging/resources/logging_zh_TW.properties b/jdk/src/share/classes/sun/util/logging/resources/logging_zh_TW.properties index d7ad070a69e..4875bc825c1 100644 --- a/jdk/src/share/classes/sun/util/logging/resources/logging_zh_TW.properties +++ b/jdk/src/share/classes/sun/util/logging/resources/logging_zh_TW.properties @@ -27,20 +27,20 @@ # these are the same as the non-localized level name. # The following ALL CAPS words should be translated. -ALL=\u6240\u6709 +ALL=\u5168\u90E8 # The following ALL CAPS words should be translated. -SEVERE=SEVERE +SEVERE=\u56B4\u91CD # The following ALL CAPS words should be translated. -WARNING=WARNING +WARNING=\u8B66\u544A # The following ALL CAPS words should be translated. INFO=\u8CC7\u8A0A # The following ALL CAPS words should be translated. -CONFIG= CONFIG +CONFIG= \u7D44\u614B # The following ALL CAPS words should be translated. FINE=\u8A73\u7D30 # The following ALL CAPS words should be translated. -FINER=FINER +FINER=\u8F03\u8A73\u7D30 # The following ALL CAPS words should be translated. -FINEST=FINEST +FINEST=\u6700\u8A73\u7D30 # The following ALL CAPS words should be translated. OFF=\u95DC\u9589 diff --git a/jdk/test/java/util/logging/LocalizedLevelName.java b/jdk/test/java/util/logging/LocalizedLevelName.java new file mode 100644 index 00000000000..cdb425a294f --- /dev/null +++ b/jdk/test/java/util/logging/LocalizedLevelName.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.*; +import java.util.logging.*; + +/* + * @test + * @bug 8016127 8024131 + * @summary test logging.properties localized + * @run main/othervm LocalizedLevelName + */ + +public class LocalizedLevelName { + private static Object[] namesMap = { + "SEVERE", Locale.ENGLISH, "Severe", Level.SEVERE, + "WARNING", Locale.FRENCH, "Avertissement", Level.WARNING, + "INFO", Locale.ITALIAN, "Informazioni", Level.INFO, + "SEVERE", Locale.FRENCH, "Grave", Level.SEVERE, + "CONFIG", Locale.GERMAN, "Konfiguration", Level.CONFIG, + "ALL", Locale.ROOT, "All", Level.ALL, + "SEVERE", Locale.ROOT, "Severe", Level.SEVERE, + "WARNING", Locale.ROOT, "Warning", Level.WARNING, + "CONFIG", Locale.ROOT, "Config", Level.CONFIG, + "INFO", Locale.ROOT, "Info", Level.INFO, + "FINE", Locale.ROOT, "Fine", Level.FINE, + "FINER", Locale.ROOT, "Finer", Level.FINER, + "FINEST", Locale.ROOT, "Finest", Level.FINEST + }; + + public static void main(String args[]) throws Exception { + Locale defaultLocale = Locale.getDefault(); + for (int i=0; i localized(" + Locale.ENGLISH + ", " + + key + ")=" + en); + System.out.println(" => localized(" + locale + ", " + key + + ")=" + other); + if (!key.equals(en.toUpperCase(Locale.ROOT))) { + throw new RuntimeException("Expect " + key + + " equals upperCase(" + en + ")"); + } + if (!Locale.ENGLISH.equals(locale) && !Locale.ROOT.equals(locale) + && key.equals(other.toUpperCase(Locale.ROOT))) { + throw new RuntimeException("Expect " + key + + " not equals upperCase(" + other +")"); + } + if ((Locale.ENGLISH.equals(locale) || Locale.ROOT.equals(locale)) + && !key.equals(other.toUpperCase(Locale.ROOT))) { + throw new RuntimeException("Expect " + key + + " equals upperCase(" + other +")"); + } + if (!other.equals(expectedTranslation)) { + throw new RuntimeException("Expected \"" + expectedTranslation + + "\" for '" + locale + "' but got \"" + other + "\""); + } + Locale.setDefault(locale); + final String levelName = level.getLocalizedName(); + System.out.println("Level.getLocalizedName() is: " + levelName); + if (!levelName.equals(other.toUpperCase(locale))) { + throw new RuntimeException("Expected \"" + + other.toUpperCase(locale) + "\" for '" + + locale + "' but got \"" + levelName + "\""); + } + Locale.setDefault(defaultLocale); + } + } + + private static final String RBNAME = "sun.util.logging.resources.logging"; + private static String getLocalizedMessage(Locale locale, String key) { + ResourceBundle rb = ResourceBundle.getBundle(RBNAME, locale); + return rb.getString(key); + } +} From f4dda09731a2ceb1a6a633589f14a7a68828eeeb Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 2 Sep 2013 16:03:34 +0200 Subject: [PATCH 07/75] 7172176: java/jconsole test/sun/tools/jconsole/ImmutableResourceTest.sh failing Reviewed-by: mchung, mfang --- .../classes/sun/tools/jconsole/Resources.java | 6 +- jdk/test/ProblemList.txt | 4 - .../tools/jconsole/ImmutableResourceTest.java | 60 --- .../tools/jconsole/ImmutableResourceTest.sh | 111 ----- .../sun/tools/jconsole/ResourceCheckTest.java | 469 +++++------------- .../sun/tools/jconsole/ResourceCheckTest.sh | 4 +- 6 files changed, 118 insertions(+), 536 deletions(-) delete mode 100644 jdk/test/sun/tools/jconsole/ImmutableResourceTest.java delete mode 100644 jdk/test/sun/tools/jconsole/ImmutableResourceTest.sh diff --git a/jdk/src/share/classes/sun/tools/jconsole/Resources.java b/jdk/src/share/classes/sun/tools/jconsole/Resources.java index 785be24d2e6..20af2e9f9e9 100644 --- a/jdk/src/share/classes/sun/tools/jconsole/Resources.java +++ b/jdk/src/share/classes/sun/tools/jconsole/Resources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.text.MessageFormat; import java.util.Collections; -import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -40,7 +40,7 @@ import java.util.ResourceBundle; */ public final class Resources { private static Map MNEMONIC_LOOKUP = Collections - .synchronizedMap(new HashMap()); + .synchronizedMap(new IdentityHashMap()); private Resources() { throw new AssertionError(); diff --git a/jdk/test/ProblemList.txt b/jdk/test/ProblemList.txt index 26c4eb54a55..1ac61aa2c3f 100644 --- a/jdk/test/ProblemList.txt +++ b/jdk/test/ProblemList.txt @@ -305,10 +305,6 @@ sun/security/krb5/auto/BadKdc4.java solaris-sparcv9 # 6461635 com/sun/tools/attach/BasicTests.sh generic-all -# 7172176 -sun/tools/jconsole/ResourceCheckTest.sh generic-all -sun/tools/jconsole/ImmutableResourceTest.sh generic-all - # 7132203 sun/jvmstat/monitor/MonitoredVm/CR6672135.java generic-all diff --git a/jdk/test/sun/tools/jconsole/ImmutableResourceTest.java b/jdk/test/sun/tools/jconsole/ImmutableResourceTest.java deleted file mode 100644 index 082776667a4..00000000000 --- a/jdk/test/sun/tools/jconsole/ImmutableResourceTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2005, 2007, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * - * - * This isn't the test case: ImmutableResourceTest.sh is. - * Refer to ImmutableResourceTest.sh when running this test. - * - * @bug 6287579 - * @summary SubClasses of ListResourceBundle should fix getContents() - */ -import java.util.ResourceBundle; - -public class ImmutableResourceTest { - - public static void main(String[] args) throws Exception { - - /* Reach under the covers and get the message strings */ - sun.tools.jconsole.resources.JConsoleResources jcr = - new sun.tools.jconsole.resources.JConsoleResources (); - Object [][] testData = jcr.getContents(); - - /* Shred our copy of the message strings */ - for (int ii = 0; ii < testData.length; ii++) { - testData[ii][0] = "xxx"; - testData[ii][1] = "yyy"; - } - - /* - * Try a lookup for the shredded key. - * If this is successful we have a problem. - */ - String ss = sun.tools.jconsole.Resources.getText("xxx"); - if ("yyy".equals(ss)) { - throw new Exception ("SubClasses of ListResourceBundle should fix getContents()"); - } - System.out.println("...Finished."); - } -} diff --git a/jdk/test/sun/tools/jconsole/ImmutableResourceTest.sh b/jdk/test/sun/tools/jconsole/ImmutableResourceTest.sh deleted file mode 100644 index dedf21271b0..00000000000 --- a/jdk/test/sun/tools/jconsole/ImmutableResourceTest.sh +++ /dev/null @@ -1,111 +0,0 @@ -# -# Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. -# -# This code is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# version 2 for more details (a copy is included in the LICENSE file that -# accompanied this code). -# -# You should have received a copy of the GNU General Public License version -# 2 along with this work; if not, write to the Free Software Foundation, -# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -# or visit www.oracle.com if you need additional information or have any -# questions. -# - -# @test -# @bug 6287579 -# @summary SubClasses of ListResourceBundle should fix getContents() -# -# @run shell ImmutableResourceTest.sh - -# Beginning of subroutines: -status=1 - -#Call this from anywhere to fail the test with an error message -# usage: fail "reason why the test failed" -fail() - { echo "The test failed :-(" - echo "$*" 1>&2 - echo "exit status was $status" - exit $status - } #end of fail() - -#Call this from anywhere to pass the test with a message -# usage: pass "reason why the test passed if applicable" -pass() - { echo "The test passed!!!" - echo "$*" 1>&2 - exit 0 - } #end of pass() - -# end of subroutines - -# The beginning of the script proper - -OS=`uname -s` -case "$OS" in - SunOS | Linux | Darwin ) - PATHSEP=":" - ;; - - Windows* | CYGWIN*) - PATHSEP=";" - ;; - - # catch all other OSs - * ) - echo "Unrecognized system! $OS" - fail "Unrecognized system! $OS" - ;; -esac - -TARGETCLASS="ImmutableResourceTest" -if [ -z "${TESTJAVA}" ] ; then - # TESTJAVA is not set, so the test is running stand-alone. - # TESTJAVA holds the path to the root directory of the build of the JDK - # to be tested. That is, any java files run explicitly in this shell - # should use TESTJAVA in the path to the java interpreter. - # So, we'll set this to the JDK spec'd on the command line. If none - # is given on the command line, tell the user that and use a default. - # THIS IS THE JDK BEING TESTED. - if [ -n "$1" ] ; then - TESTJAVA=$1 - else - TESTJAVA=$JAVA_HOME - fi - TESTSRC=. - TESTCLASSES=. - #Deal with .class files: -fi -# -echo "JDK under test is: $TESTJAVA" -# -CP="-classpath ${TESTCLASSES}${PATHSEP}${TESTJAVA}/lib/jconsole.jar" -# Compile the test class using the classpath we need: -# -env -# -set -vx -# -#Compile. jconsole.jar is required on the classpath. -${TESTJAVA}/bin/javac -d "${TESTCLASSES}" ${CP} -g \ - "${TESTSRC}"/"${TARGETCLASS}".java -# -#Run the test class, again with the classpath we need: -${TESTJAVA}/bin/java ${CP} ${TARGETCLASS} -status=$? -echo "test status was: $status" -if [ $status -eq "0" ]; - then pass "" - - else fail "unspecified test failure" -fi diff --git a/jdk/test/sun/tools/jconsole/ResourceCheckTest.java b/jdk/test/sun/tools/jconsole/ResourceCheckTest.java index 6d4e27be860..1ed5bb642c7 100644 --- a/jdk/test/sun/tools/jconsole/ResourceCheckTest.java +++ b/jdk/test/sun/tools/jconsole/ResourceCheckTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,377 +27,134 @@ * This isn't the test case: ResourceCheckTest.sh is. * Refer to ResourceCheckTest.sh when running this test. * - * @bug 5008856 5023573 5024917 5062569 + * @bug 5008856 5023573 5024917 5062569 7172176 * @summary 'missing resource key' error for key = "Operating system" */ -import java.awt.event.KeyEvent; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import sun.tools.jconsole.Messages; import sun.tools.jconsole.Resources; +/* + * Ensures that there is a one-to-one mapping between constants in the + * Message class and the keys in the sun.tools.jconsole.resources.messages + * bundle. + * + * An error will be thrown if there is a: + * + * - key in the resource bundle that doesn't have a public static field with + * the same name in the Message class. + * + * - public static field in the Message class that doesn't have a key with + * the same name in the resource bundle. + * + * - message with a mnemonic identifier(&) for which a mnemonic can't + * be looked up using Resources#getMnemonicInt(). + * + */ public class ResourceCheckTest { + private static final String MISSING_RESOURCE_KEY_PREFIX = "missing message for"; + private static final String RESOURCE_BUNDLE = "sun.tools.jconsole.resources.messages"; + private static final String NEW_LINE = String.format("%n"); - public static void main(String[] args){ - Object [][] testData = { - {"<", "", "", "", ""}, - {"<<", "", "", "", ""}, - {">", "", "", "", ""}, - {" 1 day", "", "", "", ""}, - {" 1 hour", "", "", "", ""}, - {" 1 min", "", "", "", ""}, - {" 1 month", "", "", "", ""}, - {" 1 year", "", "", "", ""}, - {" 2 hours", "", "", "", ""}, - {" 3 hours", "", "", "", ""}, - {" 3 months", "", "", "", ""}, - {" 5 min", "", "", "", ""}, - {" 6 hours", "", "", "", ""}, - {" 6 months", "", "", "", ""}, - {" 7 days", "", "", "", ""}, - {"10 min", "", "", "", ""}, - {"12 hours", "", "", "", ""}, - {"30 min", "", "", "", ""}, - {"ACTION", "", "", "", ""}, - {"ACTION_INFO", "", "", "", ""}, - {"All", "", "", "", ""}, - {"Architecture", "", "", "", ""}, - {"Attribute", "", "", "", ""}, - {"Attribute value", "", "", "", ""}, - {"Attribute values", "", "", "", ""}, - {"Attributes", "", "", "", ""}, - {"Blank", "", "", "", ""}, - {"BlockedCount WaitedCount", "BlockedCount", "WaitedCount", "", ""}, - {"Boot class path", "", "", "", ""}, - {"BorderedComponent.moreOrLessButton.toolTip", "", "", "", ""}, - {"Close", "", "", "", ""}, - {"CPU Usage", "", "", "", ""}, - {"CPUUsageFormat","PhonyPercentage", "", "", ""}, - {"Cancel", "", "", "", ""}, - {"Cascade", "", "", "", ""}, - {"Cascade.mnemonic", "", "", "", ""}, - {"Chart:", "", "", "", ""}, - {"Chart:.mnemonic", "", "", "", ""}, - {"ClassTab.infoLabelFormat", "LoadedCount", "UnloadedCount", "TotalCount", ""}, - {"ClassTab.loadedClassesPlotter.accessibleName", "", "", "", ""}, - {"Class path", "", "", "", ""}, - {"Classes", "", "", "", ""}, - {"ClassName", "", "", "", ""}, - {"Column.Name", "", "", "", ""}, - {"Column.PID", "", "", "", ""}, - {"Committed", "", "", "", ""}, - {"Committed memory", "", "", "", ""}, - {"Committed virtual memory", "", "", "", ""}, - {"Compiler", "", "", "", ""}, - {"Connect...", "", "", "", ""}, - {"Connect", "", "", "", ""}, - {"Connect.mnemonic", "", "", "", ""}, - {"ConnectDialog.connectButton.toolTip", "", "", "", ""}, - {"ConnectDialog.accessibleDescription", "", "", "", ""}, - {"ConnectDialog.masthead.accessibleName", "", "", "", ""}, - {"ConnectDialog.masthead.title", "", "", "", ""}, - {"ConnectDialog.statusBar.accessibleName", "", "", "", ""}, - {"ConnectDialog.title", "", "", "", ""}, - {"Connected. Click to disconnect.", "", "", "", ""}, - {"connectingTo1", "PhonyConnectionName", "", "", ""}, - {"connectingTo2", "PhonyConnectionName", "", "", ""}, - {"connectionFailed1", "", "", "", ""}, - {"connectionFailed2", "PhonyConnectionName", "", "", ""}, - {"connectionLost1", "", "", "", ""}, - {"connectionLost2", "PhonyConnectionName", "", "", ""}, - {"Connection failed", "", "", "", ""}, - {"Connection", "", "", "", ""}, - {"Connection.mnemonic", "", "", "", ""}, - {"Connection name", "", "", "", ""}, - {"ConnectionName (disconnected)", "Phony", "Phony", "", ""}, - {"Constructor", "", "", "", ""}, - {"Create", "Phony", "Phony", "", ""}, - {"Current classes loaded", "", "", "", ""}, - {"Current heap size", "", "", "", ""}, - {"Current value", "PhonyValue", "", "", ""}, - {"Daemon threads", "", "", "", ""}, - {"deadlockAllTab", "", "", "", ""}, - {"deadlockTab", "", "", "", ""}, - {"deadlockTabN", "PhonyInt", "", "", ""}, - {"Description", "", "", "", ""}, - {"Descriptor", "", "", "", ""}, - {"Details", "", "", "", ""}, - {"Detect Deadlock", "", "", "", ""}, - {"Detect Deadlock.mnemonic", "", "", "", ""}, - {"Detect Deadlock.toolTip", "", "", "", ""}, - {"Dimension is not supported:", "", "", "", ""}, - {"Discard chart", "", "", "", ""}, - {"Disconnected. Click to connect.", "", "", "", ""}, - {"Double click to expand/collapse", "", "", "", ""}, - {"Double click to visualize", "", "", "", ""}, - {"DurationDaysHoursMinutes", 0, 13, 54, ""}, - {"DurationDaysHoursMinutes", 1, 13, 54, ""}, - {"DurationDaysHoursMinutes", 2, 13, 54, ""}, - {"DurationDaysHoursMinutes", 1024, 13, 45, ""}, - {"DurationHoursMinutes", 0, 13, "", ""}, - {"DurationHoursMinutes", 1, 0, "", ""}, - {"DurationHoursMinutes", 1, 1, "", ""}, - {"DurationHoursMinutes", 2, 42, "", ""}, - {"DurationMinutes", 0, "", "", ""}, - {"DurationMinutes", 1, "", "", ""}, - {"DurationMinutes", 2, "", "", ""}, - {"DurationSeconds", 0, "", "", ""}, - {"DurationSeconds", 1, "", "", ""}, - {"DurationSeconds", 2, "", "", ""}, - {"Empty array", "", "", "", ""}, - {"Error", "", "", "", ""}, - {"Error: MBeans already exist", "", "", "", ""}, - {"Error: MBeans do not exist", "", "", "", ""}, - {"Event", "", "", "", ""}, - {"Exit", "", "", "", ""}, - {"Exit.mnemonic", "", "", "", ""}, - {"expand", "", "", "", ""}, - {"Fail to load plugin", "", "", "", ""}, - {"FileChooser.fileExists.cancelOption", "", "", "", ""}, - {"FileChooser.fileExists.message", "PhonyFileName", "", "", ""}, - {"FileChooser.fileExists.okOption", "", "", "", ""}, - {"FileChooser.fileExists.title", "", "", "", ""}, - {"FileChooser.savedFile", "PhonyFilePath", "PhonyFileSize", "", ""}, - {"FileChooser.saveFailed.message", "PhonyFilePath", "PhonyMessage", "", ""}, - {"FileChooser.saveFailed.title", "", "", "", ""}, - {"Free physical memory", "", "", "", ""}, - {"Free swap space", "", "", "", ""}, - {"Garbage collector", "", "", "", ""}, - {"GC time", "", "", "", ""}, - {"GC time details", 54, "Phony", 11, ""}, - {"GcInfo", "Phony", -1, 768, ""}, - {"GcInfo", "Phony", 0, 768, ""}, - {"GcInfo", "Phony", 1, 768, ""}, - {"Heap", "", "", "", ""}, - {"Heap Memory Usage", "", "", "", ""}, - {"Help.AboutDialog.accessibleDescription", "", "", "", ""}, - {"Help.AboutDialog.jConsoleVersion", "DummyVersion", "", "", ""}, - {"Help.AboutDialog.javaVersion", "DummyVersion", "", "", ""}, - {"Help.AboutDialog.masthead.accessibleName", "", "", "", ""}, - {"Help.AboutDialog.masthead.title", "", "", "", ""}, - {"Help.AboutDialog.title", "", "", "", ""}, - {"Help.AboutDialog.userGuideLink", "DummyMessage", "", "", ""}, - {"Help.AboutDialog.userGuideLink.mnemonic", "", "", "", ""}, - {"Help.AboutDialog.userGuideLink.url", "DummyURL", "", "", ""}, - {"HelpMenu.About.title", "", "", "", ""}, - {"HelpMenu.About.title.mnemonic", "", "", "", ""}, - {"HelpMenu.UserGuide.title", "", "", "", ""}, - {"HelpMenu.UserGuide.title.mnemonic", "", "", "", ""}, - {"HelpMenu.title", "", "", "", ""}, - {"HelpMenu.title.mnemonic", "", "", "", ""}, - {"Hotspot MBeans...", "", "", "", ""}, - {"Hotspot MBeans....mnemonic", "", "", "", ""}, - {"Hotspot MBeans.dialog.accessibleDescription", "", "", "", ""}, - {"Impact", "", "", "", ""}, - {"Info", "", "", "", ""}, - {"INFO", "", "", "", ""}, - {"Invalid plugin path", "", "", "", ""}, - {"Invalid URL", "", "", "", ""}, - {"Is", "", "", "", ""}, - {"Java Monitoring & Management Console", "", "", "", ""}, - {"Java Virtual Machine", "", "", "", ""}, - {"JConsole: ", "", "", "", ""}, - {"JConsole.accessibleDescription", "", "", "", ""}, - {"JConsole version", "PhonyVersion", "", "", ""}, - {"JIT compiler", "", "", "", ""}, - {"Library path", "", "", "", ""}, - {"Live Threads", "", "", "", ""}, - {"Loaded", "", "", "", ""}, - {"Local Process:", "", "", "", ""}, - {"Local Process:.mnemonic", "", "", "", ""}, - {"Manage Hotspot MBeans in: ", "", "", "", ""}, - {"Management Not Enabled", "", "", "", ""}, - {"Management Will Be Enabled", "", "", "", ""}, - {"Masthead.font", "", "", "", ""}, - {"Max", "", "", "", ""}, - {"Max", "", "", "", ""}, - {"Maximum heap size", "", "", "", ""}, - {"MBeanAttributeInfo", "", "", "", ""}, - {"MBeanInfo", "", "", "", ""}, - {"MBeanNotificationInfo", "", "", "", ""}, - {"MBeanOperationInfo", "", "", "", ""}, - {"MBeans", "", "", "", ""}, - {"MBeansTab.clearNotificationsButton", "", "", "", ""}, - {"MBeansTab.clearNotificationsButton.mnemonic", "", "", "", ""}, - {"MBeansTab.clearNotificationsButton.toolTip", "", "", "", ""}, - {"MBeansTab.compositeNavigationMultiple", 0, 0, "", ""}, - {"MBeansTab.compositeNavigationSingle", "", "", "", ""}, - {"MBeansTab.refreshAttributesButton", "", "", "", ""}, - {"MBeansTab.refreshAttributesButton.mnemonic", "", "", "", ""}, - {"MBeansTab.refreshAttributesButton.toolTip", "", "", "", ""}, - {"MBeansTab.subscribeNotificationsButton", "", "", "", ""}, - {"MBeansTab.subscribeNotificationsButton.mnemonic", "", "", "", ""}, - {"MBeansTab.subscribeNotificationsButton.toolTip", "", "", "", ""}, - {"MBeansTab.tabularNavigationMultiple", 0, 0, "", ""}, - {"MBeansTab.tabularNavigationSingle", "", "", "", ""}, - {"MBeansTab.unsubscribeNotificationsButton", "", "", "", ""}, - {"MBeansTab.unsubscribeNotificationsButton.mnemonic", "", "", "", ""}, - {"MBeansTab.unsubscribeNotificationsButton.toolTip", "", "", "", ""}, - {"Memory", "", "", "", ""}, - {"MemoryPoolLabel", "PhonyMemoryPool", "", "", ""}, - {"MemoryTab.heapPlotter.accessibleName", "", "", "", ""}, - {"MemoryTab.infoLabelFormat", "UsedCount", "CommittedCount", "MaxCount", ""}, - {"MemoryTab.nonHeapPlotter.accessibleName", "", "", "", ""}, - {"MemoryTab.poolChart.aboveThreshold", "Threshold", "", "", ""}, - {"MemoryTab.poolChart.accessibleName", "", "", "", ""}, - {"MemoryTab.poolChart.belowThreshold", "Threshold", "", "", ""}, - {"MemoryTab.poolPlotter.accessibleName", "PhonyMemoryPool", "", "", ""}, - {"Message", "", "", "", ""}, - {"Method successfully invoked", "", "", "", ""}, - {"Monitor locked", "", "", "", ""}, - {"Minimize All", "", "", "", ""}, - {"Minimize All.mnemonic", "", "", "", ""}, - {"Name", "", "", "", ""}, - {"Name and Build", "PhonyName", "PhonyBuild", "", ""}, - {"Name Build and Mode", "PhonyName", "PhonyBuild", "PhonyMode", ""}, - {"Name State", "PhonyName", "PhonyState", "", ""}, - {"Name State LockName", "PhonyName", "PhonyState", "PhonyLock", ""}, - {"Name State LockName LockOwner", "PhonyName", "PhonyState", "PhonyLock", "PhonyOwner"}, - {"New Connection...", "", "", "", ""}, - {"New Connection....mnemonic", "", "", "", ""}, - {"No deadlock detected", "", "", "", ""}, - {"Non-Heap", "", "", "", ""}, - {"Non-Heap Memory Usage", "", "", "", ""}, - {"Notification", "", "", "", ""}, - {"Notification buffer", "", "", "", ""}, - {"Notifications", "", "", "", ""}, - {"NotifTypes", "", "", "", ""}, - {"Number of Loaded Classes", "", "", "", ""}, - {"Number of processors", "", "", "", ""}, - {"Number of Threads", "", "", "", ""}, - {"ObjectName", "", "", "", ""}, - {"Operating System", "", "", "", ""}, - {"Operation", "", "", "", ""}, - {"Operation invocation", "", "", "", ""}, - {"Operation return value", "", "", "", ""}, - {"Operations", "", "", "", ""}, - {"Overview", "", "", "", ""}, - {"OverviewPanel.plotter.accessibleName", "PhonyPlotter", "", "", ""}, - {"Parameter", "", "", "", ""}, - {"Password: ", "", "", "", ""}, - {"Password: .mnemonic", "", "", "", ""}, - {"Password.accessibleName", "", "", "", ""}, - {"Peak", "", "", "", ""}, - {"Perform GC", "", "", "", ""}, - {"Perform GC.mnemonic", "", "", "", ""}, - {"Perform GC.toolTip", "", "", "", ""}, - {"Plotter.accessibleName", "", "", "", ""}, - {"Plotter.accessibleName.keyAndValue", "Key", "Value", "", ""}, - {"Plotter.accessibleName.noData", "", "", "", ""}, - {"Plotter.saveAsMenuItem", "", "", "", ""}, - {"Plotter.saveAsMenuItem.mnemonic", "", "", "", ""}, - {"Plotter.timeRangeMenu", "", "", "", ""}, - {"Plotter.timeRangeMenu.mnemonic", "", "", "", ""}, - {"plot", "", "", "", ""}, - {"Problem adding listener", "", "", "", ""}, - {"Problem displaying MBean", "", "", "", ""}, - {"Problem invoking", "", "", "", ""}, - {"Problem removing listener", "", "", "", ""}, - {"Problem setting attribute", "", "", "", ""}, - {"Process CPU time", "", "", "", ""}, - {"Readable", "", "", "", ""}, - {"Reconnect", "", "", "", ""}, - {"Remote Process:", "", "", "", ""}, - {"Remote Process:.mnemonic", "", "", "", ""}, - {"Remote Process.textField.accessibleName", "", "", "", ""}, - {"remoteTF.usage", "", "", "", ""}, - {"Restore All", "", "", "", ""}, - {"Restore All.mnemonic", "", "", "", ""}, - {"ReturnType", "", "", "", ""}, - {"SeqNum", "", "", "", ""}, - {"Size Bytes", 512, "", "", ""}, - {"Size Gb", 512, "", "", ""}, - {"Size Kb", 512, "", "", ""}, - {"Size Mb", 512, "", "", ""}, - {"Source", "", "", "", ""}, - {"Stack trace", "", "", "", ""}, - {"SummaryTab.headerDateTimeFormat", "", "", "", ""}, - {"SummaryTab.pendingFinalization.label", "", "", "", ""}, - {"SummaryTab.pendingFinalization.value", "ObjectCount", "", "", ""}, - {"SummaryTab.tabName", "", "", "", ""}, - {"SummaryTab.vmVersion", "VMName", "VMVersion", "", ""}, - {"ThreadTab.infoLabelFormat", "LiveCount", "PeakCount", "TotalCount", ""}, - {"ThreadTab.threadInfo.accessibleName", "", "", "", ""}, - {"ThreadTab.threadPlotter.accessibleName", "", "", "", ""}, - {"Threads", "", "", "", ""}, - {"Threshold", "", "", "", ""}, - {"Tile", "", "", "", ""}, - {"Tile.mnemonic", "", "", "", ""}, - {"Time", "", "", "", ""}, - {"Time Range:", "", "", "", ""}, - {"Time Range:.mnemonic", "", "", "", ""}, - {"TimeStamp", "", "", "", ""}, - {"Total classes loaded", "", "", "", ""}, - {"Total classes unloaded", "", "", "", ""}, - {"Total compile time", "", "", "", ""}, - {"Total Loaded", "", "", "", ""}, - {"Total physical memory", "", "", "", ""}, - {"Total swap space", "", "", "", ""}, - {"Total threads started", "", "", "", ""}, - {"Type", "", "", "", ""}, - {"Unavailable", "", "", "", ""}, - {"UNKNOWN", "", "", "", ""}, - {"Unregister", "", "", "", ""}, - {"Uptime", "", "", "", ""}, - {"Usage Threshold", "", "", "", ""}, - {"Used", "", "", "", ""}, - {"Username: ", "", "", "", ""}, - {"Username: .mnemonic", "", "", "", ""}, - {"Username.accessibleName", "", "", "", ""}, - {"UserData", "", "", "", ""}, - {"Value", "", "", "", ""}, - {"Vendor", "", "", "", ""}, - {"Verbose Output", "", "", "", ""}, - {"Verbose Output.toolTip", "", "", "", ""}, - {"visualize", "", "", "", ""}, - {"VM", "", "", "", ""}, - {"VMInternalFrame.accessibleDescription", "", "", "", ""}, - {"VM arguments", "", "", "", ""}, - {"Virtual Machine", "", "", "", ""}, - {"Window", "", "", "", ""}, - {"Window.mnemonic", "", "", "", ""}, - {"Writable", "", "", "", ""}, - {"zz usage text", "PhonyName", "", "", ""}, - }; - //boolean verbose = false; - boolean verbose = true; - - long badLookups = 0; - System.out.println("Start..."); - for (int ii = 0; ii < testData.length; ii++) { - String key = (String)testData[ii][0]; - - if (key.endsWith(".mnemonic")) { - String baseKey = key.substring(0, key.length() - ".mnemonic".length()); - int mnemonic = Resources.getMnemonicInt(baseKey); - if (mnemonic == 0) { - badLookups++; - System.out.println("****lookup failed for key = " + key); + public static void main(String... args) { + List errors = new ArrayList<>(); + // Ensure that all Message fields have a corresponding key/value + // in the resource bundle and that mnemonics can be looked + // up where applicable. + ResourceBundle rb = ResourceBundle.getBundle(RESOURCE_BUNDLE); + for (Field field : Messages.class.getFields()) { + if (isResourceKeyField(field)) { + String resourceKey = field.getName(); + String message = readField(field); + if (message.startsWith(MISSING_RESOURCE_KEY_PREFIX)) { + errors.add("Can't find message (and perhaps mnemonic) for " + + Messages.class.getSimpleName() + "." + + resourceKey + " in resource bundle."); } else { - if (verbose) { - System.out.println(" mnemonic: " + KeyEvent.getKeyText(mnemonic)); + String resourceMessage = rb.getString(resourceKey); + if (hasMnemonicIdentifier(resourceMessage)) { + int mi = Resources.getMnemonicInt(message); + if (mi == 0) { + errors.add("Could not look up mnemonic for message '" + + message + "'."); + } } } - continue; } + } - String ss = Resources.getText(key, - testData[ii][1], - testData[ii][2], - testData[ii][3], - testData[ii][4]); - if (ss.startsWith("missing resource key")) { - badLookups++; - System.out.println("****lookup failed for key = " + key); - } else { - if (verbose) { - System.out.println(" " + ss); + // Ensure that there is Message class field for every resource key. + for (String key : Collections.list(rb.getKeys())) { + try { + Messages.class.getField(key); + } catch (NoSuchFieldException nfe) { + errors.add("Can't find static field (" + + Messages.class.getSimpleName() + "." + key + + ") matching '" + key + + "' in resource bundle. Unused message?"); + } + } + + if (errors.size() > 0) { + throwError(errors); + } + } + + private static String readField(Field field) { + try { + return (String) field.get(null); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new Error("Could not access field " + field.getName() + + " when trying to read resource message."); + } + } + + private static boolean isResourceKeyField(Field field) { + int modifiers = field.getModifiers(); + return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); + } + + private static boolean hasMnemonicIdentifier(String s) { + for (int i = 0; i < s.length() - 1; i++) { + if (s.charAt(i) == '&') { + if (s.charAt(i + 1) != '&') { + return true; + } else { + i++; } } } - if (badLookups > 0) { - throw new Error ("Resource lookup failed " + badLookups + - " time(s); Test failed"); + return false; + } + + private static void throwError(List errors) { + StringBuffer buffer = new StringBuffer(); + buffer.append("Found "); + buffer.append(errors.size()); + buffer.append(" error(s) when checking one-to-one mapping "); + buffer.append("between Message and resource bundle keys in "); + buffer.append(RESOURCE_BUNDLE); + buffer.append(" with "); + buffer.append(Locale.getDefault()); + buffer.append(" locale."); + buffer.append(NEW_LINE); + int errorIndex = 1; + for (String error : errors) { + buffer.append("Error "); + buffer.append(errorIndex); + buffer.append(": "); + buffer.append(error); + buffer.append(NEW_LINE); + errorIndex++; } - System.out.println("...Finished."); + throw new Error(buffer.toString()); } } diff --git a/jdk/test/sun/tools/jconsole/ResourceCheckTest.sh b/jdk/test/sun/tools/jconsole/ResourceCheckTest.sh index bec63a7992f..01c2e4b6f01 100644 --- a/jdk/test/sun/tools/jconsole/ResourceCheckTest.sh +++ b/jdk/test/sun/tools/jconsole/ResourceCheckTest.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,7 @@ pass() OS=`uname -s` case "$OS" in - SunOS | Linux ) + SunOS | Linux | Darwin) PATHSEP=":" ;; From 9b5513a8e8d82a40dacc910ddfa3d88bdecb6887 Mon Sep 17 00:00:00 2001 From: Mike Duigou Date: Tue, 3 Sep 2013 11:29:12 -0700 Subject: [PATCH 08/75] 8024015: TEST.groups: move jdk/lambda tests from jdk_other to jdk_lang Reviewed-by: alanb, mchung --- jdk/test/TEST.groups | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk/test/TEST.groups b/jdk/test/TEST.groups index 0674bdc1d91..ed70cbf6704 100644 --- a/jdk/test/TEST.groups +++ b/jdk/test/TEST.groups @@ -27,6 +27,7 @@ jdk_lang = \ sun/invoke \ sun/misc \ sun/reflect \ + jdk/lambda \ vm jdk_util = \ @@ -133,7 +134,6 @@ jdk_other = \ javax/xml \ -javax/xml/crypto \ jdk/asm \ - jdk/lambda \ com/sun/jndi \ com/sun/corba \ lib/testlibrary \ From f7b61b93f1d44b921df005f950e5836fad15c8cc Mon Sep 17 00:00:00 2001 From: Brian Goetz Date: Wed, 28 Aug 2013 14:13:03 -0700 Subject: [PATCH 09/75] 8022176: Weaken contract of java.lang.AutoCloseable Reviewed-by: alanb, martin, mduigou, psandoz --- .../classes/java/lang/AutoCloseable.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/jdk/src/share/classes/java/lang/AutoCloseable.java b/jdk/src/share/classes/java/lang/AutoCloseable.java index ce0fffe5939..be47cd0835f 100644 --- a/jdk/src/share/classes/java/lang/AutoCloseable.java +++ b/jdk/src/share/classes/java/lang/AutoCloseable.java @@ -26,7 +26,24 @@ package java.lang; /** - * A resource that must be closed when it is no longer needed. + * An object that may hold resources (such as file or socket handles) + * until it is closed. The {@link #close()} method of an {@code AutoCloseable} + * object is called automatically when exiting a {@code + * try}-with-resources block for which the object has been declared in + * the resource specification header. This construction ensures prompt + * release, avoiding resource exhaustion exceptions and errors that + * may otherwise occur. + * + * @apiNote + *

It is possible, and in fact common, for a base class to + * implement AutoCloseable even though not all of its subclasses or + * instances will hold releasable resources. For code that must operate + * in complete generality, or when it is known that the {@code AutoCloseable} + * instance requires resource release, it is recommended to use {@code + * try}-with-resources constructions. However, when using facilities such as + * {@link java.util.stream.Stream} that support both I/O-based and + * non-I/O-based forms, {@code try}-with-resources blocks are in + * general unnecessary when using non-I/O-based forms. * * @author Josh Bloch * @since 1.7 From 45d26c95711c9e57c51394ceeb10fd69d320c62a Mon Sep 17 00:00:00 2001 From: Henry Jen Date: Tue, 3 Sep 2013 11:44:34 -0700 Subject: [PATCH 10/75] 8024178: Difference in Stream.collect(Collector) methods located in jdk8 and jdk8-lambda repos Reviewed-by: mduigou --- jdk/src/share/classes/java/util/stream/DelegatingStream.java | 2 +- jdk/src/share/classes/java/util/stream/ReferencePipeline.java | 2 +- jdk/src/share/classes/java/util/stream/Stream.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jdk/src/share/classes/java/util/stream/DelegatingStream.java b/jdk/src/share/classes/java/util/stream/DelegatingStream.java index 2dab1a4430c..fbe7735a515 100644 --- a/jdk/src/share/classes/java/util/stream/DelegatingStream.java +++ b/jdk/src/share/classes/java/util/stream/DelegatingStream.java @@ -209,7 +209,7 @@ public class DelegatingStream implements Stream { } @Override - public R collect(Collector collector) { + public R collect(Collector collector) { return delegate.collect(collector); } diff --git a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java index 1fffff48b18..6c6fe647ee9 100644 --- a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java +++ b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java @@ -493,7 +493,7 @@ abstract class ReferencePipeline @Override @SuppressWarnings("unchecked") - public final R collect(Collector collector) { + public final R collect(Collector collector) { A container; if (isParallel() && (collector.characteristics().contains(Collector.Characteristics.CONCURRENT)) diff --git a/jdk/src/share/classes/java/util/stream/Stream.java b/jdk/src/share/classes/java/util/stream/Stream.java index 59d703b118e..1071b20110e 100644 --- a/jdk/src/share/classes/java/util/stream/Stream.java +++ b/jdk/src/share/classes/java/util/stream/Stream.java @@ -657,7 +657,7 @@ public interface Stream extends BaseStream> { * @see #collect(Supplier, BiConsumer, BiConsumer) * @see Collectors */ - R collect(Collector collector); + R collect(Collector collector); /** * Returns the minimum element of this stream according to the provided From 7bc062de1deb8ae4c5cca937970e1fb5794946d0 Mon Sep 17 00:00:00 2001 From: Brian Goetz Date: Tue, 3 Sep 2013 12:16:01 -0700 Subject: [PATCH 11/75] 8017513: Support for closeable streams 8022237: j.u.s.BaseStream.onClose() has an issue in implementation or requires spec clarification 8022572: Same exception instances thrown from j.u.stream.Stream.onClose() handlers are not listed as suppressed BaseStream implements AutoCloseable; Remove CloseableStream and DelegatingStream Reviewed-by: alanb, mduigou, psandoz --- .../share/classes/java/nio/file/Files.java | 298 +++++++++++------- .../java/util/stream/AbstractPipeline.java | 44 ++- .../classes/java/util/stream/BaseStream.java | 32 +- .../java/util/stream/CloseableStream.java | 57 ---- .../java/util/stream/DelegatingStream.java | 270 ---------------- .../java/util/stream/DoublePipeline.java | 9 +- .../java/util/stream/DoubleStream.java | 6 +- .../classes/java/util/stream/IntPipeline.java | 9 +- .../classes/java/util/stream/IntStream.java | 6 +- .../java/util/stream/LongPipeline.java | 9 +- .../classes/java/util/stream/LongStream.java | 6 +- .../java/util/stream/ReferencePipeline.java | 36 ++- .../classes/java/util/stream/Stream.java | 6 +- .../classes/java/util/stream/Streams.java | 57 ++++ jdk/test/java/nio/file/Files/StreamTest.java | 103 +++--- .../util/stream/DoubleStreamTestScenario.java | 3 +- .../util/stream/IntStreamTestScenario.java | 3 +- .../util/stream/LongStreamTestScenario.java | 3 +- .../java/util/stream/StreamTestScenario.java | 3 +- .../java/util/stream/StreamCloseTest.java | 166 ++++++++++ 20 files changed, 578 insertions(+), 548 deletions(-) delete mode 100644 jdk/src/share/classes/java/util/stream/CloseableStream.java delete mode 100644 jdk/src/share/classes/java/util/stream/DelegatingStream.java create mode 100644 jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/StreamCloseTest.java diff --git a/jdk/src/share/classes/java/nio/file/Files.java b/jdk/src/share/classes/java/nio/file/Files.java index 721184c1533..f084040c179 100644 --- a/jdk/src/share/classes/java/nio/file/Files.java +++ b/jdk/src/share/classes/java/nio/file/Files.java @@ -25,34 +25,56 @@ package java.nio.file; -import java.nio.file.attribute.*; -import java.nio.file.spi.FileSystemProvider; -import java.nio.file.spi.FileTypeDetector; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.io.Writer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; -import java.io.Closeable; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Writer; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.*; -import java.util.function.BiPredicate; -import java.util.stream.CloseableStream; -import java.util.stream.DelegatingStream; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.DosFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileOwnerAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; +import java.nio.file.spi.FileSystemProvider; +import java.nio.file.spi.FileTypeDetector; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.BiPredicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * This class consists exclusively of static methods that operate on files, @@ -74,6 +96,21 @@ public final class Files { return path.getFileSystem().provider(); } + /** + * Convert a Closeable to a Runnable by converting checked IOException + * to UncheckedIOException + */ + private static Runnable asUncheckedRunnable(Closeable c) { + return () -> { + try { + c.close(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + } + // -- File contents -- /** @@ -3228,29 +3265,7 @@ public final class Files { // -- Stream APIs -- /** - * Implementation of CloseableStream - */ - private static class DelegatingCloseableStream extends DelegatingStream - implements CloseableStream - { - private final Closeable closeable; - - DelegatingCloseableStream(Closeable c, Stream delegate) { - super(delegate); - this.closeable = c; - } - - public void close() { - try { - closeable.close(); - } catch (IOException ex) { - throw new UncheckedIOException(ex); - } - } - } - - /** - * Return a lazily populated {@code CloseableStream}, the elements of + * Return a lazily populated {@code Stream}, the elements of * which are the entries in the directory. The listing is not recursive. * *

The elements of the stream are {@link Path} objects that are @@ -3264,10 +3279,13 @@ public final class Files { * reflect updates to the directory that occur after returning from this * method. * - *

When not using the try-with-resources construct, then the stream's - * {@link CloseableStream#close close} method should be invoked after the - * operation is completed so as to free any resources held for the open - * directory. Operating on a closed stream behaves as if the end of stream + *

The returned stream encapsulates a {@link DirectoryStream}. + * If timely disposal of file system resources is required, the + * {@code try}-with-resources construct should be used to ensure that the + * stream's {@link Stream#close close} method is invoked after the stream + * operations are completed. + * + *

Operating on a closed stream behaves as if the end of stream * has been reached. Due to read-ahead, one or more elements may be * returned after the stream has been closed. * @@ -3278,7 +3296,7 @@ public final class Files { * * @param dir The path to the directory * - * @return The {@code CloseableStream} describing the content of the + * @return The {@code Stream} describing the content of the * directory * * @throws NotDirectoryException @@ -3294,43 +3312,54 @@ public final class Files { * @see #newDirectoryStream(Path) * @since 1.8 */ - public static CloseableStream list(Path dir) throws IOException { + public static Stream list(Path dir) throws IOException { DirectoryStream ds = Files.newDirectoryStream(dir); - final Iterator delegate = ds.iterator(); + try { + final Iterator delegate = ds.iterator(); - // Re-wrap DirectoryIteratorException to UncheckedIOException - Iterator it = new Iterator() { - public boolean hasNext() { - try { - return delegate.hasNext(); - } catch (DirectoryIteratorException e) { - throw new UncheckedIOException(e.getCause()); + // Re-wrap DirectoryIteratorException to UncheckedIOException + Iterator it = new Iterator() { + @Override + public boolean hasNext() { + try { + return delegate.hasNext(); + } catch (DirectoryIteratorException e) { + throw new UncheckedIOException(e.getCause()); + } } - } - public Path next() { - try { - return delegate.next(); - } catch (DirectoryIteratorException e) { - throw new UncheckedIOException(e.getCause()); + @Override + public Path next() { + try { + return delegate.next(); + } catch (DirectoryIteratorException e) { + throw new UncheckedIOException(e.getCause()); + } } - } - }; + }; - Stream s = StreamSupport.stream( - Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), - false); - return new DelegatingCloseableStream<>(ds, s); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false) + .onClose(asUncheckedRunnable(ds)); + } catch (Error|RuntimeException e) { + try { + ds.close(); + } catch (IOException ex) { + try { + e.addSuppressed(ex); + } catch (Throwable ignore) {} + } + throw e; + } } /** - * Return a {@code CloseableStream} that is lazily populated with {@code + * Return a {@code Stream} that is lazily populated with {@code * Path} by walking the file tree rooted at a given starting file. The * file tree is traversed depth-first, the elements in the stream * are {@link Path} objects that are obtained as if by {@link * Path#resolve(Path) resolving} the relative path against {@code start}. * *

The {@code stream} walks the file tree as elements are consumed. - * The {@code CloseableStream} returned is guaranteed to have at least one + * The {@code Stream} returned is guaranteed to have at least one * element, the starting file itself. For each file visited, the stream * attempts to read its {@link BasicFileAttributes}. If the file is a * directory and can be opened successfully, entries in the directory, and @@ -3370,10 +3399,11 @@ public final class Files { *

When a security manager is installed and it denies access to a file * (or directory), then it is ignored and not included in the stream. * - *

When not using the try-with-resources construct, then the stream's - * {@link CloseableStream#close close} method should be invoked after the - * operation is completed so as to free any resources held for the open - * directory. Operate the stream after it is closed will throw an + *

The returned stream encapsulates one or more {@link DirectoryStream}s. + * If timely disposal of file system resources is required, the + * {@code try}-with-resources construct should be used to ensure that the + * stream's {@link Stream#close close} method is invoked after the stream + * operations are completed. Operating on a closed stream will result in an * {@link java.lang.IllegalStateException}. * *

If an {@link IOException} is thrown when accessing the directory @@ -3388,7 +3418,7 @@ public final class Files { * @param options * options to configure the traversal * - * @return the {@link CloseableStream} of {@link Path} + * @return the {@link Stream} of {@link Path} * * @throws IllegalArgumentException * if the {@code maxDepth} parameter is negative @@ -3401,21 +3431,22 @@ public final class Files { * if an I/O error is thrown when accessing the starting file. * @since 1.8 */ - public static CloseableStream walk(Path start, int maxDepth, - FileVisitOption... options) - throws IOException - { + public static Stream walk(Path start, int maxDepth, + FileVisitOption... options) + throws IOException { FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); - - Stream s = StreamSupport.stream( - Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT), - false). - map(entry -> entry.file()); - return new DelegatingCloseableStream<>(iterator, s); + try { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT), false) + .onClose(iterator::close) + .map(entry -> entry.file()); + } catch (Error|RuntimeException e) { + iterator.close(); + throw e; + } } /** - * Return a {@code CloseableStream} that is lazily populated with {@code + * Return a {@code Stream} that is lazily populated with {@code * Path} by walking the file tree rooted at a given starting file. The * file tree is traversed depth-first, the elements in the stream * are {@link Path} objects that are obtained as if by {@link @@ -3428,12 +3459,19 @@ public final class Files { * * In other words, it visits all levels of the file tree. * + *

The returned stream encapsulates one or more {@link DirectoryStream}s. + * If timely disposal of file system resources is required, the + * {@code try}-with-resources construct should be used to ensure that the + * stream's {@link Stream#close close} method is invoked after the stream + * operations are completed. Operating on a closed stream will result in an + * {@link java.lang.IllegalStateException}. + * * @param start * the starting file * @param options * options to configure the traversal * - * @return the {@link CloseableStream} of {@link Path} + * @return the {@link Stream} of {@link Path} * * @throws SecurityException * If the security manager denies access to the starting file. @@ -3446,15 +3484,14 @@ public final class Files { * @see #walk(Path, int, FileVisitOption...) * @since 1.8 */ - public static CloseableStream walk(Path start, - FileVisitOption... options) - throws IOException - { + public static Stream walk(Path start, + FileVisitOption... options) + throws IOException { return walk(start, Integer.MAX_VALUE, options); } /** - * Return a {@code CloseableStream} that is lazily populated with {@code + * Return a {@code Stream} that is lazily populated with {@code * Path} by searching for files in a file tree rooted at a given starting * file. * @@ -3463,12 +3500,19 @@ public final class Files { * {@link BiPredicate} is invoked with its {@link Path} and {@link * BasicFileAttributes}. The {@code Path} object is obtained as if by * {@link Path#resolve(Path) resolving} the relative path against {@code - * start} and is only included in the returned {@link CloseableStream} if + * start} and is only included in the returned {@link Stream} if * the {@code BiPredicate} returns true. Compare to calling {@link * java.util.stream.Stream#filter filter} on the {@code Stream} * returned by {@code walk} method, this method may be more efficient by * avoiding redundant retrieval of the {@code BasicFileAttributes}. * + *

The returned stream encapsulates one or more {@link DirectoryStream}s. + * If timely disposal of file system resources is required, the + * {@code try}-with-resources construct should be used to ensure that the + * stream's {@link Stream#close close} method is invoked after the stream + * operations are completed. Operating on a closed stream will result in an + * {@link java.lang.IllegalStateException}. + * *

If an {@link IOException} is thrown when accessing the directory * after returned from this method, it is wrapped in an {@link * UncheckedIOException} which will be thrown from the method that caused @@ -3484,7 +3528,7 @@ public final class Files { * @param options * options to configure the traversal * - * @return the {@link CloseableStream} of {@link Path} + * @return the {@link Stream} of {@link Path} * * @throws IllegalArgumentException * if the {@code maxDepth} parameter is negative @@ -3499,24 +3543,25 @@ public final class Files { * @see #walk(Path, int, FileVisitOption...) * @since 1.8 */ - public static CloseableStream find(Path start, - int maxDepth, - BiPredicate matcher, - FileVisitOption... options) - throws IOException - { + public static Stream find(Path start, + int maxDepth, + BiPredicate matcher, + FileVisitOption... options) + throws IOException { FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); - - Stream s = StreamSupport.stream( - Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT), - false). - filter(entry -> matcher.test(entry.file(), entry.attributes())). - map(entry -> entry.file()); - return new DelegatingCloseableStream<>(iterator, s); + try { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT), false) + .onClose(iterator::close) + .filter(entry -> matcher.test(entry.file(), entry.attributes())) + .map(entry -> entry.file()); + } catch (Error|RuntimeException e) { + iterator.close(); + throw e; + } } /** - * Read all lines from a file as a {@code CloseableStream}. Unlike {@link + * Read all lines from a file as a {@code Stream}. Unlike {@link * #readAllLines(Path, Charset) readAllLines}, this method does not read * all lines into a {@code List}, but instead populates lazily as the stream * is consumed. @@ -3528,22 +3573,24 @@ public final class Files { *

After this method returns, then any subsequent I/O exception that * occurs while reading from the file or when a malformed or unmappable byte * sequence is read, is wrapped in an {@link UncheckedIOException} that will - * be thrown form the + * be thrown from the * {@link java.util.stream.Stream} method that caused the read to take * place. In case an {@code IOException} is thrown when closing the file, * it is also wrapped as an {@code UncheckedIOException}. * - *

When not using the try-with-resources construct, then stream's - * {@link CloseableStream#close close} method should be invoked after - * operation is completed so as to free any resources held for the open - * file. + *

The returned stream encapsulates a {@link Reader}. If timely + * disposal of file system resources is required, the try-with-resources + * construct should be used to ensure that the stream's + * {@link Stream#close close} method is invoked after the stream operations + * are completed. + * * * @param path * the path to the file * @param cs * the charset to use for decoding * - * @return the lines from the file as a {@code CloseableStream} + * @return the lines from the file as a {@code Stream} * * @throws IOException * if an I/O error occurs opening the file @@ -3557,10 +3604,19 @@ public final class Files { * @see java.io.BufferedReader#lines() * @since 1.8 */ - public static CloseableStream lines(Path path, Charset cs) - throws IOException - { + public static Stream lines(Path path, Charset cs) throws IOException { BufferedReader br = Files.newBufferedReader(path, cs); - return new DelegatingCloseableStream<>(br, br.lines()); + try { + return br.lines().onClose(asUncheckedRunnable(br)); + } catch (Error|RuntimeException e) { + try { + br.close(); + } catch (IOException ex) { + try { + e.addSuppressed(ex); + } catch (Throwable ignore) {} + } + throw e; + } } } diff --git a/jdk/src/share/classes/java/util/stream/AbstractPipeline.java b/jdk/src/share/classes/java/util/stream/AbstractPipeline.java index 3b2f5bdc5f7..dd60c2520e4 100644 --- a/jdk/src/share/classes/java/util/stream/AbstractPipeline.java +++ b/jdk/src/share/classes/java/util/stream/AbstractPipeline.java @@ -71,6 +71,9 @@ import java.util.function.Supplier; */ abstract class AbstractPipeline> extends PipelineHelper implements BaseStream { + private static final String MSG_STREAM_LINKED = "stream has already been operated upon or closed"; + private static final String MSG_CONSUMED = "source already consumed or closed"; + /** * Backlink to the head of the pipeline chain (self if this is the source * stage). @@ -137,6 +140,8 @@ abstract class AbstractPipeline> */ private boolean sourceAnyStateful; + private Runnable sourceCloseAction; + /** * True if pipeline is parallel, otherwise the pipeline is sequential; only * valid for the source stage. @@ -195,7 +200,7 @@ abstract class AbstractPipeline> */ AbstractPipeline(AbstractPipeline previousStage, int opFlags) { if (previousStage.linkedOrConsumed) - throw new IllegalStateException("stream has already been operated upon"); + throw new IllegalStateException(MSG_STREAM_LINKED); previousStage.linkedOrConsumed = true; previousStage.nextStage = this; @@ -221,7 +226,7 @@ abstract class AbstractPipeline> final R evaluate(TerminalOp terminalOp) { assert getOutputShape() == terminalOp.inputShape(); if (linkedOrConsumed) - throw new IllegalStateException("stream has already been operated upon"); + throw new IllegalStateException(MSG_STREAM_LINKED); linkedOrConsumed = true; return isParallel() @@ -238,7 +243,7 @@ abstract class AbstractPipeline> @SuppressWarnings("unchecked") final Node evaluateToArrayNode(IntFunction generator) { if (linkedOrConsumed) - throw new IllegalStateException("stream has already been operated upon"); + throw new IllegalStateException(MSG_STREAM_LINKED); linkedOrConsumed = true; // If the last intermediate operation is stateful then @@ -266,7 +271,7 @@ abstract class AbstractPipeline> throw new IllegalStateException(); if (linkedOrConsumed) - throw new IllegalStateException("stream has already been operated upon"); + throw new IllegalStateException(MSG_STREAM_LINKED); linkedOrConsumed = true; if (sourceStage.sourceSpliterator != null) { @@ -282,7 +287,7 @@ abstract class AbstractPipeline> return s; } else { - throw new IllegalStateException("source already consumed"); + throw new IllegalStateException(MSG_CONSUMED); } } @@ -302,12 +307,35 @@ abstract class AbstractPipeline> return (S) this; } + @Override + public void close() { + linkedOrConsumed = true; + sourceSupplier = null; + sourceSpliterator = null; + if (sourceStage.sourceCloseAction != null) { + Runnable closeAction = sourceStage.sourceCloseAction; + sourceStage.sourceCloseAction = null; + closeAction.run(); + } + } + + @Override + @SuppressWarnings("unchecked") + public S onClose(Runnable closeHandler) { + Runnable existingHandler = sourceStage.sourceCloseAction; + sourceStage.sourceCloseAction = + (existingHandler == null) + ? closeHandler + : Streams.composeWithExceptions(existingHandler, closeHandler); + return (S) this; + } + // Primitive specialization use co-variant overrides, hence is not final @Override @SuppressWarnings("unchecked") public Spliterator spliterator() { if (linkedOrConsumed) - throw new IllegalStateException("stream has already been operated upon"); + throw new IllegalStateException(MSG_STREAM_LINKED); linkedOrConsumed = true; if (this == sourceStage) { @@ -324,7 +352,7 @@ abstract class AbstractPipeline> return lazySpliterator(s); } else { - throw new IllegalStateException("source already consumed"); + throw new IllegalStateException(MSG_CONSUMED); } } else { @@ -424,7 +452,7 @@ abstract class AbstractPipeline> sourceStage.sourceSupplier = null; } else { - throw new IllegalStateException("source already consumed"); + throw new IllegalStateException(MSG_CONSUMED); } if (isParallel()) { diff --git a/jdk/src/share/classes/java/util/stream/BaseStream.java b/jdk/src/share/classes/java/util/stream/BaseStream.java index 94dbc7de73f..9c87a309098 100644 --- a/jdk/src/share/classes/java/util/stream/BaseStream.java +++ b/jdk/src/share/classes/java/util/stream/BaseStream.java @@ -35,7 +35,8 @@ import java.util.Spliterator; * @param type of stream implementing {@code BaseStream} * @since 1.8 */ -public interface BaseStream> { +public interface BaseStream> + extends AutoCloseable { /** * Returns an iterator for the elements of this stream. * @@ -103,4 +104,33 @@ public interface BaseStream> { * @return an unordered stream */ S unordered(); + + /** + * Returns an equivalent stream with an additional close handler. Close + * handlers are run when the {@link #close()} method + * is called on the stream, and are executed in the order they were + * added. All close handlers are run, even if earlier close handlers throw + * exceptions. If any close handler throws an exception, the first + * exception thrown will be relayed to the caller of {@code close()}, with + * any remaining exceptions added to that exception as suppressed exceptions + * (unless one of the remaining exceptions is the same exception as the + * first exception, since an exception cannot suppress itself.) May + * return itself. + * + *

This is an intermediate + * operation. + * + * @param closeHandler A task to execute when the stream is closed + * @return a stream with a handler that is run if the stream is closed + */ + S onClose(Runnable closeHandler); + + /** + * Closes this stream, causing all close handlers for this stream pipeline + * to be called. + * + * @see AutoCloseable#close() + */ + @Override + void close(); } diff --git a/jdk/src/share/classes/java/util/stream/CloseableStream.java b/jdk/src/share/classes/java/util/stream/CloseableStream.java deleted file mode 100644 index bbcce516f99..00000000000 --- a/jdk/src/share/classes/java/util/stream/CloseableStream.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.util.stream; - -/** - * A {@code CloseableStream} is a {@code Stream} that can be closed. - * The close method is invoked to release resources that the object is - * holding (such as open files). - * - * @param The type of stream elements - * @since 1.8 - */ -public interface CloseableStream extends Stream, AutoCloseable { - - /** - * Closes this resource, relinquishing any underlying resources. - * This method is invoked automatically on objects managed by the - * {@code try}-with-resources statement. Does nothing if called when - * the resource has already been closed. - * - * This method does not allow throwing checked {@code Exception}s like - * {@link AutoCloseable#close() AutoCloseable.close()}. Cases where the - * close operation may fail require careful attention by implementers. It - * is strongly advised to relinquish the underlying resources and to - * internally mark the resource as closed. The {@code close} - * method is unlikely to be invoked more than once and so this ensures - * that the resources are released in a timely manner. Furthermore it - * reduces problems that could arise when the resource wraps, or is - * wrapped, by another resource. - * - * @see AutoCloseable#close() - */ - void close(); -} diff --git a/jdk/src/share/classes/java/util/stream/DelegatingStream.java b/jdk/src/share/classes/java/util/stream/DelegatingStream.java deleted file mode 100644 index fbe7735a515..00000000000 --- a/jdk/src/share/classes/java/util/stream/DelegatingStream.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.util.stream; - -import java.util.Comparator; -import java.util.Iterator; -import java.util.Objects; -import java.util.Optional; -import java.util.Spliterator; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.BinaryOperator; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.function.ToDoubleFunction; -import java.util.function.ToIntFunction; -import java.util.function.ToLongFunction; - -/** - * A {@code Stream} implementation that delegates operations to another {@code - * Stream}. - * - * @param type of stream elements for this stream and underlying delegate - * stream - * - * @since 1.8 - */ -public class DelegatingStream implements Stream { - final private Stream delegate; - - /** - * Construct a {@code Stream} that delegates operations to another {@code - * Stream}. - * - * @param delegate the underlying {@link Stream} to which we delegate all - * {@code Stream} methods - * @throws NullPointerException if the delegate is null - */ - public DelegatingStream(Stream delegate) { - this.delegate = Objects.requireNonNull(delegate); - } - - // -- BaseStream methods -- - - @Override - public Spliterator spliterator() { - return delegate.spliterator(); - } - - @Override - public boolean isParallel() { - return delegate.isParallel(); - } - - @Override - public Iterator iterator() { - return delegate.iterator(); - } - - // -- Stream methods -- - - @Override - public Stream filter(Predicate predicate) { - return delegate.filter(predicate); - } - - @Override - public Stream map(Function mapper) { - return delegate.map(mapper); - } - - @Override - public IntStream mapToInt(ToIntFunction mapper) { - return delegate.mapToInt(mapper); - } - - @Override - public LongStream mapToLong(ToLongFunction mapper) { - return delegate.mapToLong(mapper); - } - - @Override - public DoubleStream mapToDouble(ToDoubleFunction mapper) { - return delegate.mapToDouble(mapper); - } - - @Override - public Stream flatMap(Function> mapper) { - return delegate.flatMap(mapper); - } - - @Override - public IntStream flatMapToInt(Function mapper) { - return delegate.flatMapToInt(mapper); - } - - @Override - public LongStream flatMapToLong(Function mapper) { - return delegate.flatMapToLong(mapper); - } - - @Override - public DoubleStream flatMapToDouble(Function mapper) { - return delegate.flatMapToDouble(mapper); - } - - @Override - public Stream distinct() { - return delegate.distinct(); - } - - @Override - public Stream sorted() { - return delegate.sorted(); - } - - @Override - public Stream sorted(Comparator comparator) { - return delegate.sorted(comparator); - } - - @Override - public void forEach(Consumer action) { - delegate.forEach(action); - } - - @Override - public void forEachOrdered(Consumer action) { - delegate.forEachOrdered(action); - } - - @Override - public Stream peek(Consumer consumer) { - return delegate.peek(consumer); - } - - @Override - public Stream limit(long maxSize) { - return delegate.limit(maxSize); - } - - @Override - public Stream substream(long startingOffset) { - return delegate.substream(startingOffset); - } - - @Override - public Stream substream(long startingOffset, long endingOffset) { - return delegate.substream(startingOffset, endingOffset); - } - - @Override - public A[] toArray(IntFunction generator) { - return delegate.toArray(generator); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public T reduce(T identity, BinaryOperator accumulator) { - return delegate.reduce(identity, accumulator); - } - - @Override - public Optional reduce(BinaryOperator accumulator) { - return delegate.reduce(accumulator); - } - - @Override - public U reduce(U identity, BiFunction accumulator, - BinaryOperator combiner) { - return delegate.reduce(identity, accumulator, combiner); - } - - @Override - public R collect(Supplier resultFactory, - BiConsumer accumulator, - BiConsumer combiner) { - return delegate.collect(resultFactory, accumulator, combiner); - } - - @Override - public R collect(Collector collector) { - return delegate.collect(collector); - } - - @Override - public Optional max(Comparator comparator) { - return delegate.max(comparator); - } - - @Override - public Optional min(Comparator comparator) { - return delegate.min(comparator); - } - - @Override - public long count() { - return delegate.count(); - } - - @Override - public boolean anyMatch(Predicate predicate) { - return delegate.anyMatch(predicate); - } - - @Override - public boolean allMatch(Predicate predicate) { - return delegate.allMatch(predicate); - } - - @Override - public boolean noneMatch(Predicate predicate) { - return delegate.noneMatch(predicate); - } - - @Override - public Optional findFirst() { - return delegate.findFirst(); - } - - @Override - public Optional findAny() { - return delegate.findAny(); - } - - @Override - public Stream unordered() { - return delegate.unordered(); - } - - @Override - public Stream sequential() { - return delegate.sequential(); - } - - @Override - public Stream parallel() { - return delegate.parallel(); - } -} diff --git a/jdk/src/share/classes/java/util/stream/DoublePipeline.java b/jdk/src/share/classes/java/util/stream/DoublePipeline.java index f894fa0abb9..75981d5801f 100644 --- a/jdk/src/share/classes/java/util/stream/DoublePipeline.java +++ b/jdk/src/share/classes/java/util/stream/DoublePipeline.java @@ -266,10 +266,11 @@ abstract class DoublePipeline @Override public void accept(double t) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - DoubleStream result = mapper.apply(t); - if (result != null) - result.sequential().forEach(i -> downstream.accept(i)); + try (DoubleStream result = mapper.apply(t)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(i -> downstream.accept(i)); + } } }; } diff --git a/jdk/src/share/classes/java/util/stream/DoubleStream.java b/jdk/src/share/classes/java/util/stream/DoubleStream.java index 93a83991e0b..bf356926154 100644 --- a/jdk/src/share/classes/java/util/stream/DoubleStream.java +++ b/jdk/src/share/classes/java/util/stream/DoubleStream.java @@ -752,7 +752,8 @@ public interface DoubleStream extends BaseStream { * elements of a first {@code DoubleStream} succeeded by all the elements of the * second {@code DoubleStream}. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input - * streams is parallel. + * streams is parallel. When the resulting stream is closed, the close + * handlers for both input streams are invoked. * * @param a the first stream * @param b the second stream to concatenate on to end of the first stream @@ -764,7 +765,8 @@ public interface DoubleStream extends BaseStream { Spliterator.OfDouble split = new Streams.ConcatSpliterator.OfDouble( a.spliterator(), b.spliterator()); - return StreamSupport.doubleStream(split, a.isParallel() || b.isParallel()); + DoubleStream stream = StreamSupport.doubleStream(split, a.isParallel() || b.isParallel()); + return stream.onClose(Streams.composedClose(a, b)); } /** diff --git a/jdk/src/share/classes/java/util/stream/IntPipeline.java b/jdk/src/share/classes/java/util/stream/IntPipeline.java index f7dc79317d3..f35bc1d7a8e 100644 --- a/jdk/src/share/classes/java/util/stream/IntPipeline.java +++ b/jdk/src/share/classes/java/util/stream/IntPipeline.java @@ -302,10 +302,11 @@ abstract class IntPipeline @Override public void accept(int t) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - IntStream result = mapper.apply(t); - if (result != null) - result.sequential().forEach(i -> downstream.accept(i)); + try (IntStream result = mapper.apply(t)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(i -> downstream.accept(i)); + } } }; } diff --git a/jdk/src/share/classes/java/util/stream/IntStream.java b/jdk/src/share/classes/java/util/stream/IntStream.java index 883b03cbd3d..c107ca46de9 100644 --- a/jdk/src/share/classes/java/util/stream/IntStream.java +++ b/jdk/src/share/classes/java/util/stream/IntStream.java @@ -806,7 +806,8 @@ public interface IntStream extends BaseStream { * elements of a first {@code IntStream} succeeded by all the elements of the * second {@code IntStream}. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input - * streams is parallel. + * streams is parallel. When the resulting stream is closed, the close + * handlers for both input streams are invoked. * * @param a the first stream * @param b the second stream to concatenate on to end of the first stream @@ -818,7 +819,8 @@ public interface IntStream extends BaseStream { Spliterator.OfInt split = new Streams.ConcatSpliterator.OfInt( a.spliterator(), b.spliterator()); - return StreamSupport.intStream(split, a.isParallel() || b.isParallel()); + IntStream stream = StreamSupport.intStream(split, a.isParallel() || b.isParallel()); + return stream.onClose(Streams.composedClose(a, b)); } /** diff --git a/jdk/src/share/classes/java/util/stream/LongPipeline.java b/jdk/src/share/classes/java/util/stream/LongPipeline.java index 3c199feab59..a59ec3f5f00 100644 --- a/jdk/src/share/classes/java/util/stream/LongPipeline.java +++ b/jdk/src/share/classes/java/util/stream/LongPipeline.java @@ -283,10 +283,11 @@ abstract class LongPipeline @Override public void accept(long t) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - LongStream result = mapper.apply(t); - if (result != null) - result.sequential().forEach(i -> downstream.accept(i)); + try (LongStream result = mapper.apply(t)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(i -> downstream.accept(i)); + } } }; } diff --git a/jdk/src/share/classes/java/util/stream/LongStream.java b/jdk/src/share/classes/java/util/stream/LongStream.java index 8fce0d68d65..e64c67204dc 100644 --- a/jdk/src/share/classes/java/util/stream/LongStream.java +++ b/jdk/src/share/classes/java/util/stream/LongStream.java @@ -812,7 +812,8 @@ public interface LongStream extends BaseStream { * elements of a first {@code LongStream} succeeded by all the elements of the * second {@code LongStream}. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input - * streams is parallel. + * streams is parallel. When the resulting stream is closed, the close + * handlers for both input streams are invoked. * * @param a the first stream * @param b the second stream to concatenate on to end of the first stream @@ -824,7 +825,8 @@ public interface LongStream extends BaseStream { Spliterator.OfLong split = new Streams.ConcatSpliterator.OfLong( a.spliterator(), b.spliterator()); - return StreamSupport.longStream(split, a.isParallel() || b.isParallel()); + LongStream stream = StreamSupport.longStream(split, a.isParallel() || b.isParallel()); + return stream.onClose(Streams.composedClose(a, b)); } /** diff --git a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java index 6c6fe647ee9..42d711f4b2b 100644 --- a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java +++ b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java @@ -264,10 +264,11 @@ abstract class ReferencePipeline @Override public void accept(P_OUT u) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - Stream result = mapper.apply(u); - if (result != null) - result.sequential().forEach(downstream); + try (Stream result = mapper.apply(u)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(downstream); + } } }; } @@ -291,10 +292,11 @@ abstract class ReferencePipeline @Override public void accept(P_OUT u) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - IntStream result = mapper.apply(u); - if (result != null) - result.sequential().forEach(downstreamAsInt); + try (IntStream result = mapper.apply(u)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(downstreamAsInt); + } } }; } @@ -318,10 +320,11 @@ abstract class ReferencePipeline @Override public void accept(P_OUT u) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - DoubleStream result = mapper.apply(u); - if (result != null) - result.sequential().forEach(downstreamAsDouble); + try (DoubleStream result = mapper.apply(u)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(downstreamAsDouble); + } } }; } @@ -345,10 +348,11 @@ abstract class ReferencePipeline @Override public void accept(P_OUT u) { - // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it - LongStream result = mapper.apply(u); - if (result != null) - result.sequential().forEach(downstreamAsLong); + try (LongStream result = mapper.apply(u)) { + // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it + if (result != null) + result.sequential().forEach(downstreamAsLong); + } } }; } diff --git a/jdk/src/share/classes/java/util/stream/Stream.java b/jdk/src/share/classes/java/util/stream/Stream.java index 1071b20110e..715729f24a7 100644 --- a/jdk/src/share/classes/java/util/stream/Stream.java +++ b/jdk/src/share/classes/java/util/stream/Stream.java @@ -891,7 +891,8 @@ public interface Stream extends BaseStream> { * elements of a first {@code Stream} succeeded by all the elements of the * second {@code Stream}. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input - * streams is parallel. + * streams is parallel. When the resulting stream is closed, the close + * handlers for both input streams are invoked. * * @param The type of stream elements * @param a the first stream @@ -906,7 +907,8 @@ public interface Stream extends BaseStream> { @SuppressWarnings("unchecked") Spliterator split = new Streams.ConcatSpliterator.OfRef<>( (Spliterator) a.spliterator(), (Spliterator) b.spliterator()); - return StreamSupport.stream(split, a.isParallel() || b.isParallel()); + Stream stream = StreamSupport.stream(split, a.isParallel() || b.isParallel()); + return stream.onClose(Streams.composedClose(a, b)); } /** diff --git a/jdk/src/share/classes/java/util/stream/Streams.java b/jdk/src/share/classes/java/util/stream/Streams.java index 15c3dcae497..8af33f2b3c2 100644 --- a/jdk/src/share/classes/java/util/stream/Streams.java +++ b/jdk/src/share/classes/java/util/stream/Streams.java @@ -833,4 +833,61 @@ final class Streams { } } } + + /** + * Given two Runnables, return a Runnable that executes both in sequence, + * even if the first throws an exception, and if both throw exceptions, add + * any exceptions thrown by the second as suppressed exceptions of the first. + */ + static Runnable composeWithExceptions(Runnable a, Runnable b) { + return new Runnable() { + @Override + public void run() { + try { + a.run(); + } + catch (Throwable e1) { + try { + b.run(); + } + catch (Throwable e2) { + try { + e1.addSuppressed(e2); + } catch (Throwable ignore) {} + } + throw e1; + } + b.run(); + } + }; + } + + /** + * Given two streams, return a Runnable that + * executes both of their {@link BaseStream#close} methods in sequence, + * even if the first throws an exception, and if both throw exceptions, add + * any exceptions thrown by the second as suppressed exceptions of the first. + */ + static Runnable composedClose(BaseStream a, BaseStream b) { + return new Runnable() { + @Override + public void run() { + try { + a.close(); + } + catch (Throwable e1) { + try { + b.close(); + } + catch (Throwable e2) { + try { + e1.addSuppressed(e2); + } catch (Throwable ignore) {} + } + throw e1; + } + b.close(); + } + }; + } } diff --git a/jdk/test/java/nio/file/Files/StreamTest.java b/jdk/test/java/nio/file/Files/StreamTest.java index b5ff2977257..5304492f1db 100644 --- a/jdk/test/java/nio/file/Files/StreamTest.java +++ b/jdk/test/java/nio/file/Files/StreamTest.java @@ -43,14 +43,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.function.BiPredicate; -import java.util.stream.CloseableStream; +import java.util.stream.Stream; import java.util.stream.Collectors; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -138,14 +137,14 @@ public class StreamTest { } public void testBasic() { - try (CloseableStream s = Files.list(testFolder)) { - Object[] actual = s.sorted(Comparator.naturalOrder()).toArray(); + try (Stream s = Files.list(testFolder)) { + Object[] actual = s.sorted().toArray(); assertEquals(actual, level1); } catch (IOException ioe) { fail("Unexpected IOException"); } - try (CloseableStream s = Files.list(testFolder.resolve("empty"))) { + try (Stream s = Files.list(testFolder.resolve("empty"))) { int count = s.mapToInt(p -> 1).reduce(0, Integer::sum); assertEquals(count, 0, "Expect empty stream."); } catch (IOException ioe) { @@ -154,8 +153,8 @@ public class StreamTest { } public void testWalk() { - try (CloseableStream s = Files.walk(testFolder)) { - Object[] actual = s.sorted(Comparator.naturalOrder()).toArray(); + try (Stream s = Files.walk(testFolder)) { + Object[] actual = s.sorted().toArray(); assertEquals(actual, all); } catch (IOException ioe) { fail("Unexpected IOException"); @@ -163,9 +162,9 @@ public class StreamTest { } public void testWalkOneLevel() { - try (CloseableStream s = Files.walk(testFolder, 1)) { + try (Stream s = Files.walk(testFolder, 1)) { Object[] actual = s.filter(path -> ! path.equals(testFolder)) - .sorted(Comparator.naturalOrder()) + .sorted() .toArray(); assertEquals(actual, level1); } catch (IOException ioe) { @@ -176,8 +175,8 @@ public class StreamTest { public void testWalkFollowLink() { // If link is not supported, the directory structure won't have link. // We still want to test the behavior with FOLLOW_LINKS option. - try (CloseableStream s = Files.walk(testFolder, FileVisitOption.FOLLOW_LINKS)) { - Object[] actual = s.sorted(Comparator.naturalOrder()).toArray(); + try (Stream s = Files.walk(testFolder, FileVisitOption.FOLLOW_LINKS)) { + Object[] actual = s.sorted().toArray(); assertEquals(actual, all_folowLinks); } catch (IOException ioe) { fail("Unexpected IOException"); @@ -185,7 +184,7 @@ public class StreamTest { } private void validateFileSystemLoopException(Path start, Path... causes) { - try (CloseableStream s = Files.walk(start, FileVisitOption.FOLLOW_LINKS)) { + try (Stream s = Files.walk(start, FileVisitOption.FOLLOW_LINKS)) { try { int count = s.mapToInt(p -> 1).reduce(0, Integer::sum); fail("Should got FileSystemLoopException, but got " + count + "elements."); @@ -282,28 +281,28 @@ public class StreamTest { public void testFind() throws IOException { PathBiPredicate pred = new PathBiPredicate((path, attrs) -> true); - try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + try (Stream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { Set result = s.collect(Collectors.toCollection(TreeSet::new)); assertEquals(pred.visited(), all); assertEquals(result.toArray(new Path[0]), pred.visited()); } pred = new PathBiPredicate((path, attrs) -> attrs.isSymbolicLink()); - try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + try (Stream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { s.forEach(path -> assertTrue(Files.isSymbolicLink(path))); assertEquals(pred.visited(), all); } pred = new PathBiPredicate((path, attrs) -> path.getFileName().toString().startsWith("e")); - try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + try (Stream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { s.forEach(path -> assertEquals(path.getFileName().toString(), "empty")); assertEquals(pred.visited(), all); } pred = new PathBiPredicate((path, attrs) -> path.getFileName().toString().startsWith("l") && attrs.isRegularFile()); - try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + try (Stream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { s.forEach(path -> fail("Expect empty stream")); assertEquals(pred.visited(), all); } @@ -317,14 +316,14 @@ public class StreamTest { try { // zero lines assertTrue(Files.size(tmpfile) == 0, "File should be empty"); - try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + try (Stream s = Files.lines(tmpfile, US_ASCII)) { assertEquals(s.mapToInt(l -> 1).reduce(0, Integer::sum), 0, "No line expected"); } // one line byte[] hi = { (byte)'h', (byte)'i' }; Files.write(tmpfile, hi); - try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + try (Stream s = Files.lines(tmpfile, US_ASCII)) { List lines = s.collect(Collectors.toList()); assertTrue(lines.size() == 1, "One line expected"); assertTrue(lines.get(0).equals("hi"), "'Hi' expected"); @@ -334,7 +333,7 @@ public class StreamTest { List expected = Arrays.asList("hi", "there"); Files.write(tmpfile, expected, US_ASCII); assertTrue(Files.size(tmpfile) > 0, "File is empty"); - try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + try (Stream s = Files.lines(tmpfile, US_ASCII)) { List lines = s.collect(Collectors.toList()); assertTrue(lines.equals(expected), "Unexpected lines"); } @@ -342,7 +341,7 @@ public class StreamTest { // MalformedInputException byte[] bad = { (byte)0xff, (byte)0xff }; Files.write(tmpfile, bad); - try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + try (Stream s = Files.lines(tmpfile, US_ASCII)) { try { List lines = s.collect(Collectors.toList()); throw new RuntimeException("UncheckedIOException expected"); @@ -378,7 +377,7 @@ public class StreamTest { fsp.setFaultyMode(false); Path fakeRoot = fs.getRoot(); try { - try (CloseableStream s = Files.list(fakeRoot)) { + try (Stream s = Files.list(fakeRoot)) { s.forEach(path -> assertEquals(path.getFileName().toString(), "DirectoryIteratorException")); } } catch (UncheckedIOException uioe) { @@ -398,7 +397,7 @@ public class StreamTest { } try { - try (CloseableStream s = Files.list(fakeRoot)) { + try (Stream s = Files.list(fakeRoot)) { s.forEach(path -> fail("should not get here")); } } catch (UncheckedIOException uioe) { @@ -427,12 +426,12 @@ public class StreamTest { try { fsp.setFaultyMode(false); Path fakeRoot = fs.getRoot(); - try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { + try (Stream s = Files.list(fakeRoot.resolve("dir2"))) { // only one file s.forEach(path -> assertEquals(path.getFileName().toString(), "IOException")); } - try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { + try (Stream s = Files.walk(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); // ordered as depth-first @@ -440,13 +439,13 @@ public class StreamTest { } fsp.setFaultyMode(true); - try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { + try (Stream s = Files.list(fakeRoot.resolve("dir2"))) { s.forEach(path -> fail("should have caused exception")); } catch (UncheckedIOException uioe) { assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); } - try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { + try (Stream s = Files.walk(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); fail("should not reach here due to IOException"); @@ -454,7 +453,7 @@ public class StreamTest { assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); } - try (CloseableStream s = Files.walk( + try (Stream s = Files.walk( fakeRoot.resolve("empty").resolve("IOException"))) { String[] result = s.map(path -> path.getFileName().toString()) @@ -502,20 +501,20 @@ public class StreamTest { fsp.setFaultyMode(false); Path fakeRoot = fs.getRoot(); // validate setting - try (CloseableStream s = Files.list(fakeRoot.resolve("empty"))) { + try (Stream s = Files.list(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "SecurityException", "sample" }); } - try (CloseableStream s = Files.walk(fakeRoot.resolve("dir2"))) { + try (Stream s = Files.walk(fakeRoot.resolve("dir2"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "dir2", "SecurityException", "fileInSE", "file" }); } if (supportsLinks) { - try (CloseableStream s = Files.list(fakeRoot.resolve("dir"))) { + try (Stream s = Files.list(fakeRoot.resolve("dir"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "d1", "f1", "lnDir2", "SecurityException", "lnDirSE", "lnFileSE" }); @@ -525,13 +524,13 @@ public class StreamTest { // execute test fsp.setFaultyMode(true); // ignore file cause SecurityException - try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { + try (Stream s = Files.walk(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "empty", "sample" }); } // skip folder cause SecurityException - try (CloseableStream s = Files.walk(fakeRoot.resolve("dir2"))) { + try (Stream s = Files.walk(fakeRoot.resolve("dir2"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "dir2", "file" }); @@ -539,14 +538,14 @@ public class StreamTest { if (supportsLinks) { // not following links - try (CloseableStream s = Files.walk(fakeRoot.resolve("dir"))) { + try (Stream s = Files.walk(fakeRoot.resolve("dir"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "dir", "d1", "f1", "lnDir2", "lnDirSE", "lnFileSE" }); } // following links - try (CloseableStream s = Files.walk(fakeRoot.resolve("dir"), FileVisitOption.FOLLOW_LINKS)) { + try (Stream s = Files.walk(fakeRoot.resolve("dir"), FileVisitOption.FOLLOW_LINKS)) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); // ?? Should fileInSE show up? @@ -556,19 +555,19 @@ public class StreamTest { } // list instead of walk - try (CloseableStream s = Files.list(fakeRoot.resolve("empty"))) { + try (Stream s = Files.list(fakeRoot.resolve("empty"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "sample" }); } - try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { + try (Stream s = Files.list(fakeRoot.resolve("dir2"))) { String[] result = s.map(path -> path.getFileName().toString()) .toArray(String[]::new); assertEqualsNoOrder(result, new String[] { "file" }); } // root cause SecurityException should be reported - try (CloseableStream s = Files.walk( + try (Stream s = Files.walk( fakeRoot.resolve("dir2").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) @@ -579,7 +578,7 @@ public class StreamTest { } // Walk a file cause SecurityException, we should get SE - try (CloseableStream s = Files.walk( + try (Stream s = Files.walk( fakeRoot.resolve("dir").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) @@ -590,7 +589,7 @@ public class StreamTest { } // List a file cause SecurityException, we should get SE as cannot read attribute - try (CloseableStream s = Files.list( + try (Stream s = Files.list( fakeRoot.resolve("dir2").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) @@ -600,7 +599,7 @@ public class StreamTest { assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); } - try (CloseableStream s = Files.list( + try (Stream s = Files.list( fakeRoot.resolve("dir").resolve("SecurityException"))) { String[] result = s.map(path -> path.getFileName().toString()) @@ -627,7 +626,7 @@ public class StreamTest { } public void testConstructException() { - try (CloseableStream s = Files.lines(testFolder.resolve("notExist"), Charset.forName("UTF-8"))) { + try (Stream s = Files.lines(testFolder.resolve("notExist"), Charset.forName("UTF-8"))) { s.forEach(l -> fail("File is not even exist!")); } catch (IOException ioe) { assertTrue(ioe instanceof NoSuchFileException); @@ -635,24 +634,26 @@ public class StreamTest { } public void testClosedStream() throws IOException { - try (CloseableStream s = Files.list(testFolder)) { + try (Stream s = Files.list(testFolder)) { s.close(); - Object[] actual = s.sorted(Comparator.naturalOrder()).toArray(); - assertTrue(actual.length <= level1.length); - } - - try (CloseableStream s = Files.walk(testFolder)) { - s.close(); - Object[] actual = s.sorted(Comparator.naturalOrder()).toArray(); + Object[] actual = s.sorted().toArray(); fail("Operate on closed stream should throw IllegalStateException"); } catch (IllegalStateException ex) { // expected } - try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, + try (Stream s = Files.walk(testFolder)) { + s.close(); + Object[] actual = s.sorted().toArray(); + fail("Operate on closed stream should throw IllegalStateException"); + } catch (IllegalStateException ex) { + // expected + } + + try (Stream s = Files.find(testFolder, Integer.MAX_VALUE, (p, attr) -> true)) { s.close(); - Object[] actual = s.sorted(Comparator.naturalOrder()).toArray(); + Object[] actual = s.sorted().toArray(); fail("Operate on closed stream should throw IllegalStateException"); } catch (IllegalStateException ex) { // expected diff --git a/jdk/test/java/util/stream/bootlib/java/util/stream/DoubleStreamTestScenario.java b/jdk/test/java/util/stream/bootlib/java/util/stream/DoubleStreamTestScenario.java index d4459cf0c9b..43b99973dd2 100644 --- a/jdk/test/java/util/stream/bootlib/java/util/stream/DoubleStreamTestScenario.java +++ b/jdk/test/java/util/stream/bootlib/java/util/stream/DoubleStreamTestScenario.java @@ -40,7 +40,7 @@ import java.util.function.Function; @SuppressWarnings({"rawtypes", "unchecked"}) public enum DoubleStreamTestScenario implements OpTestCase.BaseStreamTestScenario { - STREAM_FOR_EACH(false) { + STREAM_FOR_EACH_WITH_CLOSE(false) { > void _run(TestData data, DoubleConsumer b, Function m) { DoubleStream s = m.apply(data.stream()); @@ -48,6 +48,7 @@ public enum DoubleStreamTestScenario implements OpTestCase.BaseStreamTestScenari s = s.sequential(); } s.forEach(b); + s.close(); } }, diff --git a/jdk/test/java/util/stream/bootlib/java/util/stream/IntStreamTestScenario.java b/jdk/test/java/util/stream/bootlib/java/util/stream/IntStreamTestScenario.java index f399cdeef25..4127a7b5a81 100644 --- a/jdk/test/java/util/stream/bootlib/java/util/stream/IntStreamTestScenario.java +++ b/jdk/test/java/util/stream/bootlib/java/util/stream/IntStreamTestScenario.java @@ -40,7 +40,7 @@ import java.util.function.IntConsumer; @SuppressWarnings({"rawtypes", "unchecked"}) public enum IntStreamTestScenario implements OpTestCase.BaseStreamTestScenario { - STREAM_FOR_EACH(false) { + STREAM_FOR_EACH_WITH_CLOSE(false) { > void _run(TestData data, IntConsumer b, Function m) { IntStream s = m.apply(data.stream()); @@ -48,6 +48,7 @@ public enum IntStreamTestScenario implements OpTestCase.BaseStreamTestScenario { s = s.sequential(); } s.forEach(b); + s.close(); } }, diff --git a/jdk/test/java/util/stream/bootlib/java/util/stream/LongStreamTestScenario.java b/jdk/test/java/util/stream/bootlib/java/util/stream/LongStreamTestScenario.java index 3010745a55d..0a334bc0261 100644 --- a/jdk/test/java/util/stream/bootlib/java/util/stream/LongStreamTestScenario.java +++ b/jdk/test/java/util/stream/bootlib/java/util/stream/LongStreamTestScenario.java @@ -40,7 +40,7 @@ import java.util.function.LongConsumer; @SuppressWarnings({"rawtypes", "unchecked"}) public enum LongStreamTestScenario implements OpTestCase.BaseStreamTestScenario { - STREAM_FOR_EACH(false) { + STREAM_FOR_EACH_WITH_CLOSE(false) { > void _run(TestData data, LongConsumer b, Function m) { LongStream s = m.apply(data.stream()); @@ -48,6 +48,7 @@ public enum LongStreamTestScenario implements OpTestCase.BaseStreamTestScenario s = s.sequential(); } s.forEach(b); + s.close(); } }, diff --git a/jdk/test/java/util/stream/bootlib/java/util/stream/StreamTestScenario.java b/jdk/test/java/util/stream/bootlib/java/util/stream/StreamTestScenario.java index b1abd4320db..d1a446234a7 100644 --- a/jdk/test/java/util/stream/bootlib/java/util/stream/StreamTestScenario.java +++ b/jdk/test/java/util/stream/bootlib/java/util/stream/StreamTestScenario.java @@ -39,7 +39,7 @@ import java.util.function.Function; @SuppressWarnings({"rawtypes", "unchecked"}) public enum StreamTestScenario implements OpTestCase.BaseStreamTestScenario { - STREAM_FOR_EACH(false) { + STREAM_FOR_EACH_WITH_CLOSE(false) { > void _run(TestData data, Consumer b, Function> m) { Stream s = m.apply(data.stream()); @@ -47,6 +47,7 @@ public enum StreamTestScenario implements OpTestCase.BaseStreamTestScenario { s = s.sequential(); } s.forEach(b); + s.close(); } }, diff --git a/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/StreamCloseTest.java b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/StreamCloseTest.java new file mode 100644 index 00000000000..51ffd4b9010 --- /dev/null +++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/StreamCloseTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.tests.java.util.stream; + +import java.util.Arrays; +import java.util.stream.OpTestCase; +import java.util.stream.Stream; + +import org.testng.annotations.Test; + +import static java.util.stream.LambdaTestHelpers.countTo; + +/** + * StreamCloseTest + * + * @author Brian Goetz + */ +@Test(groups = { "serialization-hostile" }) +public class StreamCloseTest extends OpTestCase { + public void testEmptyCloseHandler() { + try (Stream ints = countTo(100).stream()) { + ints.forEach(i -> {}); + } + } + + public void testOneCloseHandler() { + final boolean[] holder = new boolean[1]; + Runnable closer = () -> { holder[0] = true; }; + + try (Stream ints = countTo(100).stream()) { + ints.onClose(closer); + ints.forEach(i -> {}); + } + assertTrue(holder[0]); + + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().onClose(closer)) { + ints.forEach(i -> {}); + } + assertTrue(holder[0]); + + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().filter(e -> true).onClose(closer)) { + ints.forEach(i -> {}); + } + assertTrue(holder[0]); + + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().filter(e -> true).onClose(closer).filter(e -> true)) { + ints.forEach(i -> {}); + } + assertTrue(holder[0]); + } + + public void testTwoCloseHandlers() { + final boolean[] holder = new boolean[2]; + Runnable close1 = () -> { holder[0] = true; }; + Runnable close2 = () -> { holder[1] = true; }; + + try (Stream ints = countTo(100).stream()) { + ints.onClose(close1).onClose(close2); + ints.forEach(i -> {}); + } + assertTrue(holder[0] && holder[1]); + + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().onClose(close1).onClose(close2)) { + ints.forEach(i -> {}); + } + assertTrue(holder[0] && holder[1]); + + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().filter(e -> true).onClose(close1).onClose(close2)) { + ints.forEach(i -> {}); + } + assertTrue(holder[0] && holder[1]); + + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().filter(e -> true).onClose(close1).onClose(close2).filter(e -> true)) { + ints.forEach(i -> {}); + } + assertTrue(holder[0] && holder[1]); + } + + public void testCascadedExceptions() { + final boolean[] holder = new boolean[3]; + boolean caught = false; + Runnable close1 = () -> { holder[0] = true; throw new RuntimeException("1"); }; + Runnable close2 = () -> { holder[1] = true; throw new RuntimeException("2"); }; + Runnable close3 = () -> { holder[2] = true; throw new RuntimeException("3"); }; + + try (Stream ints = countTo(100).stream()) { + ints.onClose(close1).onClose(close2).onClose(close3); + ints.forEach(i -> {}); + } + catch (RuntimeException e) { + assertCascaded(e, 3); + assertTrue(holder[0] && holder[1] && holder[2]); + caught = true; + } + assertTrue(caught); + + Arrays.fill(holder, false); + caught = false; + try (Stream ints = countTo(100).stream().onClose(close1).onClose(close2).onClose(close3)) { + ints.forEach(i -> {}); + } + catch (RuntimeException e) { + assertCascaded(e, 3); + assertTrue(holder[0] && holder[1] && holder[2]); + caught = true; + } + assertTrue(caught); + + caught = false; + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().filter(e -> true).onClose(close1).onClose(close2).onClose(close3)) { + ints.forEach(i -> {}); + } + catch (RuntimeException e) { + assertCascaded(e, 3); + assertTrue(holder[0] && holder[1] && holder[2]); + caught = true; + } + assertTrue(caught); + + caught = false; + Arrays.fill(holder, false); + try (Stream ints = countTo(100).stream().filter(e -> true).onClose(close1).onClose(close2).filter(e -> true).onClose(close3)) { + ints.forEach(i -> {}); + } + catch (RuntimeException e) { + assertCascaded(e, 3); + assertTrue(holder[0] && holder[1] && holder[2]); + caught = true; + } + assertTrue(caught); + } + + private void assertCascaded(RuntimeException e, int n) { + assertTrue(e.getMessage().equals("1")); + assertTrue(e.getSuppressed().length == n - 1); + for (int i=0; i Date: Tue, 3 Sep 2013 22:37:07 +0100 Subject: [PATCH 12/75] 8017195: Introduce option to setKeepAlive parameter on CORBA sockets Reviewed-by: chegar, msheppar --- .../sun/corba/transport/KeepAliveSockets.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 jdk/test/com/sun/corba/transport/KeepAliveSockets.java diff --git a/jdk/test/com/sun/corba/transport/KeepAliveSockets.java b/jdk/test/com/sun/corba/transport/KeepAliveSockets.java new file mode 100644 index 00000000000..4d9b9d97855 --- /dev/null +++ b/jdk/test/com/sun/corba/transport/KeepAliveSockets.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8017195 + * @summary Introduce option to setKeepAlive parameter on CORBA sockets + * + * @run main/othervm KeepAliveSockets + * @run main/othervm -Dcom.sun.CORBA.transport.enableTcpKeepAlive KeepAliveSockets + * @run main/othervm -Dcom.sun.CORBA.transport.enableTcpKeepAlive=true KeepAliveSockets + * @run main/othervm -Dcom.sun.CORBA.transport.enableTcpKeepAlive=false KeepAliveSockets + */ + +import java.lang.*; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.ServerSocketChannel; +import java.util.*; +import com.sun.corba.se.impl.orb.*; + +import com.sun.corba.se.impl.transport.*; + +public class KeepAliveSockets { + + public static void main(String[] args) throws Exception { + + boolean keepAlive = false; + String prop = System.getProperty("com.sun.CORBA.transport.enableTcpKeepAlive"); + if (prop != null) + keepAlive = !"false".equalsIgnoreCase(prop); + + DefaultSocketFactoryImpl sfImpl = new DefaultSocketFactoryImpl(); + ORBImpl orb = new ORBImpl(); + orb.set_parameters(null); + sfImpl.setORB(orb); + + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.socket().bind(new InetSocketAddress(0)); + + InetSocketAddress isa = new InetSocketAddress("localhost", ssc.socket().getLocalPort()); + Socket s = sfImpl.createSocket("ignore", isa); + System.out.println("Received factory socket" + s); + if (keepAlive != s.getKeepAlive()) + throw new RuntimeException("KeepAlive value not honoured in CORBA socket"); + } + +} From 25af2121aa224df049dbf1dbc50dc869523bdeed Mon Sep 17 00:00:00 2001 From: Henry Jen Date: Tue, 3 Sep 2013 16:05:45 -0700 Subject: [PATCH 13/75] 8023997: j.l.String.join(java.lang.CharSequence, java.lang.Iterable) sample doesn't compile and is incorrect Reviewed-by: alanb --- jdk/src/share/classes/java/lang/String.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdk/src/share/classes/java/lang/String.java b/jdk/src/share/classes/java/lang/String.java index 651a85017ed..e9d75afbbce 100644 --- a/jdk/src/share/classes/java/lang/String.java +++ b/jdk/src/share/classes/java/lang/String.java @@ -2457,8 +2457,8 @@ public final class String * String message = String.join(" ", strings); * //message returned is: "Java is cool" * - * Set strings = new HashSet<>(); - * Strings.add("Java"); strings.add("is"); + * Set strings = new LinkedHashSet<>(); + * strings.add("Java"); strings.add("is"); * strings.add("very"); strings.add("cool"); * String message = String.join("-", strings); * //message returned is: "Java-is-very-cool" From 7fc1c28757b950e48af2058b2bb7c45f7c9979b2 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Tue, 3 Sep 2013 23:47:27 -0400 Subject: [PATCH 14/75] 8024140: [TESTBUG] Profile based regression test groups for jdk repo Reviewed-by: alanb, chegar --- jdk/test/TEST.groups | 338 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) diff --git a/jdk/test/TEST.groups b/jdk/test/TEST.groups index ed70cbf6704..a59b38853ff 100644 --- a/jdk/test/TEST.groups +++ b/jdk/test/TEST.groups @@ -212,3 +212,341 @@ jdk_desktop = \ :jdk_swing \ :jdk_sound \ :jdk_imageio + +############################################################################### +# Profile-based Test Group Definitions +# +# These groups define the tests that cover the different possible runtimes: +# - compact1, compact2, compact3, full JRE, JDK +# +# In addition they support testing of the minimal VM on compact1 and compact2. +# Essentially this defines groups based around the specified API's and VM +# services available in the runtime. +# +# The groups are defined hierarchically in two forms: +# - The need_xxx groups list all the tests that have a dependency on +# a specific profile. This is either because it tests a feature in +# that profile, or the test infrastructure uses a feature in that +# profile. +# - The primary groups are defined in terms of the other primary groups +# combined with the needs_xxx groups (including and excluding them as +# appropriate). For example the jre can run all tests from compact3, plus +# those from needs_jre, but excluding those from need_jdk. +# +# The bottom group defines all the actual tests to be considered, simply +# by listing the top-level test directories. + +# Full JDK can run all tests +# +jdk = \ + :jre \ + :needs_jdk + +# Tests that require a full JDK to execute. Either they test a feature +# only in the JDK or they use tools that are only in the JDK. The latter +# can be resolved in some cases by using tools from the compile-jdk. +# +needs_jdk = \ + :jdk_jdi \ + com/sun/tools \ + demo \ + sun/security/tools/jarsigner \ + sun/rmi/rmic \ + sun/tools \ + sun/jvmstat \ + tools \ + com/sun/jmx/remote/NotificationMarshalVersions/TestSerializationMismatch.java \ + java/io/Serializable/serialver \ + java/lang/invoke/lambda/LambdaAccessControlDoPrivilegedTest.java \ + java/lang/invoke/lambda/LambdaAccessControlTest.java \ + java/lang/System/MacEncoding/TestFileEncoding.java \ + java/net/URLClassLoader/closetest/GetResourceAsStream.java \ + java/util/Collections/EmptyIterator.java \ + java/util/concurrent/locks/Lock/TimedAcquireLeak.java \ + java/util/jar/Manifest/CreateManifest.java \ + java/util/jar/JarInputStream/ExtraFileInMetaInf.java \ + java/util/logging/AnonLoggerWeakRefLeak.sh \ + java/util/logging/LoggerWeakRefLeak.sh \ + java/util/zip/3GBZipFiles.sh \ + jdk/lambda/FDTest.java \ + jdk/lambda/separate/Compiler.java \ + sun/management/jdp/JdpTest.sh \ + sun/management/jmxremote/bootstrap/JvmstatCountersTest.java \ + sun/management/jmxremote/bootstrap/LocalManagementTest.sh \ + sun/misc/JarIndex/metaInfFilenames/Basic.java \ + sun/misc/JarIndex/JarIndexMergeForClassLoaderTest.java \ + sun/reflect/CallerSensitive/CallerSensitiveFinder.java \ + sun/reflect/CallerSensitive/MissingCallerSensitive.java \ + sun/security/util/Resources/NewNamesFormat.java \ + vm/verifier/defaultMethods/DefaultMethodRegressionTestsRun.java + +# JRE adds further tests to compact3 +# +jre = \ + :compact3 \ + :needs_jre \ + -:needs_jdk + +# Tests that require the full JRE +# +needs_jre = \ + :needs_charsets \ + :jdk_desktop \ + com/sun/corba \ + com/sun/jndi/cosnaming \ + sun/net/ftp \ + sun/net/www/protocol/ftp \ + sun/security/tools/policytool \ + java/net/URI/URItoURLTest.java \ + java/net/URL/URIToURLTest.java \ + java/net/URLConnection/HandleContentTypeWithAttrs.java \ + java/security/Security/ClassLoaderDeadlock/ClassLoaderDeadlock.sh \ + java/security/Security/ClassLoaderDeadlock/Deadlock.sh \ + java/util/logging/Listeners.java \ + java/util/logging/ListenersWithSM.java \ + java/util/ResourceBundle/Control/Bug6530694.java \ + java/text/Bidi/BidiConformance.java \ + java/text/Bidi/BidiEmbeddingTest.java \ + java/text/Bidi/Bug6665028.java \ + java/text/Bidi/Bug7042148.java \ + java/text/Bidi/Bug7051769.java \ + javax/crypto/Cipher/CipherStreamClose.java \ + javax/management/monitor/AttributeArbitraryDataTypeTest.java \ + jdk/lambda/vm/InterfaceAccessFlagsTest.java \ + sun/misc/URLClassPath/ClassnameCharTest.java + +# Tests dependent on the optional charsets.jar +# These are isolated for easy exclusions +# +needs_charsets = \ + java/io/OutputStreamWriter/TestWrite.java \ + java/nio/charset/RemovingSunIO/SunioAlias.java \ + java/nio/charset/coders/Check.java \ + java/nio/charset/Charset/CharsetContainmentTest.java \ + java/nio/charset/Charset/Contains.java \ + java/nio/charset/Charset/NIOCharsetAvailabilityTest.java \ + java/nio/charset/Charset/RegisteredCharsets.java \ + java/nio/charset/CharsetEncoder/Flush.java \ + java/nio/charset/coders/CheckSJISMappingProp.sh \ + java/nio/charset/coders/ResetISO2022JP.java \ + java/util/Locale/InternationalBAT.java \ + java/util/Locale/LocaleProviders.sh \ + java/util/Calendar/CldrFormatNamesTest.java \ + java/util/TimeZone/CLDRDisplayNamesTest.java \ + java/util/zip/ZipCoding.java \ + sun/nio/cs/EucJpLinux0212.java \ + sun/nio/cs/EUCJPUnderflowDecodeTest.java \ + sun/nio/cs/EuroConverter.java \ + sun/nio/cs/JISAutoDetectTest.java \ + sun/nio/cs/OLD/TestIBMDB.java \ + sun/nio/cs/SJISCanEncode.java \ + sun/nio/cs/Test6254467.java \ + sun/nio/cs/TestCompoundTest.java \ + sun/nio/cs/TestCp834_SBCS.java \ + sun/nio/cs/TestEUC_TW.java \ + sun/nio/cs/TestISO2022CNDecoder.java \ + sun/nio/cs/TestISO2022JPEncoder.java \ + sun/nio/cs/TestISO2022JPSubBytes.java \ + sun/nio/cs/TestIllegalSJIS.java \ + sun/nio/cs/TestJIS0208Decoder.java \ + sun/nio/cs/TestJIS0212Decoder.java \ + sun/nio/cs/TestMiscEUC_JP.java \ + sun/nio/cs/TestSJIS0213_SM.java \ + sun/nio/cs/BufferUnderflowEUCTWTest.java \ + sun/nio/cs/CheckCaseInsensitiveEncAliases.java \ + sun/nio/cs/CheckHistoricalNames.java \ + sun/nio/cs/EucJpLinuxDecoderRecoveryTest.java \ + sun/nio/cs/HWKatakanaMS932EncodeTest.java \ + sun/nio/cs/ISCIITest.java \ + sun/nio/cs/LatinCharReplacementTWTest.java \ + sun/nio/cs/NIOJISAutoDetectTest.java \ + sun/nio/cs/StreamEncoderClose.java \ + sun/nio/cs/SurrogateGB18030Test.java \ + sun/nio/cs/SurrogateTestEUCTW.java \ + sun/nio/cs/SurrogateTestHKSCS.java \ + sun/nio/cs/TestConverterDroppedCharacters.java \ + sun/nio/cs/TestCp93xSISO.java \ + sun/nio/cs/TestIBM1364.java \ + sun/nio/cs/TestIBMBugs.java \ + sun/nio/cs/TestIllegalISO2022Esc.java \ + sun/nio/cs/TestISO2022JP.java \ + sun/nio/cs/TestMS5022X.java \ + sun/nio/cs/TestSJIS0213.java \ + sun/nio/cs/TestTrailingEscapesISO2022JP.java \ + sun/nio/cs/TestUni2HKSCS.java \ + sun/nio/cs/ZeroedByteArrayEUCTWTest.java + +# Compact 3 adds further tests to compact2 +# +compact3 = \ + :compact2 \ + :needs_compact3 \ + -:needs_jre \ + -:needs_jdk + + +# Tests that require compact3 API's +# +needs_compact3 = \ + :jdk_instrument \ + :jdk_jmx \ + :jdk_management \ + :jdk_sctp \ + com/sun/jndi \ + com/sun/org/apache/xml/internal/security \ + com/sun/security/auth \ + com/sun/security/sasl \ + com/sun/security/jgss \ + com/sun/tracing \ + java/util/prefs \ + javax/naming \ + javax/security \ + javax/smartcardio \ + javax/sql/rowset \ + javax/xml/crypto \ + sun/security/acl \ + sun/security/jgss \ + sun/security/krb5 \ + java/lang/System/MacEncoding/TestFileEncoding.java \ + java/nio/channels/AsynchronousSocketChannel/Leaky.java \ + java/security/PermissionCollection/Concurrent.java \ + java/security/Principal/Implies.java \ + java/security/cert/GetInstance.java \ + java/util/logging/DrainFindDeadlockTest.java \ + java/util/logging/LoggingMXBeanTest.java \ + sun/net/www/http/KeepAliveCache/B5045306.java \ + sun/security/provider/PolicyFile/Alias.java \ + sun/security/provider/PolicyFile/Comparator.java \ + sun/security/provider/PolicyFile/SelfWildcard.java \ + sun/security/ssl/com/sun/net/ssl/internal/ssl/SSLEngineImpl/SSLEngineDeadlock.java \ + sun/security/util/Oid/OidFormat.java \ + sun/security/util/Resources/Format.java \ + sun/security/util/Resources/NewNamesFormat.java + +# Compact 2 adds full VM tests +compact2 = \ + :compact2_minimal \ + :compact1 \ + :needs_full_vm_compact2 \ + -:needs_compact3 \ + -:needs_jre \ + -:needs_jdk + +# Tests that require compact2 API's and a full VM +# +needs_full_vm_compact2 = + +# Minimal VM on Compact 2 adds in some compact2 tests +# +compact2_minimal = \ + :compact1_minimal \ + :needs_compact2 \ + -:needs_compact3 \ + -:needs_jre \ + -:needs_jdk + +# Tests that require compact2 API's +# +needs_compact2 = \ + :jdk_rmi \ + :jdk_time \ + com/sun/org/apache \ + com/sun/net/httpserver \ + java/sql \ + javax/sql \ + javax/xml \ + jdk/lambda \ + sun/net/www/http \ + sun/net/www/protocol/http \ + java/io/BufferedReader/Lines.java \ + java/lang/reflect/DefaultStaticTest/DefaultStaticInvokeTest.java \ + java/lang/CharSequence/DefaultTest.java \ + java/lang/IntegralPrimitiveToString.java \ + java/lang/PrimitiveSumMinMaxTest.java \ + java/lang/String/StringJoinTest.java \ + java/lang/Thread/StopThrowable.java \ + java/net/Authenticator/Deadlock.java \ + java/net/CookieHandler/LocalHostCookie.java \ + java/net/CookieHandler/CookieManagerTest.java \ + java/net/CookieHandler/EmptyCookieHeader.java \ + java/net/HttpCookie/IllegalCookieNameTest.java \ + java/net/HttpURLConnection/UnmodifiableMaps.java \ + java/net/HttpURLPermission/URLTest.java \ + java/net/ResponseCache/Test.java \ + java/net/URLClassLoader/ClassLoad.java \ + java/net/URLClassLoader/closetest/CloseTest.java \ + java/nio/Buffer/Chars.java \ + java/nio/file/Files/StreamTest.java \ + java/security/BasicPermission/Wildcard.java \ + java/util/Arrays/ParallelPrefix.java \ + java/util/Arrays/SetAllTest.java \ + java/util/BitSet/BitSetStreamTest.java \ + java/util/Collection/CollectionDefaults.java \ + java/util/Collection/ListDefaults.java \ + java/util/Collections/CheckedIdentityMap.java \ + java/util/Collections/CheckedMapBash.java \ + java/util/Collections/CheckedSetBash.java \ + java/util/Collections/EmptyCollectionSerialization.java \ + java/util/Collections/EmptyNavigableMap.java \ + java/util/Collections/EmptyNavigableSet.java \ + java/util/Collections/UnmodifiableMapEntrySet.java \ + java/util/Comparator/BasicTest.java \ + java/util/Comparator/TypeTest.java \ + java/util/Iterator/IteratorDefaults.java \ + java/util/Iterator/PrimitiveIteratorDefaults.java \ + java/util/Map/BasicSerialization.java \ + java/util/Map/Defaults.java \ + java/util/Map/EntryComparators.java \ + java/util/Optional/Basic.java \ + java/util/Optional/BasicDouble.java \ + java/util/Optional/BasicInt.java \ + java/util/Optional/BasicLong.java \ + java/util/Random/RandomStreamTest.java \ + java/util/ResourceBundle/Bug6359330.java \ + java/util/Spliterator/SpliteratorCharacteristics.java \ + java/util/Spliterator/SpliteratorCollisions.java \ + java/util/Spliterator/SpliteratorLateBindingFailFastTest.java \ + java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java \ + java/util/StringJoiner/MergeTest.java \ + java/util/StringJoiner/StringJoinerTest.java \ + java/util/concurrent/atomic/AtomicReferenceTest.java \ + java/util/function/BinaryOperator/BasicTest.java \ + java/util/logging/LoggerSupplierAPIsTest.java \ + java/util/zip/ZipFile/StreamZipEntriesTest.java \ + java/util/zip/ZipFile/DeleteTempJar.java \ + javax/crypto/Cipher/CipherStreamClose.java \ + sun/misc/URLClassPath/ClassnameCharTest.java \ + sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/HttpsCreateSockTest.java \ + sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/HttpsSocketFacTest.java + +# Compact 1 adds full VM tests +# +compact1 = \ + :compact1_minimal \ + :needs_full_vm_compact1 \ + -:needs_compact2 \ + -:needs_full_vm_compact2 \ + -:needs_compact3 \ + -:needs_jre \ + -:needs_jdk + +# Tests that require compact1 API's and a full VM +# +needs_full_vm_compact1 = + +# All tests that run on the most minimal configuration: Minimal VM on Compact 1 +compact1_minimal = \ + com \ + java \ + javax \ + jdk \ + lib \ + sample \ + sun \ + vm \ + -:needs_full_vm_compact1 \ + -:needs_full_vm_compact2 \ + -:needs_compact2 \ + -:needs_compact3 \ + -:needs_jre \ + -:needs_jdk From 59440ee0bef7cce9a5f528e7840d098b89e9fc43 Mon Sep 17 00:00:00 2001 From: John Rose Date: Tue, 3 Sep 2013 21:42:56 -0700 Subject: [PATCH 15/75] 8008688: Make MethodHandleInfo public A major overhaul to MethodHandleInfo and method handles in general. Reviewed-by: vlivanov, twisti --- .../AbstractValidatingLambdaMetafactory.java | 2 +- .../java/lang/invoke/InfoFromMemberName.java | 145 ++++ .../classes/java/lang/invoke/Invokers.java | 2 + .../classes/java/lang/invoke/MemberName.java | 41 +- .../java/lang/invoke/MethodHandle.java | 40 +- .../java/lang/invoke/MethodHandleImpl.java | 75 +- .../java/lang/invoke/MethodHandleInfo.java | 286 +++++-- .../java/lang/invoke/MethodHandleNatives.java | 63 +- .../java/lang/invoke/MethodHandles.java | 93 ++- .../java/lang/invoke/SerializedLambda.java | 2 +- .../java/lang/invoke/7087570/Test7087570.java | 130 +-- .../java/lang/invoke/RevealDirectTest.java | 753 ++++++++++++++++++ .../java/lang/invoke/jtreg.security.policy | 9 + 13 files changed, 1437 insertions(+), 204 deletions(-) create mode 100644 jdk/src/share/classes/java/lang/invoke/InfoFromMemberName.java create mode 100644 jdk/test/java/lang/invoke/RevealDirectTest.java create mode 100644 jdk/test/java/lang/invoke/jtreg.security.policy diff --git a/jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java index 79b3b69fcf3..92a44a048f3 100644 --- a/jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -124,7 +124,7 @@ import static sun.invoke.util.Wrapper.isWrapperType; this.samMethodType = samMethodType; this.implMethod = implMethod; - this.implInfo = new MethodHandleInfo(implMethod); + this.implInfo = caller.revealDirect(implMethod); // @@@ Temporary work-around pending resolution of 8005119 this.implKind = (implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial) ? MethodHandleInfo.REF_invokeVirtual diff --git a/jdk/src/share/classes/java/lang/invoke/InfoFromMemberName.java b/jdk/src/share/classes/java/lang/invoke/InfoFromMemberName.java new file mode 100644 index 00000000000..0ecd005a8d3 --- /dev/null +++ b/jdk/src/share/classes/java/lang/invoke/InfoFromMemberName.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.invoke; + +import java.security.*; +import java.lang.reflect.*; +import java.lang.invoke.MethodHandleNatives.Constants; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandleStatics.*; + +/* + * Auxiliary to MethodHandleInfo, wants to nest in MethodHandleInfo but must be non-public. + */ +/*non-public*/ +final +class InfoFromMemberName implements MethodHandleInfo { + private final MemberName member; + private final int referenceKind; + + InfoFromMemberName(Lookup lookup, MemberName member, byte referenceKind) { + assert(member.isResolved() || member.isMethodHandleInvoke()); + assert(member.referenceKindIsConsistentWith(referenceKind)); + this.member = member; + this.referenceKind = referenceKind; + } + + @Override + public Class getDeclaringClass() { + return member.getDeclaringClass(); + } + + @Override + public String getName() { + return member.getName(); + } + + @Override + public MethodType getMethodType() { + return member.getMethodOrFieldType(); + } + + @Override + public int getModifiers() { + return member.getModifiers(); + } + + @Override + public int getReferenceKind() { + return referenceKind; + } + + @Override + public String toString() { + return MethodHandleInfo.toString(getReferenceKind(), getDeclaringClass(), getName(), getMethodType()); + } + + @Override + public T reflectAs(Class expected, Lookup lookup) { + if (member.isMethodHandleInvoke() && !member.isVarargs()) { + // This member is an instance of a signature-polymorphic method, which cannot be reflected + // A method handle invoker can come in either of two forms: + // A generic placeholder (present in the source code, and varargs) + // and a signature-polymorphic instance (synthetic and not varargs). + // For more information see comments on {@link MethodHandleNatives#linkMethod}. + throw new IllegalArgumentException("cannot reflect signature polymorphic method"); + } + Member mem = AccessController.doPrivileged(new PrivilegedAction() { + public Member run() { + try { + return reflectUnchecked(); + } catch (ReflectiveOperationException ex) { + throw new IllegalArgumentException(ex); + } + } + }); + try { + Class defc = getDeclaringClass(); + byte refKind = (byte) getReferenceKind(); + lookup.checkAccess(refKind, defc, convertToMemberName(refKind, mem)); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException(ex); + } + return expected.cast(mem); + } + + private Member reflectUnchecked() throws ReflectiveOperationException { + byte refKind = (byte) getReferenceKind(); + Class defc = getDeclaringClass(); + boolean isPublic = Modifier.isPublic(getModifiers()); + if (MethodHandleNatives.refKindIsMethod(refKind)) { + if (isPublic) + return defc.getMethod(getName(), getMethodType().parameterArray()); + else + return defc.getDeclaredMethod(getName(), getMethodType().parameterArray()); + } else if (MethodHandleNatives.refKindIsConstructor(refKind)) { + if (isPublic) + return defc.getConstructor(getMethodType().parameterArray()); + else + return defc.getDeclaredConstructor(getMethodType().parameterArray()); + } else if (MethodHandleNatives.refKindIsField(refKind)) { + if (isPublic) + return defc.getField(getName()); + else + return defc.getDeclaredField(getName()); + } else { + throw new IllegalArgumentException("referenceKind="+refKind); + } + } + + private static MemberName convertToMemberName(byte refKind, Member mem) throws IllegalAccessException { + if (mem instanceof Method) { + boolean wantSpecial = (refKind == REF_invokeSpecial); + return new MemberName((Method) mem, wantSpecial); + } else if (mem instanceof Constructor) { + return new MemberName((Constructor) mem); + } else if (mem instanceof Field) { + boolean isSetter = (refKind == REF_putField || refKind == REF_putStatic); + return new MemberName((Field) mem, isSetter); + } + throw new InternalError(mem.getClass().getName()); + } +} diff --git a/jdk/src/share/classes/java/lang/invoke/Invokers.java b/jdk/src/share/classes/java/lang/invoke/Invokers.java index 8d8e019d722..0ef6078b6b5 100644 --- a/jdk/src/share/classes/java/lang/invoke/Invokers.java +++ b/jdk/src/share/classes/java/lang/invoke/Invokers.java @@ -87,6 +87,7 @@ class Invokers { lform = invokeForm(mtype, true, MethodTypeForm.LF_EX_INVOKER); invoker = SimpleMethodHandle.make(invokerType, lform); } + invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke("invokeExact", mtype)); assert(checkInvoker(invoker)); exactInvoker = invoker; return invoker; @@ -110,6 +111,7 @@ class Invokers { lform = invokeForm(mtype, true, MethodTypeForm.LF_GEN_INVOKER); invoker = SimpleMethodHandle.make(invokerType, lform); } + invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke("invoke", mtype)); assert(checkInvoker(invoker)); generalInvoker = invoker; return invoker; diff --git a/jdk/src/share/classes/java/lang/invoke/MemberName.java b/jdk/src/share/classes/java/lang/invoke/MemberName.java index dfb468417d6..910574befe7 100644 --- a/jdk/src/share/classes/java/lang/invoke/MemberName.java +++ b/jdk/src/share/classes/java/lang/invoke/MemberName.java @@ -320,14 +320,18 @@ import java.util.Objects; /** Utility method to query if this member is a method handle invocation (invoke or invokeExact). */ public boolean isMethodHandleInvoke() { - final int bits = Modifier.NATIVE | Modifier.FINAL; + final int bits = MH_INVOKE_MODS; final int negs = Modifier.STATIC; if (testFlags(bits | negs, bits) && clazz == MethodHandle.class) { - return name.equals("invoke") || name.equals("invokeExact"); + return isMethodHandleInvokeName(name); } return false; } + public static boolean isMethodHandleInvokeName(String name) { + return name.equals("invoke") || name.equals("invokeExact"); + } + private static final int MH_INVOKE_MODS = Modifier.NATIVE | Modifier.FINAL | Modifier.PUBLIC; /** Utility method to query the modifier flags of this member. */ public boolean isStatic() { @@ -482,12 +486,27 @@ import java.util.Objects; m.getClass(); // NPE check // fill in vmtarget, vmindex while we have m in hand: MethodHandleNatives.init(this, m); + if (clazz == null) { // MHN.init failed + if (m.getDeclaringClass() == MethodHandle.class && + isMethodHandleInvokeName(m.getName())) { + // The JVM did not reify this signature-polymorphic instance. + // Need a special case here. + // See comments on MethodHandleNatives.linkMethod. + MethodType type = MethodType.methodType(m.getReturnType(), m.getParameterTypes()); + int flags = flagsMods(IS_METHOD, m.getModifiers(), REF_invokeVirtual); + init(MethodHandle.class, m.getName(), type, flags); + if (isMethodHandleInvoke()) + return; + } + throw new LinkageError(m.toString()); + } assert(isResolved() && this.clazz != null); this.name = m.getName(); if (this.type == null) this.type = new Object[] { m.getReturnType(), m.getParameterTypes() }; if (wantSpecial) { - assert(!isAbstract()) : this; + if (isAbstract()) + throw new AbstractMethodError(this.toString()); if (getReferenceKind() == REF_invokeVirtual) changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual); else if (getReferenceKind() == REF_invokeInterface) @@ -562,6 +581,22 @@ import java.util.Objects; initResolved(true); } + /** + * Create a name for a signature-polymorphic invoker. + * This is a placeholder for a signature-polymorphic instance + * (of MH.invokeExact, etc.) that the JVM does not reify. + * See comments on {@link MethodHandleNatives#linkMethod}. + */ + static MemberName makeMethodHandleInvoke(String name, MethodType type) { + return makeMethodHandleInvoke(name, type, MH_INVOKE_MODS | SYNTHETIC); + } + static MemberName makeMethodHandleInvoke(String name, MethodType type, int mods) { + MemberName mem = new MemberName(MethodHandle.class, name, type, REF_invokeVirtual); + mem.flags |= mods; // it's not resolved, but add these modifiers anyway + assert(mem.isMethodHandleInvoke()) : mem; + return mem; + } + // bare-bones constructor; the JVM will fill it in MemberName() { } diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java index df784d35e5b..408cbd08abc 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java @@ -1284,6 +1284,11 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString()); return null; // DMH returns DMH.member } + /*non-public*/ + MethodHandle withInternalMemberName(MemberName member) { + return MethodHandleImpl.makeWrappedMember(this, member); + } + /*non-public*/ boolean isInvokeSpecial() { return false; // DMH.Special returns true @@ -1356,7 +1361,7 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString()); MethodHandle rebind() { // Bind 'this' into a new invoker, of the known class BMH. MethodType type2 = type(); - LambdaForm form2 = reinvokerForm(type2.basicType()); + LambdaForm form2 = reinvokerForm(this); // form2 = lambda (bmh, arg*) { thismh = bmh[0]; invokeBasic(thismh, arg*) } return BoundMethodHandle.bindSingle(type2, form2, this); } @@ -1369,23 +1374,38 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString()); /** Create a LF which simply reinvokes a target of the given basic type. * The target MH must override {@link #reinvokerTarget} to provide the target. */ - static LambdaForm reinvokerForm(MethodType mtype) { - mtype = mtype.basicType(); + static LambdaForm reinvokerForm(MethodHandle target) { + MethodType mtype = target.type().basicType(); LambdaForm reinvoker = mtype.form().cachedLambdaForm(MethodTypeForm.LF_REINVOKE); if (reinvoker != null) return reinvoker; - MethodHandle MH_invokeBasic = MethodHandles.basicInvoker(mtype); + if (mtype.parameterSlotCount() >= MethodType.MAX_MH_ARITY) + return makeReinvokerForm(target.type(), target); // cannot cache this + reinvoker = makeReinvokerForm(mtype, null); + return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_REINVOKE, reinvoker); + } + private static LambdaForm makeReinvokerForm(MethodType mtype, MethodHandle customTargetOrNull) { + boolean customized = (customTargetOrNull != null); + MethodHandle MH_invokeBasic = customized ? null : MethodHandles.basicInvoker(mtype); final int THIS_BMH = 0; final int ARG_BASE = 1; final int ARG_LIMIT = ARG_BASE + mtype.parameterCount(); int nameCursor = ARG_LIMIT; - final int NEXT_MH = nameCursor++; + final int NEXT_MH = customized ? -1 : nameCursor++; final int REINVOKE = nameCursor++; LambdaForm.Name[] names = LambdaForm.arguments(nameCursor - ARG_LIMIT, mtype.invokerType()); - names[NEXT_MH] = new LambdaForm.Name(NF_reinvokerTarget, names[THIS_BMH]); - Object[] targetArgs = Arrays.copyOfRange(names, THIS_BMH, ARG_LIMIT, Object[].class); - targetArgs[0] = names[NEXT_MH]; // overwrite this MH with next MH - names[REINVOKE] = new LambdaForm.Name(MH_invokeBasic, targetArgs); - return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_REINVOKE, new LambdaForm("BMH.reinvoke", ARG_LIMIT, names)); + Object[] targetArgs; + MethodHandle targetMH; + if (customized) { + targetArgs = Arrays.copyOfRange(names, ARG_BASE, ARG_LIMIT, Object[].class); + targetMH = customTargetOrNull; + } else { + names[NEXT_MH] = new LambdaForm.Name(NF_reinvokerTarget, names[THIS_BMH]); + targetArgs = Arrays.copyOfRange(names, THIS_BMH, ARG_LIMIT, Object[].class); + targetArgs[0] = names[NEXT_MH]; // overwrite this MH with next MH + targetMH = MethodHandles.basicInvoker(mtype); + } + names[REINVOKE] = new LambdaForm.Name(targetMH, targetArgs); + return new LambdaForm("BMH.reinvoke", ARG_LIMIT, names); } private static final LambdaForm.NamedFunction NF_reinvokerTarget; diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java index 04eda964966..5ab7f7adb7b 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -317,7 +317,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; private MethodHandle cache; AsVarargsCollector(MethodHandle target, MethodType type, Class arrayType) { - super(type, reinvokerForm(type)); + super(type, reinvokerForm(target)); this.target = target; this.arrayType = arrayType; this.cache = target.asCollector(arrayType, 0); @@ -778,16 +778,27 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; } static Empty throwException(T t) throws T { throw t; } - static MethodHandle FAKE_METHOD_HANDLE_INVOKE; - static - MethodHandle fakeMethodHandleInvoke(MemberName method) { - MethodType type = method.getInvocationType(); - assert(type.equals(MethodType.methodType(Object.class, Object[].class))); - MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE; + static MethodHandle[] FAKE_METHOD_HANDLE_INVOKE = new MethodHandle[2]; + static MethodHandle fakeMethodHandleInvoke(MemberName method) { + int idx; + assert(method.isMethodHandleInvoke()); + switch (method.getName()) { + case "invoke": idx = 0; break; + case "invokeExact": idx = 1; break; + default: throw new InternalError(method.getName()); + } + MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE[idx]; if (mh != null) return mh; - mh = throwException(type.insertParameterTypes(0, UnsupportedOperationException.class)); + MethodType type = MethodType.methodType(Object.class, UnsupportedOperationException.class, + MethodHandle.class, Object[].class); + mh = throwException(type); mh = mh.bindTo(new UnsupportedOperationException("cannot reflectively invoke MethodHandle")); - FAKE_METHOD_HANDLE_INVOKE = mh; + if (!method.getInvocationType().equals(mh.type())) + throw new InternalError(method.toString()); + mh = mh.withInternalMemberName(method); + mh = mh.asVarargsCollector(Object[].class); + assert(method.isVarargs()); + FAKE_METHOD_HANDLE_INVOKE[idx] = mh; return mh; } @@ -821,7 +832,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; MethodHandle vamh = prepareForInvoker(mh); // Cache the result of makeInjectedInvoker once per argument class. MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass); - return restoreToType(bccInvoker.bindTo(vamh), mh.type()); + return restoreToType(bccInvoker.bindTo(vamh), mh.type(), mh.internalMemberName()); } private static MethodHandle makeInjectedInvoker(Class hostClass) { @@ -876,8 +887,11 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; } // Undo the adapter effect of prepareForInvoker: - private static MethodHandle restoreToType(MethodHandle vamh, MethodType type) { - return vamh.asCollector(Object[].class, type.parameterCount()).asType(type); + private static MethodHandle restoreToType(MethodHandle vamh, MethodType type, MemberName member) { + MethodHandle mh = vamh.asCollector(Object[].class, type.parameterCount()); + mh = mh.asType(type); + mh = mh.withInternalMemberName(member); + return mh; } private static final MethodHandle MH_checkCallerClass; @@ -939,4 +953,41 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; } } } + + + /** This subclass allows a wrapped method handle to be re-associated with an arbitrary member name. */ + static class WrappedMember extends MethodHandle { + private final MethodHandle target; + private final MemberName member; + + private WrappedMember(MethodHandle target, MethodType type, MemberName member) { + super(type, reinvokerForm(target)); + this.target = target; + this.member = member; + } + + @Override + MethodHandle reinvokerTarget() { + return target; + } + @Override + MemberName internalMemberName() { + return member; + } + @Override + boolean isInvokeSpecial() { + return target.isInvokeSpecial(); + } + @Override + MethodHandle viewAsType(MethodType newType) { + return new WrappedMember(target, newType, member); + } + } + + static MethodHandle makeWrappedMember(MethodHandle target, MemberName member) { + if (member.equals(target.internalMemberName())) + return target; + return new WrappedMember(target, target.type(), member); + } + } diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java index 380ca59b6e1..72fd8e91035 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java @@ -24,80 +24,246 @@ */ package java.lang.invoke; + +import java.lang.reflect.*; +import java.util.*; import java.lang.invoke.MethodHandleNatives.Constants; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandleStatics.*; /** - * Cracking (reflecting) method handles back into their constituent symbolic parts. + * A symbolic reference obtained by cracking a method handle into its consitutent symbolic parts. + * To crack a direct method handle, call {@link Lookup#revealDirect Lookup.revealDirect}. + *

+ * A direct method handle represents a method, constructor, or field without + * any intervening argument bindings or other transformations. + * The method, constructor, or field referred to by a direct method handle is called + * its underlying member. + * Direct method handles may be obtained in any of these ways: + *

+ * In all of these cases, it is possible to crack the resulting direct method handle + * to recover a symbolic reference for the underlying method, constructor, or field. + * Cracking must be done via a {@code Lookup} object equivalent to that which created + * the target method handle, or which has enough access permissions to recreate + * an equivalent method handle. * + *

Reference kinds

+ * The Lookup Factory Methods + * correspond to all major use cases for methods, constructors, and fields. + * These use cases may be distinguished using small integers as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
reference kinddescriptive namescopememberbehavior
{@code 1}{@code REF_getField}{@code class}{@code FT f;}{@code (T) this.f;}
{@code 2}{@code REF_getStatic}{@code class} or {@code interface}{@code static}
{@code FT f;}
{@code (T) C.f;}
{@code 3}{@code REF_putField}{@code class}{@code FT f;}{@code this.f = x;}
{@code 4}{@code REF_putStatic}{@code class}{@code static}
{@code FT f;}
{@code C.f = arg;}
{@code 5}{@code REF_invokeVirtual}{@code class}{@code T m(A*);}{@code (T) this.m(arg*);}
{@code 6}{@code REF_invokeStatic}{@code class} or {@code interface}{@code static}
{@code T m(A*);}
{@code (T) C.m(arg*);}
{@code 7}{@code REF_invokeSpecial}{@code class} or {@code interface}{@code T m(A*);}{@code (T) super.m(arg*);}
{@code 8}{@code REF_newInvokeSpecial}{@code class}{@code C(A*);}{@code new C(arg*);}
{@code 9}{@code REF_invokeInterface}{@code interface}{@code T m(A*);}{@code (T) this.m(arg*);}
+ * @since 1.8 */ -final class MethodHandleInfo { - public static final int - REF_getField = Constants.REF_getField, - REF_getStatic = Constants.REF_getStatic, - REF_putField = Constants.REF_putField, - REF_putStatic = Constants.REF_putStatic, - REF_invokeVirtual = Constants.REF_invokeVirtual, - REF_invokeStatic = Constants.REF_invokeStatic, - REF_invokeSpecial = Constants.REF_invokeSpecial, - REF_newInvokeSpecial = Constants.REF_newInvokeSpecial, - REF_invokeInterface = Constants.REF_invokeInterface; +public +interface MethodHandleInfo { + /** + * A direct method handle reference kind, + * as defined in the table above. + */ + public static final int + REF_getField = Constants.REF_getField, + REF_getStatic = Constants.REF_getStatic, + REF_putField = Constants.REF_putField, + REF_putStatic = Constants.REF_putStatic, + REF_invokeVirtual = Constants.REF_invokeVirtual, + REF_invokeStatic = Constants.REF_invokeStatic, + REF_invokeSpecial = Constants.REF_invokeSpecial, + REF_newInvokeSpecial = Constants.REF_newInvokeSpecial, + REF_invokeInterface = Constants.REF_invokeInterface; - private final Class declaringClass; - private final String name; - private final MethodType methodType; - private final int referenceKind; + /** + * Returns the reference kind of the cracked method handle, which in turn + * determines whether the method handle's underlying member was a constructor, method, or field. + * See the table above for definitions. + * @return the integer code for the kind of reference used to access the underlying member + */ + public int getReferenceKind(); - public MethodHandleInfo(MethodHandle mh) { - MemberName mn = mh.internalMemberName(); - if (mn == null) throw new IllegalArgumentException("not a direct method handle"); - this.declaringClass = mn.getDeclaringClass(); - this.name = mn.getName(); - this.methodType = mn.getMethodOrFieldType(); - byte refKind = mn.getReferenceKind(); - if (refKind == REF_invokeSpecial && !mh.isInvokeSpecial()) - // Devirtualized method invocation is usually formally virtual. - refKind = REF_invokeVirtual; - this.referenceKind = refKind; - } + /** + * Returns the class in which the cracked method handle's underlying member was defined. + * @return the declaring class of the underlying member + */ + public Class getDeclaringClass(); - public Class getDeclaringClass() { - return declaringClass; - } + /** + * Returns the name of the cracked method handle's underlying member. + * This is {@code "<init>"} if the underlying member was a constructor, + * else it is a simple method name or field name. + * @return the simple name of the underlying member + */ + public String getName(); - public String getName() { - return name; - } + /** + * Returns the nominal type of the cracked symbolic reference, expressed as a method type. + * If the reference is to a constructor, the return type will be {@code void}. + * If it is to a non-static method, the method type will not mention the {@code this} parameter. + * If it is to a field and the requested access is to read the field, + * the method type will have no parameters and return the field type. + * If it is to a field and the requested access is to write the field, + * the method type will have one parameter of the field type and return {@code void}. + *

+ * Note that original direct method handle may include a leading {@code this} parameter, + * or (in the case of a constructor) will replace the {@code void} return type + * with the constructed class. + * The nominal type does not include any {@code this} parameter, + * and (in the case of a constructor) will return {@code void}. + * @return the type of the underlying member, expressed as a method type + */ + public MethodType getMethodType(); - public MethodType getMethodType() { - return methodType; - } + // Utility methods. + // NOTE: class/name/type and reference kind constitute a symbolic reference + // member and modifiers are an add-on, derived from Core Reflection (or the equivalent) - public int getModifiers() { - return -1; //TODO - } + /** + * Reflects the underlying member as a method, constructor, or field object. + * If the underlying member is public, it is reflected as if by + * {@code getMethod}, {@code getConstructor}, or {@code getField}. + * Otherwise, it is reflected as if by + * {@code getDeclaredMethod}, {@code getDeclaredConstructor}, or {@code getDeclaredField}. + * The underlying member must be accessible to the given lookup object. + * @param the desired type of the result, either {@link Member} or a subtype + * @param expected a class object representing the desired result type {@code T} + * @param lookup the lookup object that created this MethodHandleInfo, or one with equivalent access privileges + * @return a reference to the method, constructor, or field object + * @exception ClassCastException if the member is not of the expected type + * @exception NullPointerException if either argument is {@code null} + * @exception IllegalArgumentException if the underlying member is not accessible to the given lookup object + */ + public T reflectAs(Class expected, Lookup lookup); - public int getReferenceKind() { - return referenceKind; - } + /** + * Returns the access modifiers of the underlying member. + * @return the Java language modifiers for underlying member, + * or -1 if the member cannot be accessed + * @see Modifier + * @see reflectAs + */ + public int getModifiers(); - static String getReferenceKindString(int referenceKind) { - switch (referenceKind) { - case REF_getField: return "getfield"; - case REF_getStatic: return "getstatic"; - case REF_putField: return "putfield"; - case REF_putStatic: return "putstatic"; - case REF_invokeVirtual: return "invokevirtual"; - case REF_invokeStatic: return "invokestatic"; - case REF_invokeSpecial: return "invokespecial"; - case REF_newInvokeSpecial: return "newinvokespecial"; - case REF_invokeInterface: return "invokeinterface"; - default: return "UNKNOWN_REFENCE_KIND" + "[" + referenceKind + "]"; - } + /** + * Determines if the underlying member was a variable arity method or constructor. + * Such members are represented by method handles that are varargs collectors. + * @implSpec + * This produces a result equivalent to: + *

{@code
+     *     getReferenceKind() >= REF_invokeVirtual && Modifier.isTransient(getModifiers())
+     * }
+ * + * + * @return {@code true} if and only if the underlying member was declared with variable arity. + */ + // spelling derived from java.lang.reflect.Executable, not MethodHandle.isVarargsCollector + public default boolean isVarArgs() { + // fields are never varargs: + if (MethodHandleNatives.refKindIsField((byte) getReferenceKind())) + return false; + // not in the public API: Modifier.VARARGS + final int ACC_VARARGS = 0x00000080; // from JVMS 4.6 (Table 4.20) + assert(ACC_VARARGS == Modifier.TRANSIENT); + return Modifier.isTransient(getModifiers()); } - @Override - public String toString() { - return String.format("%s %s.%s:%s", getReferenceKindString(referenceKind), - declaringClass.getName(), name, methodType); + /** + * Returns the descriptive name of the given reference kind, + * as defined in the table above. + * The conventional prefix "REF_" is omitted. + * @param referenceKind an integer code for a kind of reference used to access a class member + * @return a mixed-case string such as {@code "getField"} + * @exception IllegalArgumentException if the argument is not a valid + * reference kind number + */ + public static String referenceKindToString(int referenceKind) { + if (!MethodHandleNatives.refKindIsValid(referenceKind)) + throw newIllegalArgumentException("invalid reference kind", referenceKind); + return MethodHandleNatives.refKindName((byte)referenceKind); + } + + /** + * Returns a string representation for a {@code MethodHandleInfo}, + * given the four parts of its symbolic reference. + * This is defined to be of the form {@code "RK C.N:MT"}, where {@code RK} is the + * {@linkplain #referenceKindToString reference kind string} for {@code kind}, + * {@code C} is the {@linkplain java.lang.Class#getName name} of {@code defc} + * {@code N} is the {@code name}, and + * {@code MT} is the {@code type}. + * These four values may be obtained from the + * {@linkplain #getReferenceKind reference kind}, + * {@linkplain #getDeclaringClass declaring class}, + * {@linkplain #getName member name}, + * and {@linkplain #getMethodType method type} + * of a {@code MethodHandleInfo} object. + * + * @implSpec + * This produces a result equivalent to: + *
{@code
+     *     String.format("%s %s.%s:%s", referenceKindToString(kind), defc.getName(), name, type)
+     * }
+ * + * @param kind the {@linkplain #getReferenceKind reference kind} part of the symbolic reference + * @param defc the {@linkplain #getDeclaringClass declaring class} part of the symbolic reference + * @param name the {@linkplain #getName member name} part of the symbolic reference + * @param type the {@linkplain #getMethodType method type} part of the symbolic reference + * @return a string of the form {@code "RK C.N:MT"} + * @exception IllegalArgumentException if the first argument is not a valid + * reference kind number + * @exception NullPointerException if any reference argument is {@code null} + */ + public static String toString(int kind, Class defc, String name, MethodType type) { + Objects.requireNonNull(name); Objects.requireNonNull(type); + return String.format("%s %s.%s:%s", referenceKindToString(kind), defc.getName(), name, type); } } diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java index 06e61a7dd8b..4f83e82158c 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -205,6 +205,9 @@ class MethodHandleNatives { static boolean refKindIsMethod(byte refKind) { return !refKindIsField(refKind) && (refKind != REF_newInvokeSpecial); } + static boolean refKindIsConstructor(byte refKind) { + return (refKind == REF_newInvokeSpecial); + } static boolean refKindHasReceiver(byte refKind) { assert(refKindIsValid(refKind)); return (refKind & 1) != 0; @@ -313,7 +316,65 @@ class MethodHandleNatives { * The method assumes the following arguments on the stack: * 0: the method handle being invoked * 1-N: the arguments to the method handle invocation - * N+1: an implicitly added type argument (the given MethodType) + * N+1: an optional, implicitly added argument (typically the given MethodType) + *

+ * The nominal method at such a call site is an instance of + * a signature-polymorphic method (see @PolymorphicSignature). + * Such method instances are user-visible entities which are + * "split" from the generic placeholder method in {@code MethodHandle}. + * (Note that the placeholder method is not identical with any of + * its instances. If invoked reflectively, is guaranteed to throw an + * {@code UnsupportedOperationException}.) + * If the signature-polymorphic method instance is ever reified, + * it appears as a "copy" of the original placeholder + * (a native final member of {@code MethodHandle}) except + * that its type descriptor has shape required by the instance, + * and the method instance is not varargs. + * The method instance is also marked synthetic, since the + * method (by definition) does not appear in Java source code. + *

+ * The JVM is allowed to reify this method as instance metadata. + * For example, {@code invokeBasic} is always reified. + * But the JVM may instead call {@code linkMethod}. + * If the result is an * ordered pair of a {@code (method, appendix)}, + * the method gets all the arguments (0..N inclusive) + * plus the appendix (N+1), and uses the appendix to complete the call. + * In this way, one reusable method (called a "linker method") + * can perform the function of any number of polymorphic instance + * methods. + *

+ * Linker methods are allowed to be weakly typed, with any or + * all references rewritten to {@code Object} and any primitives + * (except {@code long}/{@code float}/{@code double}) + * rewritten to {@code int}. + * A linker method is trusted to return a strongly typed result, + * according to the specific method type descriptor of the + * signature-polymorphic instance it is emulating. + * This can involve (as necessary) a dynamic check using + * data extracted from the appendix argument. + *

+ * The JVM does not inspect the appendix, other than to pass + * it verbatim to the linker method at every call. + * This means that the JDK runtime has wide latitude + * for choosing the shape of each linker method and its + * corresponding appendix. + * Linker methods should be generated from {@code LambdaForm}s + * so that they do not become visible on stack traces. + *

+ * The {@code linkMethod} call is free to omit the appendix + * (returning null) and instead emulate the required function + * completely in the linker method. + * As a corner case, if N==255, no appendix is possible. + * In this case, the method returned must be custom-generated to + * to perform any needed type checking. + *

+ * If the JVM does not reify a method at a call site, but instead + * calls {@code linkMethod}, the corresponding call represented + * in the bytecodes may mention a valid method which is not + * representable with a {@code MemberName}. + * Therefore, use cases for {@code linkMethod} tend to correspond to + * special cases in reflective code such as {@code findVirtual} + * or {@code revealDirect}. */ static MemberName linkMethod(Class callerClass, int refKind, Class defc, String name, Object type, diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java index 78b01215636..f0f9447e001 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java @@ -26,8 +26,6 @@ package java.lang.invoke; import java.lang.reflect.*; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.List; import java.util.ArrayList; import java.util.Arrays; @@ -54,6 +52,7 @@ import sun.security.util.SecurityConstants; * *

* @author John Rose, JSR 292 EG + * @since 1.7 */ public class MethodHandles { @@ -96,6 +95,38 @@ public class MethodHandles { return Lookup.PUBLIC_LOOKUP; } + /** + * Performs an unchecked "crack" of a direct method handle. + * The result is as if the user had obtained a lookup object capable enough + * to crack the target method handle, called + * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect} + * on the target to obtain its symbolic reference, and then called + * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs} + * to resolve the symbolic reference to a member. + *

+ * If there is a security manager, its {@code checkPermission} method + * is called with a {@code ReflectPermission("suppressAccessChecks")} permission. + * @param the desired type of the result, either {@link Member} or a subtype + * @param target a direct method handle to crack into symbolic reference components + * @param expected a class object representing the desired result type {@code T} + * @return a reference to the method, constructor, or field object + * @exception SecurityException if the caller is not privileged to call {@code setAccessible} + * @exception NullPointerException if either argument is {@code null} + * @exception IllegalArgumentException if the target is not a direct method handle + * @exception ClassCastException if the member is not of the expected type + * @since 1.8 + */ + public static T + reflectAs(Class expected, MethodHandle target) { + SecurityManager smgr = System.getSecurityManager(); + if (smgr != null) smgr.checkPermission(ACCESS_PERMISSION); + Lookup lookup = Lookup.IMPL_LOOKUP; // use maximally privileged lookup + return lookup.revealDirect(target).reflectAs(expected, lookup); + } + // Copied from AccessibleObject, as used by Method.setAccessible, etc.: + static final private java.security.Permission ACCESS_PERMISSION = + new ReflectPermission("suppressAccessChecks"); + /** * A lookup object is a factory for creating method handles, * when the creation requires access checking. @@ -647,6 +678,7 @@ public class MethodHandles { return invoker(type); if ("invokeExact".equals(name)) return exactInvoker(type); + assert(!MemberName.isMethodHandleInvokeName(name)); return null; } @@ -892,6 +924,10 @@ return mh1; * @throws NullPointerException if the argument is null */ public MethodHandle unreflect(Method m) throws IllegalAccessException { + if (m.getDeclaringClass() == MethodHandle.class) { + MethodHandle mh = unreflectForMH(m); + if (mh != null) return mh; + } MemberName method = new MemberName(m); byte refKind = method.getReferenceKind(); if (refKind == REF_invokeSpecial) @@ -900,6 +936,12 @@ return mh1; Lookup lookup = m.isAccessible() ? IMPL_LOOKUP : this; return lookup.getDirectMethod(refKind, method.getDeclaringClass(), method, findBoundCallerClass(method)); } + private MethodHandle unreflectForMH(Method m) { + // these names require special lookups because they throw UnsupportedOperationException + if (MemberName.isMethodHandleInvokeName(m.getName())) + return MethodHandleImpl.fakeMethodHandleInvoke(new MemberName(m)); + return null; + } /** * Produces a method handle for a reflected method. @@ -1004,6 +1046,46 @@ return mh1; return unreflectField(f, true); } + /** + * Cracks a direct method handle created by this lookup object or a similar one. + * Security and access checks are performed to ensure that this lookup object + * is capable of reproducing the target method handle. + * This means that the cracking may fail if target is a direct method handle + * but was created by an unrelated lookup object. + * @param target a direct method handle to crack into symbolic reference components + * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails + * @exception NullPointerException if the target is {@code null} + * @since 1.8 + */ + public MethodHandleInfo revealDirect(MethodHandle target) { + MemberName member = target.internalMemberName(); + if (member == null || (!member.isResolved() && !member.isMethodHandleInvoke())) + throw newIllegalArgumentException("not a direct method handle"); + Class defc = member.getDeclaringClass(); + byte refKind = member.getReferenceKind(); + assert(MethodHandleNatives.refKindIsValid(refKind)); + if (refKind == REF_invokeSpecial && !target.isInvokeSpecial()) + // Devirtualized method invocation is usually formally virtual. + // To avoid creating extra MemberName objects for this common case, + // we encode this extra degree of freedom using MH.isInvokeSpecial. + refKind = REF_invokeVirtual; + if (refKind == REF_invokeVirtual && defc.isInterface()) + // Symbolic reference is through interface but resolves to Object method (toString, etc.) + refKind = REF_invokeInterface; + // Check SM permissions and member access before cracking. + try { + checkSecurityManager(defc, member); + checkAccess(refKind, defc, member); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException(ex); + } + // Produce the handle to the results. + return new InfoFromMemberName(this, member, refKind); + } + /// Helper methods, all package-private. MemberName resolveOrFail(byte refKind, Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { @@ -1201,12 +1283,12 @@ return mh1; private MethodHandle getDirectMethodCommon(byte refKind, Class refc, MemberName method, boolean doRestrict, Class callerClass) throws IllegalAccessException { checkMethod(refKind, refc, method); - if (method.isMethodHandleInvoke()) - return fakeMethodHandleInvoke(method); + assert(!method.isMethodHandleInvoke()); Class refcAsSuper; if (refKind == REF_invokeSpecial && refc != lookupClass() && + !refc.isInterface() && refc != (refcAsSuper = lookupClass().getSuperclass()) && refc.isAssignableFrom(lookupClass())) { assert(!method.getName().equals("")); // not this code path @@ -1234,9 +1316,6 @@ return mh1; mh = restrictReceiver(method, mh, lookupClass()); return mh; } - private MethodHandle fakeMethodHandleInvoke(MemberName method) { - return throwException(method.getReturnType(), UnsupportedOperationException.class); - } private MethodHandle maybeBindCaller(MemberName method, MethodHandle mh, Class callerClass) throws IllegalAccessException { diff --git a/jdk/src/share/classes/java/lang/invoke/SerializedLambda.java b/jdk/src/share/classes/java/lang/invoke/SerializedLambda.java index a775fc43e34..9be96ff685b 100644 --- a/jdk/src/share/classes/java/lang/invoke/SerializedLambda.java +++ b/jdk/src/share/classes/java/lang/invoke/SerializedLambda.java @@ -225,7 +225,7 @@ public final class SerializedLambda implements Serializable { @Override public String toString() { - String implKind=MethodHandleInfo.getReferenceKindString(implMethodKind); + String implKind=MethodHandleInfo.referenceKindToString(implMethodKind); return String.format("SerializedLambda[%s=%s, %s=%s.%s:%s, " + "%s=%s %s.%s:%s, %s=%s, %s=%d]", "capturingClass", capturingClass, diff --git a/jdk/test/java/lang/invoke/7087570/Test7087570.java b/jdk/test/java/lang/invoke/7087570/Test7087570.java index 572faccdf5a..732467b8c62 100644 --- a/jdk/test/java/lang/invoke/7087570/Test7087570.java +++ b/jdk/test/java/lang/invoke/7087570/Test7087570.java @@ -35,20 +35,9 @@ import java.util.*; import static java.lang.invoke.MethodHandles.*; import static java.lang.invoke.MethodType.*; +import static java.lang.invoke.MethodHandleInfo.*; public class Test7087570 { - // XXX may remove the following constant declarations when MethodHandleInfo is made public - private static final int - REF_getField = 1, - REF_getStatic = 2, - REF_putField = 3, - REF_putStatic = 4, - REF_invokeVirtual = 5, - REF_invokeStatic = 6, - REF_invokeSpecial = 7, - REF_newInvokeSpecial = 8, - REF_invokeInterface = 9, - REF_LIMIT = 10; private static final TestMethodData[] TESTS = new TestMethodData[] { // field accessors @@ -87,17 +76,17 @@ public class Test7087570 { } private static void doTest(MethodHandle mh, TestMethodData testMethod) { - Object mhi = newMethodHandleInfo(mh); + MethodHandleInfo mhi = LOOKUP.revealDirect(mh); System.out.printf("%s.%s: %s, nominal refKind: %s, actual refKind: %s\n", testMethod.clazz.getName(), testMethod.name, testMethod.methodType, - REF_KIND_NAMES[testMethod.referenceKind], - REF_KIND_NAMES[getReferenceKind(mhi)]); - assertEquals(testMethod.name, getName(mhi)); - assertEquals(testMethod.methodType, getMethodType(mhi)); - assertEquals(testMethod.declaringClass, getDeclaringClass(mhi)); + referenceKindToString(testMethod.referenceKind), + referenceKindToString(mhi.getReferenceKind())); + assertEquals(testMethod.name, mhi.getName()); + assertEquals(testMethod.methodType, mhi.getMethodType()); + assertEquals(testMethod.declaringClass, mhi.getDeclaringClass()); assertEquals(testMethod.referenceKind == REF_invokeSpecial, isInvokeSpecial(mh)); - assertRefKindEquals(testMethod.referenceKind, getReferenceKind(mhi)); + assertRefKindEquals(testMethod.referenceKind, mhi.getReferenceKind()); } private static void testWithLookup() throws Throwable { @@ -122,50 +111,8 @@ public class Test7087570 { return methodType(void.class, clazz); } - private static final String[] REF_KIND_NAMES = { - "MH::invokeBasic", - "REF_getField", "REF_getStatic", "REF_putField", "REF_putStatic", - "REF_invokeVirtual", "REF_invokeStatic", "REF_invokeSpecial", - "REF_newInvokeSpecial", "REF_invokeInterface" - }; - private static final Lookup LOOKUP = lookup(); - // XXX may remove the following reflective logic when MethodHandleInfo is made public - private static final MethodHandle MH_IS_INVOKESPECIAL; - private static final MethodHandle MHI_CONSTRUCTOR; - private static final MethodHandle MHI_GET_NAME; - private static final MethodHandle MHI_GET_METHOD_TYPE; - private static final MethodHandle MHI_GET_DECLARING_CLASS; - private static final MethodHandle MHI_GET_REFERENCE_KIND; - - static { - try { - // This is white box testing. Use reflection to grab private implementation bits. - String magicName = "IMPL_LOOKUP"; - Field magicLookup = MethodHandles.Lookup.class.getDeclaredField(magicName); - // This unit test will fail if a security manager is installed. - magicLookup.setAccessible(true); - // Forbidden fruit... - Lookup directInvokeLookup = (Lookup) magicLookup.get(null); - Class mhiClass = Class.forName("java.lang.invoke.MethodHandleInfo", false, MethodHandle.class.getClassLoader()); - MH_IS_INVOKESPECIAL = directInvokeLookup - .findVirtual(MethodHandle.class, "isInvokeSpecial", methodType(boolean.class)); - MHI_CONSTRUCTOR = directInvokeLookup - .findConstructor(mhiClass, methodType(void.class, MethodHandle.class)); - MHI_GET_NAME = directInvokeLookup - .findVirtual(mhiClass, "getName", methodType(String.class)); - MHI_GET_METHOD_TYPE = directInvokeLookup - .findVirtual(mhiClass, "getMethodType", methodType(MethodType.class)); - MHI_GET_DECLARING_CLASS = directInvokeLookup - .findVirtual(mhiClass, "getDeclaringClass", methodType(Class.class)); - MHI_GET_REFERENCE_KIND = directInvokeLookup - .findVirtual(mhiClass, "getReferenceKind", methodType(int.class)); - } catch (ReflectiveOperationException ex) { - throw new Error(ex); - } - } - private static class TestMethodData { final Class clazz; final String name; @@ -208,7 +155,9 @@ public class Test7087570 { return LOOKUP.findStatic(testMethod.clazz, testMethod.name, testMethod.methodType); case REF_invokeSpecial: Class thisClass = LOOKUP.lookupClass(); - return LOOKUP.findSpecial(testMethod.clazz, testMethod.name, testMethod.methodType, thisClass); + MethodHandle smh = LOOKUP.findSpecial(testMethod.clazz, testMethod.name, testMethod.methodType, thisClass); + noteInvokeSpecial(smh); + return smh; case REF_newInvokeSpecial: return LOOKUP.findConstructor(testMethod.clazz, testMethod.methodType); default: @@ -238,7 +187,9 @@ public class Test7087570 { case REF_invokeSpecial: { Method m = testMethod.clazz.getDeclaredMethod(testMethod.name, testMethod.methodType.parameterArray()); Class thisClass = LOOKUP.lookupClass(); - return LOOKUP.unreflectSpecial(m, thisClass); + MethodHandle smh = LOOKUP.unreflectSpecial(m, thisClass); + noteInvokeSpecial(smh); + return smh; } case REF_newInvokeSpecial: { Constructor c = testMethod.clazz.getDeclaredConstructor(testMethod.methodType.parameterArray()); @@ -249,59 +200,20 @@ public class Test7087570 { } } - private static Object newMethodHandleInfo(MethodHandle mh) { - try { - return MHI_CONSTRUCTOR.invoke(mh); - } catch (Throwable ex) { - throw new Error(ex); - } + private static List specialMethodHandles = new ArrayList<>(); + private static void noteInvokeSpecial(MethodHandle mh) { + specialMethodHandles.add(mh); + assert(isInvokeSpecial(mh)); } - private static boolean isInvokeSpecial(MethodHandle mh) { - try { - return (boolean) MH_IS_INVOKESPECIAL.invokeExact(mh); - } catch (Throwable ex) { - throw new Error(ex); - } - } - - private static String getName(Object mhi) { - try { - return (String) MHI_GET_NAME.invoke(mhi); - } catch (Throwable ex) { - throw new Error(ex); - } - } - - private static MethodType getMethodType(Object mhi) { - try { - return (MethodType) MHI_GET_METHOD_TYPE.invoke(mhi); - } catch (Throwable ex) { - throw new Error(ex); - } - } - - private static Class getDeclaringClass(Object mhi) { - try { - return (Class) MHI_GET_DECLARING_CLASS.invoke(mhi); - } catch (Throwable ex) { - throw new Error(ex); - } - } - - private static int getReferenceKind(Object mhi) { - try { - return (int) MHI_GET_REFERENCE_KIND.invoke(mhi); - } catch (Throwable ex) { - throw new Error(ex); - } + return specialMethodHandles.contains(mh); } private static void assertRefKindEquals(int expect, int observed) { if (expect == observed) return; - String msg = "expected " + REF_KIND_NAMES[(int) expect] + - " but observed " + REF_KIND_NAMES[(int) observed]; + String msg = "expected " + referenceKindToString(expect) + + " but observed " + referenceKindToString(observed); System.out.println("FAILED: " + msg); throw new AssertionError(msg); } diff --git a/jdk/test/java/lang/invoke/RevealDirectTest.java b/jdk/test/java/lang/invoke/RevealDirectTest.java new file mode 100644 index 00000000000..f05b19077c1 --- /dev/null +++ b/jdk/test/java/lang/invoke/RevealDirectTest.java @@ -0,0 +1,753 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary verify Lookup.revealDirect on a variety of input handles + * @compile -XDignore.symbol.file RevealDirectTest.java + * @run junit/othervm -ea -esa test.java.lang.invoke.RevealDirectTest + * + * @test + * @summary verify Lookup.revealDirect on a variety of input handles, with security manager + * @run main/othervm/policy=jtreg.security.policy/secure=java.lang.SecurityManager -ea -esa test.java.lang.invoke.RevealDirectTest + */ + +/* To run manually: + * $ $JAVA8X_HOME/bin/javac -cp $JUNIT4_JAR -d ../../../.. -XDignore.symbol.file RevealDirectTest.java + * $ $JAVA8X_HOME/bin/java -cp $JUNIT4_JAR:../../../.. -ea -esa org.junit.runner.JUnitCore test.java.lang.invoke.RevealDirectTest + * $ $JAVA8X_HOME/bin/java -cp $JUNIT4_JAR:../../../.. -ea -esa -Djava.security.manager test.java.lang.invoke.RevealDirectTest + */ + +package test.java.lang.invoke; + +import java.lang.reflect.*; +import java.lang.invoke.*; +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +import static java.lang.invoke.MethodHandleInfo.*; +import java.util.*; +import static org.junit.Assert.*; +import org.junit.*; + +public class RevealDirectTest { + public static void main(String... av) throws Throwable { + // Run the @Test methods explicitly, in case we don't want to use the JUnitCore driver. + // This appears to be necessary when running with a security manager. + Throwable fail = null; + for (Method test : RevealDirectTest.class.getDeclaredMethods()) { + if (!test.isAnnotationPresent(Test.class)) continue; + try { + test.invoke(new RevealDirectTest()); + } catch (Throwable ex) { + if (ex instanceof InvocationTargetException) + ex = ex.getCause(); + if (fail == null) fail = ex; + System.out.println("Testcase: "+test.getName() + +"("+test.getDeclaringClass().getName() + +"):\tCaused an ERROR"); + System.out.println(ex); + ex.printStackTrace(System.out); + } + } + if (fail != null) throw fail; + } + + public interface SimpleSuperInterface { + public abstract int getInt(); + public static void printAll(String... args) { + System.out.println(Arrays.toString(args)); + } + public int NICE_CONSTANT = 42; + } + public interface SimpleInterface extends SimpleSuperInterface { + default float getFloat() { return getInt(); } + public static void printAll(String[] args) { + System.out.println(Arrays.toString(args)); + } + } + public static class Simple implements SimpleInterface, Cloneable { + public int intField; + public final int finalField; + private static String stringField; + public int getInt() { return NICE_CONSTANT; } + private static Number getNum() { return 804; } + public Simple clone() { + try { + return (Simple) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(ex); + } + } + Simple() { finalField = -NICE_CONSTANT; } + private static Lookup localLookup() { return lookup(); } + private static List members() { return getMembers(lookup().lookupClass()); }; + } + + static boolean VERBOSE = false; + + @Test public void testSimple() throws Throwable { + if (VERBOSE) System.out.println("@Test testSimple"); + testOnMembers("testSimple", Simple.members(), Simple.localLookup()); + } + @Test public void testPublicLookup() throws Throwable { + if (VERBOSE) System.out.println("@Test testPublicLookup"); + List mems = publicOnly(Simple.members()); + Lookup pubLookup = publicLookup(), privLookup = Simple.localLookup(); + testOnMembers("testPublicLookup/1", mems, pubLookup); + // reveal using publicLookup: + testOnMembers("testPublicLookup/2", mems, privLookup, pubLookup); + // lookup using publicLookup, but reveal using private: + testOnMembers("testPublicLookup/3", mems, pubLookup, privLookup); + } + @Test public void testPublicLookupNegative() throws Throwable { + if (VERBOSE) System.out.println("@Test testPublicLookupNegative"); + List mems = nonPublicOnly(Simple.members()); + Lookup pubLookup = publicLookup(), privLookup = Simple.localLookup(); + testOnMembersNoLookup("testPublicLookupNegative/1", mems, pubLookup); + testOnMembersNoReveal("testPublicLookupNegative/2", mems, privLookup, pubLookup); + testOnMembersNoReflect("testPublicLookupNegative/3", mems, privLookup, pubLookup); + } + @Test public void testJavaLangClass() throws Throwable { + if (VERBOSE) System.out.println("@Test testJavaLangClass"); + List mems = callerSensitive(false, publicOnly(getMembers(Class.class))); + mems = limit(20, mems); + testOnMembers("testJavaLangClass", mems, Simple.localLookup()); + } + @Test public void testCallerSensitive() throws Throwable { + if (VERBOSE) System.out.println("@Test testCallerSensitive"); + List mems = union(getMembers(MethodHandles.class, "lookup"), + getMembers(Method.class, "invoke"), + getMembers(Field.class, "get", "set", "getLong"), + getMembers(Class.class)); + mems = callerSensitive(true, publicOnly(mems)); + mems = limit(10, mems); + testOnMembers("testCallerSensitive", mems, Simple.localLookup()); + } + @Test public void testCallerSensitiveNegative() throws Throwable { + if (VERBOSE) System.out.println("@Test testCallerSensitiveNegative"); + List mems = union(getMembers(MethodHandles.class, "lookup"), + getMembers(Class.class, "forName"), + getMembers(Method.class, "invoke")); + mems = callerSensitive(true, publicOnly(mems)); + // CS methods cannot be looked up with publicLookup + testOnMembersNoLookup("testCallerSensitiveNegative", mems, publicLookup()); + } + @Test public void testMethodHandleNatives() throws Throwable { + if (VERBOSE) System.out.println("@Test testMethodHandleNatives"); + List mems = getMembers(MethodHandle.class, "invoke", "invokeExact"); + testOnMembers("testMethodHandleNatives", mems, Simple.localLookup()); + } + @Test public void testMethodHandleInvokes() throws Throwable { + if (VERBOSE) System.out.println("@Test testMethodHandleInvokes"); + List types = new ArrayList<>(); + Class[] someParamTypes = { void.class, int.class, Object.class, Object[].class }; + for (Class rt : someParamTypes) { + for (Class p0 : someParamTypes) { + if (p0 == void.class) { types.add(methodType(rt)); continue; } + for (Class p1 : someParamTypes) { + if (p1 == void.class) { types.add(methodType(rt, p0)); continue; } + for (Class p2 : someParamTypes) { + if (p2 == void.class) { types.add(methodType(rt, p0, p1)); continue; } + types.add(methodType(rt, p0, p1, p2)); + } + } + } + } + List mems = union(getPolyMembers(MethodHandle.class, "invoke", types), + getPolyMembers(MethodHandle.class, "invokeExact", types)); + testOnMembers("testMethodHandleInvokes/1", mems, Simple.localLookup()); + testOnMembers("testMethodHandleInvokes/2", mems, publicLookup()); + } + + static List getPolyMembers(Class cls, String name, List types) { + assert(cls == MethodHandle.class); + ArrayList mems = new ArrayList<>(); + for (MethodType type : types) { + mems.add(new SignaturePolymorphicMethod(name, type)); + } + return mems; + } + static List getMembers(Class cls) { + return getMembers(cls, (String[]) null); + } + static List getMembers(Class cls, String... onlyNames) { + List names = (onlyNames == null || onlyNames.length == 0 ? null : Arrays.asList(onlyNames)); + ArrayList res = new ArrayList<>(); + for (Class sup : getSupers(cls)) { + res.addAll(getDeclaredMembers(sup, "getDeclaredFields")); + res.addAll(getDeclaredMembers(sup, "getDeclaredMethods")); + res.addAll(getDeclaredMembers(sup, "getDeclaredConstructors")); + } + res = new ArrayList<>(new LinkedHashSet<>(res)); + for (int i = 0; i < res.size(); i++) { + Member mem = res.get(i); + if (!canBeReached(mem, cls) || + res.indexOf(mem) != i || + mem.isSynthetic() || + (names != null && !names.contains(mem.getName())) + ) { + res.remove(i--); + } + } + return res; + } + static List> getSupers(Class cls) { + ArrayList> res = new ArrayList<>(); + ArrayList> intfs = new ArrayList<>(); + for (Class sup = cls; sup != null; sup = sup.getSuperclass()) { + res.add(sup); + for (Class intf : cls.getInterfaces()) { + if (!intfs.contains(intf)) + intfs.add(intf); + } + } + for (int i = 0; i < intfs.size(); i++) { + for (Class intf : intfs.get(i).getInterfaces()) { + if (!intfs.contains(intf)) + intfs.add(intf); + } + } + res.addAll(intfs); + //System.out.println("getSupers => "+res); + return res; + } + static boolean hasSM() { + return (System.getSecurityManager() != null); + } + static List getDeclaredMembers(Class cls, String accessor) { + Member[] mems = {}; + Method getter = getMethod(Class.class, accessor); + if (hasSM()) { + try { + mems = (Member[]) invokeMethod(getter, cls); + } catch (SecurityException ex) { + //if (VERBOSE) ex.printStackTrace(); + accessor = accessor.replace("Declared", ""); + getter = getMethod(Class.class, accessor); + if (VERBOSE) System.out.println("replaced accessor: "+getter); + } + } + if (mems.length == 0) { + try { + mems = (Member[]) invokeMethod(getter, cls); + } catch (SecurityException ex) { + ex.printStackTrace(); + } + } + if (VERBOSE) System.out.println(accessor+" "+cls.getName()+" => "+mems.length+" members"); + return Arrays.asList(mems); + } + static Method getMethod(Class cls, String name) { + try { + return cls.getMethod(name); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } + } + static Object invokeMethod(Method m, Object recv, Object... args) { + try { + return m.invoke(recv, args); + } catch (InvocationTargetException ex) { + Throwable ex2 = ex.getCause(); + if (ex2 instanceof RuntimeException) throw (RuntimeException) ex2; + if (ex2 instanceof Error) throw (Error) ex2; + throw new AssertionError(ex); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } + } + + static List limit(int len, List mems) { + if (mems.size() <= len) return mems; + return mems.subList(0, len); + } + @SafeVarargs + static List union(List mems, List... mem2s) { + for (List mem2 : mem2s) { + for (Member m : mem2) { + if (!mems.contains(m)) + mems.add(m); + } + } + return mems; + } + static List callerSensitive(boolean cond, List members) { + for (Iterator i = members.iterator(); i.hasNext(); ) { + Member mem = i.next(); + if (isCallerSensitive(mem) != cond) + i.remove(); + } + if (members.isEmpty()) throw new AssertionError("trivial result"); + return members; + } + static boolean isCallerSensitive(Member mem) { + if (!(mem instanceof AnnotatedElement)) return false; + AnnotatedElement ae = (AnnotatedElement) mem; + if (CS_CLASS != null) + return ae.isAnnotationPresent(sun.reflect.CallerSensitive.class); + for (java.lang.annotation.Annotation a : ae.getDeclaredAnnotations()) { + if (a.toString().contains(".CallerSensitive")) + return true; + } + return false; + } + static final Class CS_CLASS; + static { + Class c = null; + try { + c = sun.reflect.CallerSensitive.class; + } catch (SecurityException | LinkageError ex) { + } + CS_CLASS = c; + } + static List publicOnly(List members) { + return removeMods(members, Modifier.PUBLIC, 0); + } + static List nonPublicOnly(List members) { + return removeMods(members, Modifier.PUBLIC, -1); + } + static List removeMods(List members, int mask, int bits) { + int publicMods = (mask & Modifier.PUBLIC); + members = new ArrayList<>(members); + for (Iterator i = members.iterator(); i.hasNext(); ) { + Member mem = i.next(); + int mods = mem.getModifiers(); + if ((publicMods & mods) != 0 && + (publicMods & mem.getDeclaringClass().getModifiers()) == 0) + mods -= publicMods; + if ((mods & mask) == (bits & mask)) + i.remove(); + } + return members; + } + + void testOnMembers(String tname, List mems, Lookup lookup, Lookup... lookups) throws Throwable { + if (VERBOSE) System.out.println("testOnMembers "+mems); + Lookup revLookup = (lookups.length > 0) ? lookups[0] : null; + if (revLookup == null) revLookup = lookup; + Lookup refLookup = (lookups.length > 1) ? lookups[1] : null; + if (refLookup == null) refLookup = lookup; + assert(lookups.length <= 2); + testOnMembersImpl(tname, mems, lookup, revLookup, refLookup, NO_FAIL); + } + void testOnMembersNoLookup(String tname, List mems, Lookup lookup) throws Throwable { + if (VERBOSE) System.out.println("testOnMembersNoLookup "+mems); + testOnMembersImpl(tname, mems, lookup, null, null, FAIL_LOOKUP); + } + void testOnMembersNoReveal(String tname, List mems, + Lookup lookup, Lookup negLookup) throws Throwable { + if (VERBOSE) System.out.println("testOnMembersNoReveal "+mems); + testOnMembersImpl(tname, mems, lookup, negLookup, null, FAIL_REVEAL); + } + void testOnMembersNoReflect(String tname, List mems, + Lookup lookup, Lookup negLookup) throws Throwable { + if (VERBOSE) System.out.println("testOnMembersNoReflect "+mems); + testOnMembersImpl(tname, mems, lookup, lookup, negLookup, FAIL_REFLECT); + } + void testOnMembersImpl(String tname, List mems, + Lookup lookup, + Lookup revLookup, + Lookup refLookup, + int failureMode) throws Throwable { + Throwable fail = null; + int failCount = 0; + failureModeCounts = new int[FAIL_MODE_COUNT]; + long tm0 = System.currentTimeMillis(); + for (Member mem : mems) { + try { + testWithMember(mem, lookup, revLookup, refLookup, failureMode); + } catch (Throwable ex) { + if (fail == null) fail = ex; + if (++failCount > 10) { System.out.println("*** FAIL: too many failures"); break; } + System.out.println("*** FAIL: "+mem+" => "+ex); + if (VERBOSE) ex.printStackTrace(System.out); + } + } + long tm1 = System.currentTimeMillis(); + System.out.printf("@Test %s executed %s tests in %d ms", + tname, testKinds(failureModeCounts), (tm1-tm0)).println(); + if (fail != null) throw fail; + } + static String testKinds(int[] modes) { + int pos = modes[0], neg = -pos; + for (int n : modes) neg += n; + if (neg == 0) return pos + " positive"; + String negs = ""; + for (int n : modes) negs += "/"+n; + negs = negs.replaceFirst("/"+pos+"/", ""); + negs += " negative"; + if (pos == 0) return negs; + return pos + " positive, " + negs; + } + static class SignaturePolymorphicMethod implements Member { // non-reflected instance of MH.invoke* + final String name; + final MethodType type; + SignaturePolymorphicMethod(String name, MethodType type) { + this.name = name; + this.type = type; + } + public String toString() { + String typeStr = type.toString(); + if (isVarArgs()) typeStr = typeStr.replaceFirst("\\[\\])$", "...)"); + return (Modifier.toString(getModifiers()) + +typeStr.substring(0, typeStr.indexOf('('))+" " + +getDeclaringClass().getTypeName()+"." + +getName()+typeStr.substring(typeStr.indexOf('('))); + } + public boolean equals(Object x) { + return (x instanceof SignaturePolymorphicMethod && equals((SignaturePolymorphicMethod)x)); + } + public boolean equals(SignaturePolymorphicMethod that) { + return this.name.equals(that.name) && this.type.equals(that.type); + } + public int hashCode() { + return name.hashCode() * 31 + type.hashCode(); + } + public Class getDeclaringClass() { return MethodHandle.class; } + public String getName() { return name; } + public MethodType getMethodType() { return type; } + public int getModifiers() { return Modifier.PUBLIC | Modifier.FINAL | Modifier.NATIVE | SYNTHETIC; } + public boolean isVarArgs() { return Modifier.isTransient(getModifiers()); } + public boolean isSynthetic() { return true; } + public Class getReturnType() { return type.returnType(); } + public Class[] getParameterTypes() { return type.parameterArray(); } + static final int SYNTHETIC = 0x00001000; + } + static class UnreflectResult { // a tuple + final MethodHandle mh; + final Throwable ex; + final byte kind; + final Member mem; + final int var; + UnreflectResult(MethodHandle mh, byte kind, Member mem, int var) { + this.mh = mh; + this.ex = null; + this.kind = kind; + this.mem = mem; + this.var = var; + } + UnreflectResult(Throwable ex, byte kind, Member mem, int var) { + this.mh = null; + this.ex = ex; + this.kind = kind; + this.mem = mem; + this.var = var; + } + public String toString() { + return toInfoString()+"/v"+var; + } + public String toInfoString() { + return String.format("%s %s.%s:%s", MethodHandleInfo.referenceKindToString(kind), + mem.getDeclaringClass().getName(), name(mem), type(mem, kind)); + } + static String name(Member mem) { + if (mem instanceof Constructor) return ""; + return mem.getName(); + } + static MethodType type(Member mem, byte kind) { + if (mem instanceof Field) { + Class type = ((Field)mem).getType(); + if (kind == REF_putStatic || kind == REF_putField) + return methodType(void.class, type); + return methodType(type); + } else if (mem instanceof SignaturePolymorphicMethod) { + return ((SignaturePolymorphicMethod)mem).getMethodType(); + } + Class[] params = ((Executable)mem).getParameterTypes(); + if (mem instanceof Constructor) + return methodType(void.class, params); + Class type = ((Method)mem).getReturnType(); + return methodType(type, params); + } + } + static UnreflectResult unreflectMember(Lookup lookup, Member mem, int variation) { + byte[] refKind = {0}; + try { + return unreflectMemberOrThrow(lookup, mem, variation, refKind); + } catch (ReflectiveOperationException|SecurityException ex) { + return new UnreflectResult(ex, refKind[0], mem, variation); + } + } + static UnreflectResult unreflectMemberOrThrow(Lookup lookup, Member mem, int variation, + byte[] refKind) throws ReflectiveOperationException { + Class cls = lookup.lookupClass(); + Class defc = mem.getDeclaringClass(); + String name = mem.getName(); + int mods = mem.getModifiers(); + boolean isStatic = Modifier.isStatic(mods); + MethodHandle mh = null; + byte kind = 0; + if (mem instanceof Method) { + Method m = (Method) mem; + MethodType type = methodType(m.getReturnType(), m.getParameterTypes()); + boolean canBeSpecial = (!isStatic && + (lookup.lookupModes() & Modifier.PRIVATE) != 0 && + defc.isAssignableFrom(cls) && + (!defc.isInterface() || Arrays.asList(cls.getInterfaces()).contains(defc))); + if (variation >= 2) + kind = REF_invokeSpecial; + else if (isStatic) + kind = REF_invokeStatic; + else if (defc.isInterface()) + kind = REF_invokeInterface; + else + kind = REF_invokeVirtual; + refKind[0] = kind; + switch (variation) { + case 0: + mh = lookup.unreflect(m); + break; + case 1: + if (defc == MethodHandle.class && + !isStatic && + m.isVarArgs() && + Modifier.isFinal(mods) && + Modifier.isNative(mods)) { + break; + } + if (isStatic) + mh = lookup.findStatic(defc, name, type); + else + mh = lookup.findVirtual(defc, name, type); + break; + case 2: + if (!canBeSpecial) + break; + mh = lookup.unreflectSpecial(m, lookup.lookupClass()); + break; + case 3: + if (!canBeSpecial) + break; + mh = lookup.findSpecial(defc, name, type, lookup.lookupClass()); + break; + } + } else if (mem instanceof SignaturePolymorphicMethod) { + SignaturePolymorphicMethod m = (SignaturePolymorphicMethod) mem; + MethodType type = methodType(m.getReturnType(), m.getParameterTypes()); + kind = REF_invokeVirtual; + refKind[0] = kind; + switch (variation) { + case 0: + mh = lookup.findVirtual(defc, name, type); + break; + } + } else if (mem instanceof Constructor) { + name = ""; // not used + Constructor m = (Constructor) mem; + MethodType type = methodType(void.class, m.getParameterTypes()); + kind = REF_newInvokeSpecial; + refKind[0] = kind; + switch (variation) { + case 0: + mh = lookup.unreflectConstructor(m); + break; + case 1: + mh = lookup.findConstructor(defc, type); + break; + } + } else if (mem instanceof Field) { + Field m = (Field) mem; + Class type = m.getType(); + boolean canHaveSetter = !Modifier.isFinal(mods); + if (variation >= 2) + kind = (byte)(isStatic ? REF_putStatic : REF_putField); + else + kind = (byte)(isStatic ? REF_getStatic : REF_getField); + refKind[0] = kind; + switch (variation) { + case 0: + mh = lookup.unreflectGetter(m); + break; + case 1: + if (isStatic) + mh = lookup.findStaticGetter(defc, name, type); + else + mh = lookup.findGetter(defc, name, type); + break; + case 3: + if (!canHaveSetter) + break; + mh = lookup.unreflectSetter(m); + break; + case 2: + if (!canHaveSetter) + break; + if (isStatic) + mh = lookup.findStaticSetter(defc, name, type); + else + mh = lookup.findSetter(defc, name, type); + break; + } + } else { + throw new IllegalArgumentException(String.valueOf(mem)); + } + if (mh == null) + // ran out of valid variations; return null to caller + return null; + return new UnreflectResult(mh, kind, mem, variation); + } + static boolean canBeReached(Member mem, Class cls) { + Class defc = mem.getDeclaringClass(); + String name = mem.getName(); + int mods = mem.getModifiers(); + if (mem instanceof Constructor) { + name = ""; // according to 292 spec. + } + if (defc == cls) + return true; + if (name.startsWith("<")) + return false; // only my own constructors + if (Modifier.isPrivate(mods)) + return false; // only my own constructors + if (defc.getPackage() == cls.getPackage()) + return true; // package access or greater OK + if (Modifier.isPublic(mods)) + return true; // publics always OK + if (Modifier.isProtected(mods) && defc.isAssignableFrom(cls)) + return true; // protected OK + return false; + } + static boolean consistent(UnreflectResult res, MethodHandleInfo info) { + assert(res.mh != null); + assertEquals(res.kind, info.getReferenceKind()); + assertEquals(res.mem.getModifiers(), info.getModifiers()); + assertEquals(res.mem.getDeclaringClass(), info.getDeclaringClass()); + String expectName = res.mem.getName(); + if (res.kind == REF_newInvokeSpecial) + expectName = ""; + assertEquals(expectName, info.getName()); + MethodType expectType = res.mh.type(); + if ((res.kind & 1) == (REF_getField & 1)) + expectType = expectType.dropParameterTypes(0, 1); + if (res.kind == REF_newInvokeSpecial) + expectType = expectType.changeReturnType(void.class); + assertEquals(expectType, info.getMethodType()); + assertEquals(res.mh.isVarargsCollector(), isVarArgs(info)); + assertEquals(res.toInfoString(), info.toString()); + assertEquals(res.toInfoString(), MethodHandleInfo.toString(info.getReferenceKind(), info.getDeclaringClass(), info.getName(), info.getMethodType())); + return true; + } + static boolean isVarArgs(MethodHandleInfo info) { + return info.isVarArgs(); + } + static boolean consistent(Member mem, Member mem2) { + assertEquals(mem, mem2); + return true; + } + static boolean consistent(MethodHandleInfo info, MethodHandleInfo info2) { + assertEquals(info.getReferenceKind(), info2.getReferenceKind()); + assertEquals(info.getModifiers(), info2.getModifiers()); + assertEquals(info.getDeclaringClass(), info2.getDeclaringClass()); + assertEquals(info.getName(), info2.getName()); + assertEquals(info.getMethodType(), info2.getMethodType()); + assertEquals(isVarArgs(info), isVarArgs(info)); + return true; + } + static boolean consistent(MethodHandle mh, MethodHandle mh2) { + assertEquals(mh.type(), mh2.type()); + assertEquals(mh.isVarargsCollector(), mh2.isVarargsCollector()); + return true; + } + int[] failureModeCounts; + static final int NO_FAIL=0, FAIL_LOOKUP=1, FAIL_REVEAL=2, FAIL_REFLECT=3, FAIL_MODE_COUNT=4; + void testWithMember(Member mem, + Lookup lookup, // initial lookup of member => MH + Lookup revLookup, // reveal MH => info + Lookup refLookup, // reflect info => member + int failureMode) throws Throwable { + boolean expectEx1 = (failureMode == FAIL_LOOKUP); // testOnMembersNoLookup + boolean expectEx2 = (failureMode == FAIL_REVEAL); // testOnMembersNoReveal + boolean expectEx3 = (failureMode == FAIL_REFLECT); // testOnMembersNoReflect + for (int variation = 0; ; variation++) { + UnreflectResult res = unreflectMember(lookup, mem, variation); + failureModeCounts[failureMode] += 1; + if (variation == 0) assert(res != null); + if (res == null) break; + if (VERBOSE && variation == 0) + System.out.println("from "+mem.getDeclaringClass().getSimpleName()); + MethodHandle mh = res.mh; + Throwable ex1 = res.ex; + if (VERBOSE) System.out.println(" "+variation+": "+res+" << "+(mh != null ? mh : ex1)); + if (expectEx1 && ex1 != null) + continue; // this is OK; we expected that lookup to fail + if (expectEx1) + throw new AssertionError("unexpected lookup for negative test"); + if (ex1 != null && !expectEx1) { + if (failureMode != NO_FAIL) + throw new AssertionError("unexpected lookup failure for negative test", ex1); + throw ex1; + } + MethodHandleInfo info; + try { + info = revLookup.revealDirect(mh); + if (expectEx2) throw new AssertionError("unexpected revelation for negative test"); + } catch (Throwable ex2) { + if (VERBOSE) System.out.println(" "+variation+": "+res+" => "+mh.getClass().getName()+" => (EX2)"+ex2); + if (expectEx2) + continue; // this is OK; we expected the reflect to fail + if (failureMode != NO_FAIL) + throw new AssertionError("unexpected revelation failure for negative test", ex2); + throw ex2; + } + assert(consistent(res, info)); + Member mem2; + try { + mem2 = info.reflectAs(Member.class, refLookup); + if (expectEx3) throw new AssertionError("unexpected reflection for negative test"); + assert(!(mem instanceof SignaturePolymorphicMethod)); + } catch (IllegalArgumentException ex3) { + if (VERBOSE) System.out.println(" "+variation+": "+info+" => (EX3)"+ex3); + if (expectEx3) + continue; // this is OK; we expected the reflect to fail + if (mem instanceof SignaturePolymorphicMethod) + continue; // this is OK; we cannot reflect MH.invokeExact(a,b,c) + if (failureMode != NO_FAIL) + throw new AssertionError("unexpected reflection failure for negative test", ex3); + throw ex3; + } + assert(consistent(mem, mem2)); + UnreflectResult res2 = unreflectMember(lookup, mem2, variation); + MethodHandle mh2 = res2.mh; + assert(consistent(mh, mh2)); + MethodHandleInfo info2 = lookup.revealDirect(mh2); + assert(consistent(info, info2)); + assert(consistent(res, info2)); + Member mem3; + if (hasSM()) + mem3 = info2.reflectAs(Member.class, lookup); + else + mem3 = MethodHandles.reflectAs(Member.class, mh2); + assert(consistent(mem2, mem3)); + if (hasSM()) { + try { + MethodHandles.reflectAs(Member.class, mh2); + throw new AssertionError("failed to throw on "+mem3); + } catch (SecurityException ex3) { + // OK... + } + } + } + } +} diff --git a/jdk/test/java/lang/invoke/jtreg.security.policy b/jdk/test/java/lang/invoke/jtreg.security.policy new file mode 100644 index 00000000000..d32c7af9b3f --- /dev/null +++ b/jdk/test/java/lang/invoke/jtreg.security.policy @@ -0,0 +1,9 @@ +/* + * security policy used by the test process + * must allow file reads so that jtreg itself can run + */ + +grant { + // standard test activation permissions + permission java.io.FilePermission "*", "read"; +}; From 1f2ba9f2286e0de55a160f5d15a9db64e7024f06 Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Mon, 12 Aug 2013 12:22:10 +0200 Subject: [PATCH 16/75] 8024182: test/java/util/Arrays/SetAllTest.java fails to compile due to recent compiler changes Use explicit lambda due to javac simplfying rules for overload resolution with implicit lambdas Reviewed-by: alanb, mduigou --- jdk/test/java/util/Arrays/SetAllTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdk/test/java/util/Arrays/SetAllTest.java b/jdk/test/java/util/Arrays/SetAllTest.java index 2388a7bfd55..528d3c57b54 100644 --- a/jdk/test/java/util/Arrays/SetAllTest.java +++ b/jdk/test/java/util/Arrays/SetAllTest.java @@ -167,13 +167,13 @@ public class SetAllTest { public void testStringSetNulls() { String[] ar = new String[2]; try { - Arrays.setAll(null, i -> "X"); + Arrays.setAll(null, (IntFunction) i -> "X"); fail("Arrays.setAll(null, foo) should throw NPE"); } catch (NullPointerException npe) { // expected } try { - Arrays.parallelSetAll(null, i -> "X"); + Arrays.parallelSetAll(null, (IntFunction) i -> "X"); fail("Arrays.parallelSetAll(null, foo) should throw NPE"); } catch (NullPointerException npe) { // expected From 0fb014c2b3cff00c15e9e7cb6653818d061216e9 Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Wed, 4 Sep 2013 09:34:25 +0200 Subject: [PATCH 17/75] 8023463: Improvements to HashMap/LinkedHashMap use of bins/buckets and trees (red/black) 8012913: LinkedHashMap key/value/entry spliterators should report ORDERED Co-authored-by: Doug Lea Reviewed-by: mduigou, forax, bchristi, alanb --- jdk/src/share/classes/java/util/HashMap.java | 4155 +++++++---------- .../classes/java/util/LinkedHashMap.java | 608 ++- .../java/lang/reflect/Generics/Probe.java | 6 +- .../java/util/Map/CheckRandomHashSeed.java | 2 - .../java/util/Map/InPlaceOpsCollisions.java | 1 - .../java/util/Map/MapBinToFromTreeTest.java | 240 + .../util/Map/TreeBinSplitBackToEntries.java | 255 - .../SpliteratorCharacteristics.java | 107 +- 8 files changed, 2429 insertions(+), 2945 deletions(-) create mode 100644 jdk/test/java/util/Map/MapBinToFromTreeTest.java delete mode 100644 jdk/test/java/util/Map/TreeBinSplitBackToEntries.java diff --git a/jdk/src/share/classes/java/util/HashMap.java b/jdk/src/share/classes/java/util/HashMap.java index 8dfafac8642..a6a7d152b5f 100644 --- a/jdk/src/share/classes/java/util/HashMap.java +++ b/jdk/src/share/classes/java/util/HashMap.java @@ -25,13 +25,14 @@ package java.util; -import java.io.*; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.concurrent.ThreadLocalRandom; import java.util.function.BiConsumer; -import java.util.function.Consumer; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -63,20 +64,25 @@ import java.util.function.Function; * structures are rebuilt) so that the hash table has approximately twice the * number of buckets. * - *

As a general rule, the default load factor (.75) offers a good tradeoff - * between time and space costs. Higher values decrease the space overhead - * but increase the lookup cost (reflected in most of the operations of the - * HashMap class, including get and put). The - * expected number of entries in the map and its load factor should be taken - * into account when setting its initial capacity, so as to minimize the - * number of rehash operations. If the initial capacity is greater - * than the maximum number of entries divided by the load factor, no - * rehash operations will ever occur. + *

As a general rule, the default load factor (.75) offers a good + * tradeoff between time and space costs. Higher values decrease the + * space overhead but increase the lookup cost (reflected in most of + * the operations of the HashMap class, including + * get and put). The expected number of entries in + * the map and its load factor should be taken into account when + * setting its initial capacity, so as to minimize the number of + * rehash operations. If the initial capacity is greater than the + * maximum number of entries divided by the load factor, no rehash + * operations will ever occur. * - *

If many mappings are to be stored in a HashMap instance, - * creating it with a sufficiently large capacity will allow the mappings to - * be stored more efficiently than letting it perform automatic rehashing as - * needed to grow the table. + *

If many mappings are to be stored in a HashMap + * instance, creating it with a sufficiently large capacity will allow + * the mappings to be stored more efficiently than letting it perform + * automatic rehashing as needed to grow the table. Note that using + * many keys with the same {@code hashCode()} is a sure way to slow + * down performance of any hash table. To ameliorate impact, when keys + * are {@link Comparable}, this class may use comparison order among + * keys to help break ties. * *

Note that this implementation is not synchronized. * If multiple threads access a hash map concurrently, and at least one of @@ -128,11 +134,100 @@ import java.util.function.Function; * @see Hashtable * @since 1.2 */ +public class HashMap extends AbstractMap + implements Map, Cloneable, Serializable { -public class HashMap - extends AbstractMap - implements Map, Cloneable, Serializable -{ + private static final long serialVersionUID = 362498820763181265L; + + /* + * Implementation notes. + * + * This map usually acts as a binned (bucketed) hash table, but + * when bins get too large, they are transformed into bins of + * TreeNodes, each structured similarly to those in + * java.util.TreeMap. Most methods try to use normal bins, but + * relay to TreeNode methods when applicable (simply by checking + * instanceof a node). Bins of TreeNodes may be traversed and + * used like any others, but additionally support faster lookup + * when overpopulated. However, since the vast majority of bins in + * normal use are not overpopulated, checking for existence of + * tree bins may be delayed in the course of table methods. + * + * Tree bins (i.e., bins whose elements are all TreeNodes) are + * ordered primarily by hashCode, but in the case of ties, if two + * elements are of the same "class C implements Comparable", + * type then their compareTo method is used for ordering. (We + * conservatively check generic types via reflection to validate + * this -- see method comparableClassFor). The added complexity + * of tree bins is worthwhile in providing worst-case O(log n) + * operations when keys either have distinct hashes or are + * orderable, Thus, performance degrades gracefully under + * accidental or malicious usages in which hashCode() methods + * return values that are poorly distributed, as well as those in + * which many keys share a hashCode, so long as they are also + * Comparable. (If neither of these apply, we may waste about a + * factor of two in time and space compared to taking no + * precautions. But the only known cases stem from poor user + * programming practices that are already so slow that this makes + * little difference.) + * + * Because TreeNodes are about twice the size of regular nodes, we + * use them only when bins contain enough nodes to warrant use + * (see TREEIFY_THRESHOLD). And when they become too small (due to + * removal or resizing) they are converted back to plain bins. In + * usages with well-distributed user hashCodes, tree bins are + * rarely used. Ideally, under random hashCodes, the frequency of + * nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average for the default resizing + * threshold of 0.75, although with a large variance because of + * resizing granularity. Ignoring variance, the expected + * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / + * factorial(k)). The first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * The root of a tree bin is normally its first node. However, + * sometimes (currently only upon Iterator.remove), the root might + * be elsewhere, but can be recovered following parent links + * (method TreeNode.root()). + * + * All applicable internal methods accept a hash code as an + * argument (as normally supplied from a public method), allowing + * them to call each other without recomputing user hashCodes. + * Most internal methods also accept a "tab" argument, that is + * normally the current table, but may be a new or old one when + * resizing or converting. + * + * When bin lists are treeified, split, or untreeified, we keep + * them in the same relative access/traversal order (i.e., field + * Node.next) to better preserve locality, and to slightly + * simplify handling of splits and traversals that invoke + * iterator.remove. When using comparators on insertion, to keep a + * total ordering (or as close as is required here) across + * rebalancings, we compare classes and identityHashCodes as + * tie-breakers. + * + * The use and transitions among plain vs tree modes is + * complicated by the existence of subclass LinkedHashMap. See + * below for hook methods defined to be invoked upon insertion, + * removal and access that allow LinkedHashMap internals to + * otherwise remain independent of these mechanics. (This also + * requires that a map instance be passed to some utility methods + * that may create new nodes.) + * + * The concurrent-programming-like SSA-based coding style helps + * avoid aliasing errors amid all of the twisty pointer operations. + */ /** * The default initial capacity - MUST be a power of two. @@ -152,35 +247,164 @@ public class HashMap static final float DEFAULT_LOAD_FACTOR = 0.75f; /** - * An empty table instance to share when the table is not inflated. + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2 and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. */ - static final Object[] EMPTY_TABLE = {}; + static final int TREEIFY_THRESHOLD = 8; /** - * The table, resized as necessary. Length MUST Always be a power of two. + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. */ - transient Object[] table = EMPTY_TABLE; + static final int UNTREEIFY_THRESHOLD = 6; + + /** + * The smallest table capacity for which bins may be treeified. + * (Otherwise the table is resized if too many nodes in a bin.) + * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts + * between resizing and treeification thresholds. + */ + static final int MIN_TREEIFY_CAPACITY = 64; + + /** + * Basic hash bin node, used for most entries. (See below for + * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) + */ + static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next; + + Node(int hash, K key, V value, Node next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + public final K getKey() { return key; } + public final V getValue() { return value; } + public final String toString() { return key + "=" + value; } + + public final int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + public final boolean equals(Object o) { + if (o == this) + return true; + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry)o; + if (Objects.equals(key, e.getKey()) && + Objects.equals(value, e.getValue())) + return true; + } + return false; + } + } + + /* ---------------- Static utilities -------------- */ + + /** + * Computes key.hashCode() and spreads (XORs) higher bits of hash + * to lower. Because the table uses power-of-two masking, sets of + * hashes that vary only in bits above the current mask will + * always collide. (Among known examples are sets of Float keys + * holding consecutive whole numbers in small tables.) So we + * apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed (so don't benefit from + * spreading), and because we use trees to handle large sets of + * collisions in bins, we just XOR some shifted bits in the + * cheapest possible way to reduce systematic lossage, as well as + * to incorporate impact of the highest bits that would otherwise + * never be used in index calculations because of table bounds. + */ + static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + + /** + * Returns x's Class if it is of the form "class C implements + * Comparable", else null. + */ + static Class comparableClassFor(Object x) { + if (x instanceof Comparable) { + Class c; Type[] ts, as; Type t; ParameterizedType p; + if ((c = x.getClass()) == String.class) // bypass checks + return c; + if ((ts = c.getGenericInterfaces()) != null) { + for (int i = 0; i < ts.length; ++i) { + if (((t = ts[i]) instanceof ParameterizedType) && + ((p = (ParameterizedType)t).getRawType() == + Comparable.class) && + (as = p.getActualTypeArguments()) != null && + as.length == 1 && as[0] == c) // type arg is c + return c; + } + } + } + return null; + } + + /** + * Returns k.compareTo(x) if x matches kc (k's screened comparable + * class), else 0. + */ + @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable + static int compareComparables(Class kc, Object k, Object x) { + return (x == null || x.getClass() != kc ? 0 : + ((Comparable)k).compareTo(x)); + } + + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = cap - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /* ---------------- Fields -------------- */ + + /** + * The table, initialized on first use, and resized as + * necessary. When allocated, length is always a power of two. + * (We also tolerate length zero in some operations to allow + * bootstrapping mechanics that are currently not needed.) + */ + transient Node[] table; + + /** + * Holds cached entrySet(). Note that AbstractMap fields are used + * for keySet() and values(). + */ + transient Set> entrySet; /** * The number of key-value mappings contained in this map. */ transient int size; - /** - * The next size value at which to resize (capacity * load factor). - * @serial - */ - // If table == EMPTY_TABLE then this is the initial capacity at which the - // table will be created when inflated. - int threshold; - - /** - * The load factor for the hash table. - * - * @serial - */ - final float loadFactor; - /** * The number of times this HashMap has been structurally modified * Structural modifications are those that change the number of mappings in @@ -191,627 +415,24 @@ public class HashMap transient int modCount; /** - * Holds values which can't be initialized until after VM is booted. - */ - private static class Holder { - static final sun.misc.Unsafe UNSAFE; - - /** - * Offset of "final" hashSeed field we must set in - * readObject() method. - */ - static final long HASHSEED_OFFSET; - - static final boolean USE_HASHSEED; - - static { - String hashSeedProp = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction( - "jdk.map.useRandomSeed")); - boolean localBool = (null != hashSeedProp) - ? Boolean.parseBoolean(hashSeedProp) : false; - USE_HASHSEED = localBool; - - if (USE_HASHSEED) { - try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - HASHSEED_OFFSET = UNSAFE.objectFieldOffset( - HashMap.class.getDeclaredField("hashSeed")); - } catch (NoSuchFieldException | SecurityException e) { - throw new InternalError("Failed to record hashSeed offset", e); - } - } else { - UNSAFE = null; - HASHSEED_OFFSET = 0; - } - } - } - - /* - * A randomizing value associated with this instance that is applied to - * hash code of keys to make hash collisions harder to find. + * The next size value at which to resize (capacity * load factor). * - * Non-final so it can be set lazily, but be sure not to set more than once. + * @serial */ - transient final int hashSeed; - - /* - * TreeBin/TreeNode code from CHM doesn't handle the null key. Store the - * null key entry here. - */ - transient Entry nullKeyEntry = null; - - /* - * In order to improve performance under high hash-collision conditions, - * HashMap will switch to storing a bin's entries in a balanced tree - * (TreeBin) instead of a linked-list once the number of entries in the bin - * passes a certain threshold (TreeBin.TREE_THRESHOLD), if at least one of - * the keys in the bin implements Comparable. This technique is borrowed - * from ConcurrentHashMap. - */ - - /* - * Code based on CHMv8 - * - * Node type for TreeBin - */ - final static class TreeNode { - TreeNode parent; // red-black tree links - TreeNode left; - TreeNode right; - TreeNode prev; // needed to unlink next upon deletion - boolean red; - final HashMap.Entry entry; - - TreeNode(HashMap.Entry entry, Object next, TreeNode parent) { - this.entry = entry; - this.entry.next = next; - this.parent = parent; - } - } + // (The javadoc description is true upon serialization. + // Additionally, if the table array has not been allocated, this + // field holds the initial array capacity, or zero signifying + // DEFAULT_INITIAL_CAPACITY.) + int threshold; /** - * Returns a Class for the given object of the form "class C - * implements Comparable", if one exists, else null. See the TreeBin - * docs, below, for explanation. - */ - static Class comparableClassFor(Object x) { - Class c, s, cmpc; Type[] ts, as; Type t; ParameterizedType p; - if ((c = x.getClass()) == String.class) // bypass checks - return c; - if ((cmpc = Comparable.class).isAssignableFrom(c)) { - while (cmpc.isAssignableFrom(s = c.getSuperclass())) - c = s; // find topmost comparable class - if ((ts = c.getGenericInterfaces()) != null) { - for (int i = 0; i < ts.length; ++i) { - if (((t = ts[i]) instanceof ParameterizedType) && - ((p = (ParameterizedType)t).getRawType() == cmpc) && - (as = p.getActualTypeArguments()) != null && - as.length == 1 && as[0] == c) // type arg is c - return c; - } - } - } - return null; - } - - /* - * Code based on CHMv8 + * The load factor for the hash table. * - * A specialized form of red-black tree for use in bins - * whose size exceeds a threshold. - * - * TreeBins use a special form of comparison for search and - * related operations (which is the main reason we cannot use - * existing collections such as TreeMaps). TreeBins contain - * Comparable elements, but may contain others, as well as - * elements that are Comparable but not necessarily Comparable - * for the same T, so we cannot invoke compareTo among them. To - * handle this, the tree is ordered primarily by hash value, then - * by Comparable.compareTo order if applicable. On lookup at a - * node, if elements are not comparable or compare as 0 then both - * left and right children may need to be searched in the case of - * tied hash values. (This corresponds to the full list search - * that would be necessary if all elements were non-Comparable and - * had tied hashes.) The red-black balancing code is updated from - * pre-jdk-collections - * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) - * based in turn on Cormen, Leiserson, and Rivest "Introduction to - * Algorithms" (CLR). + * @serial */ - final class TreeBin { - /* - * The bin count threshold for using a tree rather than list for a bin. The - * value reflects the approximate break-even point for using tree-based - * operations. - */ - static final int TREE_THRESHOLD = 16; + final float loadFactor; - TreeNode root; // root of tree - TreeNode first; // head of next-pointer list - - /* - * Split a TreeBin into lo and hi parts and install in given table. - * - * Existing Entrys are re-used, which maintains the before/after links for - * LinkedHashMap.Entry. - * - * No check for Comparable, though this is the same as CHM. - */ - final void splitTreeBin(Object[] newTable, int i, TreeBin loTree, TreeBin hiTree) { - TreeBin oldTree = this; - int bit = newTable.length >>> 1; - int loCount = 0, hiCount = 0; - TreeNode e = oldTree.first; - TreeNode next; - - // This method is called when the table has just increased capacity, - // so indexFor() is now taking one additional bit of hash into - // account ("bit"). Entries in this TreeBin now belong in one of - // two bins, "i" or "i+bit", depending on if the new top bit of the - // hash is set. The trees for the two bins are loTree and hiTree. - // If either tree ends up containing fewer than TREE_THRESHOLD - // entries, it is converted back to a linked list. - while (e != null) { - // Save entry.next - it will get overwritten in putTreeNode() - next = (TreeNode)e.entry.next; - - int h = e.entry.hash; - K k = (K) e.entry.key; - V v = e.entry.value; - if ((h & bit) == 0) { - ++loCount; - // Re-using e.entry - loTree.putTreeNode(h, k, v, e.entry); - } else { - ++hiCount; - hiTree.putTreeNode(h, k, v, e.entry); - } - // Iterate using the saved 'next' - e = next; - } - if (loCount < TREE_THRESHOLD) { // too small, convert back to list - HashMap.Entry loEntry = null; - TreeNode p = loTree.first; - while (p != null) { - @SuppressWarnings("unchecked") - TreeNode savedNext = (TreeNode) p.entry.next; - p.entry.next = loEntry; - loEntry = p.entry; - p = savedNext; - } - // assert newTable[i] == null; - newTable[i] = loEntry; - } else { - // assert newTable[i] == null; - newTable[i] = loTree; - } - if (hiCount < TREE_THRESHOLD) { // too small, convert back to list - HashMap.Entry hiEntry = null; - TreeNode p = hiTree.first; - while (p != null) { - @SuppressWarnings("unchecked") - TreeNode savedNext = (TreeNode) p.entry.next; - p.entry.next = hiEntry; - hiEntry = p.entry; - p = savedNext; - } - // assert newTable[i + bit] == null; - newTable[i + bit] = hiEntry; - } else { - // assert newTable[i + bit] == null; - newTable[i + bit] = hiTree; - } - } - - /* - * Popuplate the TreeBin with entries from the linked list e - * - * Assumes 'this' is a new/empty TreeBin - * - * Note: no check for Comparable - * Note: I believe this changes iteration order - */ - @SuppressWarnings("unchecked") - void populate(HashMap.Entry e) { - // assert root == null; - // assert first == null; - HashMap.Entry next; - while (e != null) { - // Save entry.next - it will get overwritten in putTreeNode() - next = (HashMap.Entry)e.next; - // Re-using Entry e will maintain before/after in LinkedHM - putTreeNode(e.hash, (K)e.key, (V)e.value, e); - // Iterate using the saved 'next' - e = next; - } - } - - /** - * Copied from CHMv8 - * From CLR - */ - private void rotateLeft(TreeNode p) { - if (p != null) { - TreeNode r = p.right, pp, rl; - if ((rl = p.right = r.left) != null) { - rl.parent = p; - } - if ((pp = r.parent = p.parent) == null) { - root = r; - } else if (pp.left == p) { - pp.left = r; - } else { - pp.right = r; - } - r.left = p; - p.parent = r; - } - } - - /** - * Copied from CHMv8 - * From CLR - */ - private void rotateRight(TreeNode p) { - if (p != null) { - TreeNode l = p.left, pp, lr; - if ((lr = p.left = l.right) != null) { - lr.parent = p; - } - if ((pp = l.parent = p.parent) == null) { - root = l; - } else if (pp.right == p) { - pp.right = l; - } else { - pp.left = l; - } - l.right = p; - p.parent = l; - } - } - - /** - * Returns the TreeNode (or null if not found) for the given - * key. A front-end for recursive version. - */ - final TreeNode getTreeNode(int h, K k) { - return getTreeNode(h, k, root, comparableClassFor(k)); - } - - /** - * Returns the TreeNode (or null if not found) for the given key - * starting at given root. - */ - @SuppressWarnings("unchecked") - final TreeNode getTreeNode (int h, K k, TreeNode p, Class cc) { - // assert k != null; - while (p != null) { - int dir, ph; Object pk; - if ((ph = p.entry.hash) != h) - dir = (h < ph) ? -1 : 1; - else if ((pk = p.entry.key) == k || k.equals(pk)) - return p; - else if (cc == null || comparableClassFor(pk) != cc || - (dir = ((Comparable)k).compareTo(pk)) == 0) { - // assert pk != null; - TreeNode r, pl, pr; // check both sides - if ((pr = p.right) != null && - (r = getTreeNode(h, k, pr, cc)) != null) - return r; - else if ((pl = p.left) != null) - dir = -1; - else // nothing there - break; - } - p = (dir > 0) ? p.right : p.left; - } - return null; - } - - /* - * Finds or adds a node. - * - * 'entry' should be used to recycle an existing Entry (e.g. in the case - * of converting a linked-list bin to a TreeBin). - * If entry is null, a new Entry will be created for the new TreeNode - * - * @return the TreeNode containing the mapping, or null if a new - * TreeNode was added - */ - @SuppressWarnings("unchecked") - TreeNode putTreeNode(int h, K k, V v, HashMap.Entry entry) { - // assert k != null; - //if (entry != null) { - // assert h == entry.hash; - // assert k == entry.key; - // assert v == entry.value; - // } - Class cc = comparableClassFor(k); - TreeNode pp = root, p = null; - int dir = 0; - while (pp != null) { // find existing node or leaf to insert at - int ph; Object pk; - p = pp; - if ((ph = p.entry.hash) != h) - dir = (h < ph) ? -1 : 1; - else if ((pk = p.entry.key) == k || k.equals(pk)) - return p; - else if (cc == null || comparableClassFor(pk) != cc || - (dir = ((Comparable)k).compareTo(pk)) == 0) { - TreeNode r, pr; - if ((pr = p.right) != null && - (r = getTreeNode(h, k, pr, cc)) != null) - return r; - else // continue left - dir = -1; - } - pp = (dir > 0) ? p.right : p.left; - } - - // Didn't find the mapping in the tree, so add it - TreeNode f = first; - TreeNode x; - if (entry != null) { - x = new TreeNode(entry, f, p); - } else { - x = new TreeNode(newEntry(h, k, v, null), f, p); - } - first = x; - - if (p == null) { - root = x; - } else { // attach and rebalance; adapted from CLR - TreeNode xp, xpp; - if (f != null) { - f.prev = x; - } - if (dir <= 0) { - p.left = x; - } else { - p.right = x; - } - x.red = true; - while (x != null && (xp = x.parent) != null && xp.red - && (xpp = xp.parent) != null) { - TreeNode xppl = xpp.left; - if (xp == xppl) { - TreeNode y = xpp.right; - if (y != null && y.red) { - y.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } else { - if (x == xp.right) { - rotateLeft(x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - rotateRight(xpp); - } - } - } - } else { - TreeNode y = xppl; - if (y != null && y.red) { - y.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } else { - if (x == xp.left) { - rotateRight(x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - rotateLeft(xpp); - } - } - } - } - } - TreeNode r = root; - if (r != null && r.red) { - r.red = false; - } - } - return null; - } - - /* - * From CHMv8 - * - * Removes the given node, that must be present before this - * call. This is messier than typical red-black deletion code - * because we cannot swap the contents of an interior node - * with a leaf successor that is pinned by "next" pointers - * that are accessible independently of lock. So instead we - * swap the tree linkages. - */ - final void deleteTreeNode(TreeNode p) { - TreeNode next = (TreeNode) p.entry.next; // unlink traversal pointers - TreeNode pred = p.prev; - if (pred == null) { - first = next; - } else { - pred.entry.next = next; - } - if (next != null) { - next.prev = pred; - } - TreeNode replacement; - TreeNode pl = p.left; - TreeNode pr = p.right; - if (pl != null && pr != null) { - TreeNode s = pr, sl; - while ((sl = s.left) != null) // find successor - { - s = sl; - } - boolean c = s.red; - s.red = p.red; - p.red = c; // swap colors - TreeNode sr = s.right; - TreeNode pp = p.parent; - if (s == pr) { // p was s's direct parent - p.parent = s; - s.right = p; - } else { - TreeNode sp = s.parent; - if ((p.parent = sp) != null) { - if (s == sp.left) { - sp.left = p; - } else { - sp.right = p; - } - } - if ((s.right = pr) != null) { - pr.parent = s; - } - } - p.left = null; - if ((p.right = sr) != null) { - sr.parent = p; - } - if ((s.left = pl) != null) { - pl.parent = s; - } - if ((s.parent = pp) == null) { - root = s; - } else if (p == pp.left) { - pp.left = s; - } else { - pp.right = s; - } - replacement = sr; - } else { - replacement = (pl != null) ? pl : pr; - } - TreeNode pp = p.parent; - if (replacement == null) { - if (pp == null) { - root = null; - return; - } - replacement = p; - } else { - replacement.parent = pp; - if (pp == null) { - root = replacement; - } else if (p == pp.left) { - pp.left = replacement; - } else { - pp.right = replacement; - } - p.left = p.right = p.parent = null; - } - if (!p.red) { // rebalance, from CLR - TreeNode x = replacement; - while (x != null) { - TreeNode xp, xpl; - if (x.red || (xp = x.parent) == null) { - x.red = false; - break; - } - if (x == (xpl = xp.left)) { - TreeNode sib = xp.right; - if (sib != null && sib.red) { - sib.red = false; - xp.red = true; - rotateLeft(xp); - sib = (xp = x.parent) == null ? null : xp.right; - } - if (sib == null) { - x = xp; - } else { - TreeNode sl = sib.left, sr = sib.right; - if ((sr == null || !sr.red) - && (sl == null || !sl.red)) { - sib.red = true; - x = xp; - } else { - if (sr == null || !sr.red) { - if (sl != null) { - sl.red = false; - } - sib.red = true; - rotateRight(sib); - sib = (xp = x.parent) == null ? - null : xp.right; - } - if (sib != null) { - sib.red = (xp == null) ? false : xp.red; - if ((sr = sib.right) != null) { - sr.red = false; - } - } - if (xp != null) { - xp.red = false; - rotateLeft(xp); - } - x = root; - } - } - } else { // symmetric - TreeNode sib = xpl; - if (sib != null && sib.red) { - sib.red = false; - xp.red = true; - rotateRight(xp); - sib = (xp = x.parent) == null ? null : xp.left; - } - if (sib == null) { - x = xp; - } else { - TreeNode sl = sib.left, sr = sib.right; - if ((sl == null || !sl.red) - && (sr == null || !sr.red)) { - sib.red = true; - x = xp; - } else { - if (sl == null || !sl.red) { - if (sr != null) { - sr.red = false; - } - sib.red = true; - rotateLeft(sib); - sib = (xp = x.parent) == null ? - null : xp.left; - } - if (sib != null) { - sib.red = (xp == null) ? false : xp.red; - if ((sl = sib.left) != null) { - sl.red = false; - } - } - if (xp != null) { - xp.red = false; - rotateRight(xp); - } - x = root; - } - } - } - } - } - if (p == replacement && (pp = p.parent) != null) { - if (p == pp.left) // detach pointers - { - pp.left = null; - } else if (p == pp.right) { - pp.right = null; - } - p.parent = null; - } - } - } + /* ---------------- Public operations -------------- */ /** * Constructs an empty HashMap with the specified initial @@ -832,9 +453,7 @@ public class HashMap throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; - threshold = initialCapacity; - hashSeed = initHashSeed(); - init(); + this.threshold = tableSizeFor(initialCapacity); } /** @@ -853,7 +472,7 @@ public class HashMap * (16) and the default load factor (0.75). */ public HashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** @@ -866,79 +485,35 @@ public class HashMap * @throws NullPointerException if the specified map is null */ public HashMap(Map m) { - this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, - DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); - inflateTable(threshold); - - putAllForCreate(m); - // assert size == m.size(); - } - - private static int roundUpToPowerOf2(int number) { - // assert number >= 0 : "number must be non-negative"; - return number >= MAXIMUM_CAPACITY - ? MAXIMUM_CAPACITY - : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); } /** - * Inflates the table. + * Implements Map.putAll and Map constructor + * + * @param m the map + * @param evict false when initially constructing this map, else + * true (relayed to method afterNodeInsertion). */ - private void inflateTable(int toSize) { - // Find a power of 2 >= toSize - int capacity = roundUpToPowerOf2(toSize); - - threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); - table = new Object[capacity]; - } - - // internal utilities - - /** - * Initialization hook for subclasses. This method is called - * in all constructors and pseudo-constructors (clone, readObject) - * after HashMap has been initialized but before any entries have - * been inserted. (In the absence of this method, readObject would - * require explicit knowledge of subclasses.) - */ - void init() { - } - - /** - * Return an initial value for the hashSeed, or 0 if the random seed is not - * enabled. - */ - final int initHashSeed() { - if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) { - int seed = ThreadLocalRandom.current().nextInt(); - return (seed != 0) ? seed : 1; + final void putMapEntries(Map m, boolean evict) { + int s = m.size(); + if (s > 0) { + if (table == null) { // pre-size + float ft = ((float)s / loadFactor) + 1.0F; + int t = ((ft < (float)MAXIMUM_CAPACITY) ? + (int)ft : MAXIMUM_CAPACITY); + if (t > threshold) + threshold = tableSizeFor(t); + } + else if (s > threshold) + resize(); + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false, evict); + } } - return 0; - } - - /** - * Retrieve object hash code and applies a supplemental hash function to the - * result hash, which defends against poor quality hash functions. This is - * critical because HashMap uses power-of-two length hash tables, that - * otherwise encounter collisions for hashCodes that do not differ - * in lower bits. - */ - final int hash(Object k) { - int h = hashSeed ^ k.hashCode(); - - // This function ensures that hashCodes that differ only by - // constant multiples at each bit position have a bounded - // number of collisions (approximately 8 at default load factor). - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); - } - - /** - * Returns index for hash code h. - */ - static int indexFor(int h, int length) { - // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; - return h & (length-1); } /** @@ -976,18 +551,36 @@ public class HashMap * * @see #put(Object, Object) */ - @SuppressWarnings("unchecked") public V get(Object key) { - Entry entry = getEntry(key); - - return null == entry ? null : entry.getValue(); + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; } - @Override - public V getOrDefault(Object key, V defaultValue) { - Entry entry = getEntry(key); - - return (entry == null) ? defaultValue : entry.getValue(); + /** + * Implements Map.get and related methods + * + * @param hash hash for key + * @param key the key + * @return the node, or null if none + */ + final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + if ((e = first.next) != null) { + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key); + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; } /** @@ -999,48 +592,9 @@ public class HashMap * key. */ public boolean containsKey(Object key) { - return getEntry(key) != null; + return getNode(hash(key), key) != null; } - /** - * Returns the entry associated with the specified key in the - * HashMap. Returns null if the HashMap contains no mapping - * for the key. - */ - @SuppressWarnings("unchecked") - final Entry getEntry(Object key) { - if (size == 0) { - return null; - } - if (key == null) { - return nullKeyEntry; - } - int hash = hash(key); - int bin = indexFor(hash, table.length); - - if (table[bin] instanceof Entry) { - Entry e = (Entry) table[bin]; - for (; e != null; e = (Entry)e.next) { - Object k; - if (e.hash == hash && - ((k = e.key) == key || key.equals(k))) { - return e; - } - } - } else if (table[bin] != null) { - TreeBin e = (TreeBin)table[bin]; - TreeNode p = e.getTreeNode(hash, (K)key); - if (p != null) { - // assert p.entry.hash == hash && p.entry.key.equals(key); - return (Entry)p.entry; - } else { - return null; - } - } - return null; - } - - /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old @@ -1053,202 +607,169 @@ public class HashMap * (A null return can also indicate that the map * previously associated null with key.) */ - @SuppressWarnings("unchecked") public V put(K key, V value) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); - } - if (key == null) - return putForNullKey(value); - int hash = hash(key); - int i = indexFor(hash, table.length); - boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? + return putVal(hash(key), key, value, false, true); + } - if (table[i] instanceof Entry) { - // Bin contains ordinary Entries. Search for key in the linked list - // of entries, counting the number of entries. Only check for - // TreeBin conversion if the list size is >= TREE_THRESHOLD. - // (The conversion still may not happen if the table gets resized.) - int listSize = 0; - Entry e = (Entry) table[i]; - for (; e != null; e = (Entry)e.next) { - Object k; - if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { - V oldValue = e.value; - e.value = value; - e.recordAccess(this); - return oldValue; + /** + * Implements Map.put and related methods + * + * @param hash hash for key + * @param key the key + * @param value the value to put + * @param onlyIfAbsent if true, don't change existing value + * @param evict if false, the table is in creation mode. + * @return previous value, or null if none + */ + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; } - listSize++; } - // Didn't find, so fall through and call addEntry() to add the - // Entry and check for TreeBin conversion. - checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; - } else if (table[i] != null) { - TreeBin e = (TreeBin)table[i]; - TreeNode p = e.putTreeNode(hash, key, value, null); - if (p == null) { // putTreeNode() added a new node - modCount++; - size++; - if (size >= threshold) { - resize(2 * table.length); - } - return null; - } else { // putTreeNode() found an existing node - Entry pEntry = (Entry)p.entry; - V oldVal = pEntry.value; - pEntry.value = value; - pEntry.recordAccess(this); - return oldVal; + if (e != null) { // existing mapping for key + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); + return oldValue; } } - modCount++; - addEntry(hash, key, value, i, checkIfNeedTree); + ++modCount; + ++size; + afterNodeInsertion(evict); return null; } /** - * Offloaded version of put for null keys + * Initializes or doubles table size. If null, allocates in + * accord with initial capacity target held in field threshold. + * Otherwise, because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move + * with a power of two offset in the new table. + * + * @return the table */ - private V putForNullKey(V value) { - if (nullKeyEntry != null) { - V oldValue = nullKeyEntry.value; - nullKeyEntry.value = value; - nullKeyEntry.recordAccess(this); - return oldValue; + final Node[] resize() { + Node[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold } - modCount++; - size++; // newEntry() skips size++ - nullKeyEntry = newEntry(0, null, value, null); - return null; - } - - private void putForCreateNullKey(V value) { - // Look for preexisting entry for key. This will never happen for - // clone or deserialize. It will only happen for construction if the - // input Map is a sorted map whose ordering is inconsistent w/ equals. - if (nullKeyEntry != null) { - nullKeyEntry.value = value; - } else { - nullKeyEntry = newEntry(0, null, value, null); - size++; + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } - } - - - /** - * This method is used instead of put by constructors and - * pseudoconstructors (clone, readObject). It does not resize the table, - * check for comodification, etc, though it will convert bins to TreeBins - * as needed. It calls createEntry rather than addEntry. - */ - @SuppressWarnings("unchecked") - private void putForCreate(K key, V value) { - if (null == key) { - putForCreateNullKey(value); - return; + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); } - int hash = hash(key); - int i = indexFor(hash, table.length); - boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? - - /** - * Look for preexisting entry for key. This will never happen for - * clone or deserialize. It will only happen for construction if the - * input Map is a sorted map whose ordering is inconsistent w/ equals. - */ - if (table[i] instanceof Entry) { - int listSize = 0; - Entry e = (Entry) table[i]; - for (; e != null; e = (Entry)e.next) { - Object k; - if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { - e.value = value; - return; + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + if ((e.hash & oldCap) == 0) { + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + else { + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } } - listSize++; } - // Didn't find, fall through to createEntry(). - // Check for conversion to TreeBin done via createEntry(). - checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; - } else if (table[i] != null) { - TreeBin e = (TreeBin)table[i]; - TreeNode p = e.putTreeNode(hash, key, value, null); - if (p != null) { - p.entry.setValue(value); // Found an existing node, set value - } else { - size++; // Added a new TreeNode, so update size - } - // don't need modCount++/check for resize - just return - return; } - - createEntry(hash, key, value, i, checkIfNeedTree); - } - - private void putAllForCreate(Map m) { - for (Map.Entry e : m.entrySet()) - putForCreate(e.getKey(), e.getValue()); + return newTab; } /** - * Rehashes the contents of this map into a new array with a - * larger capacity. This method is called automatically when the - * number of keys in this map reaches its threshold. - * - * If current capacity is MAXIMUM_CAPACITY, this method does not - * resize the map, but sets threshold to Integer.MAX_VALUE. - * This has the effect of preventing future calls. - * - * @param newCapacity the new capacity, MUST be a power of two; - * must be greater than current capacity unless current - * capacity is MAXIMUM_CAPACITY (in which case value - * is irrelevant). + * Replaces all linked nodes in bin at index for given hash unless + * table is too small, in which case resizes instead. */ - void resize(int newCapacity) { - Object[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity == MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - - Object[] newTable = new Object[newCapacity]; - transfer(newTable); - table = newTable; - threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); - } - - /** - * Transfers all entries from current table to newTable. - * - * Assumes newTable is larger than table - */ - @SuppressWarnings("unchecked") - void transfer(Object[] newTable) { - Object[] src = table; - // assert newTable.length > src.length : "newTable.length(" + - // newTable.length + ") expected to be > src.length("+src.length+")"; - int newCapacity = newTable.length; - for (int j = 0; j < src.length; j++) { - if (src[j] instanceof Entry) { - // Assume: since wasn't TreeBin before, won't need TreeBin now - Entry e = (Entry) src[j]; - while (null != e) { - Entry next = (Entry)e.next; - int i = indexFor(e.hash, newCapacity); - e.next = (Entry) newTable[i]; - newTable[i] = e; - e = next; + final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) + resize(); + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; + do { + TreeNode p = replacementTreeNode(e, null); + if (tl == null) + hd = p; + else { + p.prev = tl; + tl.next = p; } - } else if (src[j] != null) { - TreeBin e = (TreeBin) src[j]; - TreeBin loTree = new TreeBin(); - TreeBin hiTree = new TreeBin(); - e.splitTreeBin(newTable, j, loTree, hiTree); - } + tl = p; + } while ((e = e.next) != null); + if ((tab[index] = hd) != null) + hd.treeify(tab); } - Arrays.fill(table, null); } /** @@ -1260,30 +781,8 @@ public class HashMap * @throws NullPointerException if the specified map is null */ public void putAll(Map m) { - int numKeysToBeAdded = m.size(); - if (numKeysToBeAdded == 0) - return; - - if (table == EMPTY_TABLE) { - inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold)); - } - - /* - * Expand the map if the map if the number of mappings to be added - * is greater than or equal to threshold. This is conservative; the - * obvious condition is (m.size() + size) >= threshold, but this - * condition could result in a map with twice the appropriate capacity, - * if the keys to be added overlap with the keys already in this map. - * By using the conservative calculation, we subject ourself - * to at most one extra resize. - */ - if (numKeysToBeAdded > threshold && table.length < MAXIMUM_CAPACITY) { - resize(table.length * 2); - } - - for (Map.Entry e : m.entrySet()) - put(e.getKey(), e.getValue()); - } + putMapEntries(m, true); + } /** * Removes the mapping for the specified key from this map if present. @@ -1295,834 +794,74 @@ public class HashMap * previously associated null with key.) */ public V remove(Object key) { - Entry e = removeEntryForKey(key); - return (e == null ? null : e.value); - } - - // optimized implementations of default methods in Map - - @Override - public void forEach(BiConsumer action) { - Objects.requireNonNull(action); - final int expectedModCount = modCount; - if (nullKeyEntry != null) { - forEachNullKey(expectedModCount, action); - } - Object[] tab = this.table; - for (int index = 0; index < tab.length; index++) { - Object item = tab[index]; - if (item == null) { - continue; - } - if (item instanceof HashMap.TreeBin) { - eachTreeNode(expectedModCount, ((TreeBin)item).first, action); - continue; - } - @SuppressWarnings("unchecked") - Entry entry = (Entry)item; - while (entry != null) { - action.accept(entry.key, entry.value); - entry = (Entry)entry.next; - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - } - } - - private void eachTreeNode(int expectedModCount, TreeNode node, BiConsumer action) { - while (node != null) { - @SuppressWarnings("unchecked") - Entry entry = (Entry)node.entry; - action.accept(entry.key, entry.value); - node = (TreeNode)entry.next; - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - } - - private void forEachNullKey(int expectedModCount, BiConsumer action) { - action.accept(null, nullKeyEntry.value); - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - - @Override - public void replaceAll(BiFunction function) { - Objects.requireNonNull(function); - final int expectedModCount = modCount; - if (nullKeyEntry != null) { - replaceforNullKey(expectedModCount, function); - } - Object[] tab = this.table; - for (int index = 0; index < tab.length; index++) { - Object item = tab[index]; - if (item == null) { - continue; - } - if (item instanceof HashMap.TreeBin) { - replaceEachTreeNode(expectedModCount, ((TreeBin)item).first, function); - continue; - } - @SuppressWarnings("unchecked") - Entry entry = (Entry)item; - while (entry != null) { - entry.value = function.apply(entry.key, entry.value); - entry = (Entry)entry.next; - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - } - } - - private void replaceEachTreeNode(int expectedModCount, TreeNode node, BiFunction function) { - while (node != null) { - @SuppressWarnings("unchecked") - Entry entry = (Entry)node.entry; - entry.value = function.apply(entry.key, entry.value); - node = (TreeNode)entry.next; - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - } - - private void replaceforNullKey(int expectedModCount, BiFunction function) { - nullKeyEntry.value = function.apply(null, nullKeyEntry.value); - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - - @Override - public V putIfAbsent(K key, V value) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); - } - if (key == null) { - if (nullKeyEntry == null || nullKeyEntry.value == null) { - putForNullKey(value); - return null; - } else { - return nullKeyEntry.value; - } - } - int hash = hash(key); - int i = indexFor(hash, table.length); - boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? - - if (table[i] instanceof Entry) { - int listSize = 0; - Entry e = (Entry) table[i]; - for (; e != null; e = (Entry)e.next) { - if (e.hash == hash && Objects.equals(e.key, key)) { - if (e.value != null) { - return e.value; - } - e.value = value; - e.recordAccess(this); - return null; - } - listSize++; - } - // Didn't find, so fall through and call addEntry() to add the - // Entry and check for TreeBin conversion. - checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; - } else if (table[i] != null) { - TreeBin e = (TreeBin)table[i]; - TreeNode p = e.putTreeNode(hash, key, value, null); - if (p == null) { // not found, putTreeNode() added a new node - modCount++; - size++; - if (size >= threshold) { - resize(2 * table.length); - } - return null; - } else { // putTreeNode() found an existing node - Entry pEntry = (Entry)p.entry; - V oldVal = pEntry.value; - if (oldVal == null) { // only replace if maps to null - pEntry.value = value; - pEntry.recordAccess(this); - } - return oldVal; - } - } - modCount++; - addEntry(hash, key, value, i, checkIfNeedTree); - return null; - } - - @Override - public boolean remove(Object key, Object value) { - if (size == 0) { - return false; - } - if (key == null) { - if (nullKeyEntry != null && - Objects.equals(nullKeyEntry.value, value)) { - removeNullKey(); - return true; - } - return false; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - - if (table[i] instanceof Entry) { - @SuppressWarnings("unchecked") - Entry prev = (Entry) table[i]; - Entry e = prev; - while (e != null) { - @SuppressWarnings("unchecked") - Entry next = (Entry) e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - if (!Objects.equals(e.value, value)) { - return false; - } - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - return true; - } - prev = e; - e = next; - } - } else if (table[i] != null) { - TreeBin tb = ((TreeBin) table[i]); - TreeNode p = tb.getTreeNode(hash, (K)key); - if (p != null) { - Entry pEntry = (Entry)p.entry; - // assert pEntry.key.equals(key); - if (Objects.equals(pEntry.value, value)) { - modCount++; - size--; - tb.deleteTreeNode(p); - pEntry.recordRemoval(this); - if (tb.root == null || tb.first == null) { - // assert tb.root == null && tb.first == null : - // "TreeBin.first and root should both be null"; - // TreeBin is now empty, we should blank this bin - table[i] = null; - } - return true; - } - } - } - return false; - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - if (size == 0) { - return false; - } - if (key == null) { - if (nullKeyEntry != null && - Objects.equals(nullKeyEntry.value, oldValue)) { - putForNullKey(newValue); - return true; - } - return false; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - - if (table[i] instanceof Entry) { - @SuppressWarnings("unchecked") - Entry e = (Entry) table[i]; - for (; e != null; e = (Entry)e.next) { - if (e.hash == hash && Objects.equals(e.key, key) && Objects.equals(e.value, oldValue)) { - e.value = newValue; - e.recordAccess(this); - return true; - } - } - return false; - } else if (table[i] != null) { - TreeBin tb = ((TreeBin) table[i]); - TreeNode p = tb.getTreeNode(hash, key); - if (p != null) { - Entry pEntry = (Entry)p.entry; - // assert pEntry.key.equals(key); - if (Objects.equals(pEntry.value, oldValue)) { - pEntry.value = newValue; - pEntry.recordAccess(this); - return true; - } - } - } - return false; - } - - @Override - public V replace(K key, V value) { - if (size == 0) { - return null; - } - if (key == null) { - if (nullKeyEntry != null) { - return putForNullKey(value); - } - return null; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - if (table[i] instanceof Entry) { - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for (; e != null; e = (Entry)e.next) { - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - e.value = value; - e.recordAccess(this); - return oldValue; - } - } - - return null; - } else if (table[i] != null) { - TreeBin tb = ((TreeBin) table[i]); - TreeNode p = tb.getTreeNode(hash, key); - if (p != null) { - Entry pEntry = (Entry)p.entry; - // assert pEntry.key.equals(key); - V oldValue = pEntry.value; - pEntry.value = value; - pEntry.recordAccess(this); - return oldValue; - } - } - return null; - } - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); - } - if (key == null) { - if (nullKeyEntry == null || nullKeyEntry.value == null) { - V newValue = mappingFunction.apply(key); - if (newValue != null) { - putForNullKey(newValue); - } - return newValue; - } - return nullKeyEntry.value; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? - - if (table[i] instanceof Entry) { - int listSize = 0; - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for (; e != null; e = (Entry)e.next) { - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - if (oldValue == null) { - V newValue = mappingFunction.apply(key); - if (newValue != null) { - e.value = newValue; - e.recordAccess(this); - } - return newValue; - } - return oldValue; - } - listSize++; - } - // Didn't find, fall through to call the mapping function - checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; - } else if (table[i] != null) { - TreeBin e = (TreeBin)table[i]; - V value = mappingFunction.apply(key); - if (value == null) { // Return the existing value, if any - TreeNode p = e.getTreeNode(hash, key); - if (p != null) { - return (V) p.entry.value; - } - return null; - } else { // Put the new value into the Tree, if absent - TreeNode p = e.putTreeNode(hash, key, value, null); - if (p == null) { // not found, new node was added - modCount++; - size++; - if (size >= threshold) { - resize(2 * table.length); - } - return value; - } else { // putTreeNode() found an existing node - Entry pEntry = (Entry)p.entry; - V oldVal = pEntry.value; - if (oldVal == null) { // only replace if maps to null - pEntry.value = value; - pEntry.recordAccess(this); - return value; - } - return oldVal; - } - } - } - V newValue = mappingFunction.apply(key); - if (newValue != null) { // add Entry and check for TreeBin conversion - modCount++; - addEntry(hash, key, newValue, i, checkIfNeedTree); - } - - return newValue; - } - - @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { - if (size == 0) { - return null; - } - if (key == null) { - V oldValue; - if (nullKeyEntry != null && (oldValue = nullKeyEntry.value) != null) { - V newValue = remappingFunction.apply(key, oldValue); - if (newValue != null ) { - putForNullKey(newValue); - return newValue; - } else { - removeNullKey(); - } - } - return null; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - if (table[i] instanceof Entry) { - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - while (e != null) { - Entry next = (Entry)e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - if (oldValue == null) - break; - V newValue = remappingFunction.apply(key, oldValue); - if (newValue == null) { - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - } else { - e.value = newValue; - e.recordAccess(this); - } - return newValue; - } - prev = e; - e = next; - } - } else if (table[i] != null) { - TreeBin tb = (TreeBin)table[i]; - TreeNode p = tb.getTreeNode(hash, key); - if (p != null) { - Entry pEntry = (Entry)p.entry; - // assert pEntry.key.equals(key); - V oldValue = pEntry.value; - if (oldValue != null) { - V newValue = remappingFunction.apply(key, oldValue); - if (newValue == null) { // remove mapping - modCount++; - size--; - tb.deleteTreeNode(p); - pEntry.recordRemoval(this); - if (tb.root == null || tb.first == null) { - // assert tb.root == null && tb.first == null : - // "TreeBin.first and root should both be null"; - // TreeBin is now empty, we should blank this bin - table[i] = null; - } - } else { - pEntry.value = newValue; - pEntry.recordAccess(this); - } - return newValue; - } - } - } - return null; - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); - } - if (key == null) { - V oldValue = nullKeyEntry == null ? null : nullKeyEntry.value; - V newValue = remappingFunction.apply(key, oldValue); - if (newValue != oldValue || (oldValue == null && nullKeyEntry != null)) { - if (newValue == null) { - removeNullKey(); - } else { - putForNullKey(newValue); - } - } - return newValue; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? - - if (table[i] instanceof Entry) { - int listSize = 0; - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - Entry next = (Entry)e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - V newValue = remappingFunction.apply(key, oldValue); - if (newValue != oldValue || oldValue == null) { - if (newValue == null) { - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - } else { - e.value = newValue; - e.recordAccess(this); - } - } - return newValue; - } - prev = e; - e = next; - listSize++; - } - checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; - } else if (table[i] != null) { - TreeBin tb = (TreeBin)table[i]; - TreeNode p = tb.getTreeNode(hash, key); - V oldValue = p == null ? null : (V)p.entry.value; - V newValue = remappingFunction.apply(key, oldValue); - if (newValue != oldValue || (oldValue == null && p != null)) { - if (newValue == null) { - Entry pEntry = (Entry)p.entry; - modCount++; - size--; - tb.deleteTreeNode(p); - pEntry.recordRemoval(this); - if (tb.root == null || tb.first == null) { - // assert tb.root == null && tb.first == null : - // "TreeBin.first and root should both be null"; - // TreeBin is now empty, we should blank this bin - table[i] = null; - } - } else { - if (p != null) { // just update the value - Entry pEntry = (Entry)p.entry; - pEntry.value = newValue; - pEntry.recordAccess(this); - } else { // need to put new node - p = tb.putTreeNode(hash, key, newValue, null); - // assert p == null; // should have added a new node - modCount++; - size++; - if (size >= threshold) { - resize(2 * table.length); - } - } - } - } - return newValue; - } - - V newValue = remappingFunction.apply(key, null); - if (newValue != null) { - modCount++; - addEntry(hash, key, newValue, i, checkIfNeedTree); - } - - return newValue; - } - - @Override - public V merge(K key, V value, BiFunction remappingFunction) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); - } - if (key == null) { - V oldValue = nullKeyEntry == null ? null : nullKeyEntry.value; - V newValue = oldValue == null ? value : remappingFunction.apply(oldValue, value); - if (newValue != null) { - putForNullKey(newValue); - } else if (nullKeyEntry != null) { - removeNullKey(); - } - return newValue; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? - - if (table[i] instanceof Entry) { - int listSize = 0; - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - Entry next = (Entry)e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - V newValue = (oldValue == null) ? value : - remappingFunction.apply(oldValue, value); - if (newValue == null) { - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - } else { - e.value = newValue; - e.recordAccess(this); - } - return newValue; - } - prev = e; - e = next; - listSize++; - } - // Didn't find, so fall through and (maybe) call addEntry() to add - // the Entry and check for TreeBin conversion. - checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; - } else if (table[i] != null) { - TreeBin tb = (TreeBin)table[i]; - TreeNode p = tb.getTreeNode(hash, key); - V oldValue = p == null ? null : (V)p.entry.value; - V newValue = (oldValue == null) ? value : - remappingFunction.apply(oldValue, value); - if (newValue == null) { - if (p != null) { - Entry pEntry = (Entry)p.entry; - modCount++; - size--; - tb.deleteTreeNode(p); - pEntry.recordRemoval(this); - - if (tb.root == null || tb.first == null) { - // assert tb.root == null && tb.first == null : - // "TreeBin.first and root should both be null"; - // TreeBin is now empty, we should blank this bin - table[i] = null; - } - } - return null; - } else if (newValue != oldValue) { - if (p != null) { // just update the value - Entry pEntry = (Entry)p.entry; - pEntry.value = newValue; - pEntry.recordAccess(this); - } else { // need to put new node - p = tb.putTreeNode(hash, key, newValue, null); - // assert p == null; // should have added a new node - modCount++; - size++; - if (size >= threshold) { - resize(2 * table.length); - } - } - } - return newValue; - } - if (value != null) { - modCount++; - addEntry(hash, key, value, i, checkIfNeedTree); - } - return value; - } - - // end of optimized implementations of default methods in Map - - /** - * Removes and returns the entry associated with the specified key - * in the HashMap. Returns null if the HashMap contains no mapping - * for this key. - * - * We don't bother converting TreeBins back to Entry lists if the bin falls - * back below TREE_THRESHOLD, but we do clear bins when removing the last - * TreeNode in a TreeBin. - */ - final Entry removeEntryForKey(Object key) { - if (size == 0) { - return null; - } - if (key == null) { - if (nullKeyEntry != null) { - return removeNullKey(); - } - return null; - } - int hash = hash(key); - int i = indexFor(hash, table.length); - - if (table[i] instanceof Entry) { - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - @SuppressWarnings("unchecked") - Entry next = (Entry) e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - return e; - } - prev = e; - e = next; - } - } else if (table[i] != null) { - TreeBin tb = ((TreeBin) table[i]); - TreeNode p = tb.getTreeNode(hash, (K)key); - if (p != null) { - Entry pEntry = (Entry)p.entry; - // assert pEntry.key.equals(key); - modCount++; - size--; - tb.deleteTreeNode(p); - pEntry.recordRemoval(this); - if (tb.root == null || tb.first == null) { - // assert tb.root == null && tb.first == null : - // "TreeBin.first and root should both be null"; - // TreeBin is now empty, we should blank this bin - table[i] = null; - } - return pEntry; - } - } - return null; + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; } /** - * Special version of remove for EntrySet using {@code Map.Entry.equals()} - * for matching. + * Implements Map.remove and related methods + * + * @param hash hash for key + * @param key the key + * @param value the value to match if matchValue, else ignored + * @param matchValue if true only remove if value is equal + * @param movable if false do not move other nodes while removing + * @return the node, or null if none */ - final Entry removeMapping(Object o) { - if (size == 0 || !(o instanceof Map.Entry)) - return null; - - Map.Entry entry = (Map.Entry) o; - Object key = entry.getKey(); - - if (key == null) { - if (entry.equals(nullKeyEntry)) { - return removeNullKey(); - } - return null; - } - - int hash = hash(key); - int i = indexFor(hash, table.length); - - if (table[i] instanceof Entry) { - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - @SuppressWarnings("unchecked") - Entry next = (Entry)e.next; - if (e.hash == hash && e.equals(entry)) { - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - return e; + final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + else if ((e = p.next) != null) { + if (p instanceof TreeNode) + node = ((TreeNode)p).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); } - prev = e; - e = next; } - } else if (table[i] != null) { - TreeBin tb = ((TreeBin) table[i]); - TreeNode p = tb.getTreeNode(hash, (K)key); - if (p != null && p.entry.equals(entry)) { - @SuppressWarnings("unchecked") - Entry pEntry = (Entry)p.entry; - // assert pEntry.key.equals(key); - modCount++; - size--; - tb.deleteTreeNode(p); - pEntry.recordRemoval(this); - if (tb.root == null || tb.first == null) { - // assert tb.root == null && tb.first == null : - // "TreeBin.first and root should both be null"; - // TreeBin is now empty, we should blank this bin - table[i] = null; - } - return pEntry; + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) + ((TreeNode)node).removeTreeNode(this, tab, movable); + else if (node == p) + tab[index] = node.next; + else + p.next = node.next; + ++modCount; + --size; + afterNodeRemoval(node); + return node; } } return null; } - /* - * Remove the mapping for the null key, and update internal accounting - * (size, modcount, recordRemoval, etc). - * - * Assumes nullKeyEntry is non-null. - */ - private Entry removeNullKey() { - // assert nullKeyEntry != null; - Entry retVal = nullKeyEntry; - modCount++; - size--; - retVal.recordRemoval(this); - nullKeyEntry = null; - return retVal; - } - /** * Removes all of the mappings from this map. * The map will be empty after this call returns. */ public void clear() { + Node[] tab; modCount++; - if (nullKeyEntry != null) { - nullKeyEntry = null; + if ((tab = table) != null && size > 0) { + size = 0; + for (int i = 0; i < tab.length; ++i) + tab[i] = null; } - Arrays.fill(table, null); - size = 0; } /** @@ -2134,351 +873,19 @@ public class HashMap * specified value */ public boolean containsValue(Object value) { - if (value == null) { - return containsNullValue(); - } - Object[] tab = table; - for (int i = 0; i < tab.length; i++) { - if (tab[i] instanceof Entry) { - Entry e = (Entry)tab[i]; - for (; e != null; e = (Entry)e.next) { - if (value.equals(e.value)) { + Node[] tab; V v; + if ((tab = table) != null && size > 0) { + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) { + if ((v = e.value) == value || + (value != null && value.equals(v))) return true; - } - } - } else if (tab[i] != null) { - TreeBin e = (TreeBin)tab[i]; - TreeNode p = e.first; - for (; p != null; p = (TreeNode) p.entry.next) { - if (value == p.entry.value || value.equals(p.entry.value)) { - return true; - } } } } - // Didn't find value in table - could be in nullKeyEntry - return (nullKeyEntry != null && (value == nullKeyEntry.value || - value.equals(nullKeyEntry.value))); + return false; } - /** - * Special-case code for containsValue with null argument - */ - private boolean containsNullValue() { - Object[] tab = table; - for (int i = 0; i < tab.length; i++) { - if (tab[i] instanceof Entry) { - Entry e = (Entry)tab[i]; - for (; e != null; e = (Entry)e.next) { - if (e.value == null) { - return true; - } - } - } else if (tab[i] != null) { - TreeBin e = (TreeBin)tab[i]; - TreeNode p = e.first; - for (; p != null; p = (TreeNode) p.entry.next) { - if (p.entry.value == null) { - return true; - } - } - } - } - // Didn't find value in table - could be in nullKeyEntry - return (nullKeyEntry != null && nullKeyEntry.value == null); - } - - /** - * Returns a shallow copy of this HashMap instance: the keys and - * values themselves are not cloned. - * - * @return a shallow copy of this map - */ - @SuppressWarnings("unchecked") - public Object clone() { - HashMap result = null; - try { - result = (HashMap)super.clone(); - } catch (CloneNotSupportedException e) { - // assert false; - } - if (result.table != EMPTY_TABLE) { - result.inflateTable(Math.min( - (int) Math.min( - size * Math.min(1 / loadFactor, 4.0f), - // we have limits... - HashMap.MAXIMUM_CAPACITY), - table.length)); - } - result.entrySet = null; - result.modCount = 0; - result.size = 0; - result.nullKeyEntry = null; - result.init(); - result.putAllForCreate(this); - - return result; - } - - static class Entry implements Map.Entry { - final K key; - V value; - Object next; // an Entry, or a TreeNode - final int hash; - - /** - * Creates new entry. - */ - Entry(int h, K k, V v, Object n) { - value = v; - next = n; - key = k; - hash = h; - } - - public final K getKey() { - return key; - } - - public final V getValue() { - return value; - } - - public final V setValue(V newValue) { - V oldValue = value; - value = newValue; - return oldValue; - } - - public final boolean equals(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - Object k1 = getKey(); - Object k2 = e.getKey(); - if (k1 == k2 || (k1 != null && k1.equals(k2))) { - Object v1 = getValue(); - Object v2 = e.getValue(); - if (v1 == v2 || (v1 != null && v1.equals(v2))) - return true; - } - return false; - } - - public final int hashCode() { - return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); - } - - public final String toString() { - return getKey() + "=" + getValue(); - } - - /** - * This method is invoked whenever the value in an entry is - * overwritten for a key that's already in the HashMap. - */ - void recordAccess(HashMap m) { - } - - /** - * This method is invoked whenever the entry is - * removed from the table. - */ - void recordRemoval(HashMap m) { - } - } - - void addEntry(int hash, K key, V value, int bucketIndex) { - addEntry(hash, key, value, bucketIndex, true); - } - - /** - * Adds a new entry with the specified key, value and hash code to - * the specified bucket. It is the responsibility of this - * method to resize the table if appropriate. The new entry is then - * created by calling createEntry(). - * - * Subclass overrides this to alter the behavior of put method. - * - * If checkIfNeedTree is false, it is known that this bucket will not need - * to be converted to a TreeBin, so don't bothering checking. - * - * Assumes key is not null. - */ - void addEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) { - // assert key != null; - if ((size >= threshold) && (null != table[bucketIndex])) { - resize(2 * table.length); - hash = hash(key); - bucketIndex = indexFor(hash, table.length); - } - createEntry(hash, key, value, bucketIndex, checkIfNeedTree); - } - - /** - * Called by addEntry(), and also used when creating entries - * as part of Map construction or "pseudo-construction" (cloning, - * deserialization). This version does not check for resizing of the table. - * - * This method is responsible for converting a bucket to a TreeBin once - * TREE_THRESHOLD is reached. However if checkIfNeedTree is false, it is known - * that this bucket will not need to be converted to a TreeBin, so don't - * bother checking. The new entry is constructed by calling newEntry(). - * - * Assumes key is not null. - * - * Note: buckets already converted to a TreeBin don't call this method, but - * instead call TreeBin.putTreeNode() to create new entries. - */ - void createEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) { - // assert key != null; - @SuppressWarnings("unchecked") - Entry e = (Entry)table[bucketIndex]; - table[bucketIndex] = newEntry(hash, key, value, e); - size++; - - if (checkIfNeedTree) { - int listSize = 0; - for (e = (Entry) table[bucketIndex]; e != null; e = (Entry)e.next) { - listSize++; - if (listSize >= TreeBin.TREE_THRESHOLD) { // Convert to TreeBin - if (comparableClassFor(key) != null) { - TreeBin t = new TreeBin(); - t.populate((Entry)table[bucketIndex]); - table[bucketIndex] = t; - } - break; - } - } - } - } - - /* - * Factory method to create a new Entry object. - */ - Entry newEntry(int hash, K key, V value, Object next) { - return new HashMap.Entry<>(hash, key, value, next); - } - - - private abstract class HashIterator implements Iterator { - Object next; // next entry to return, an Entry or a TreeNode - int expectedModCount; // For fast-fail - int index; // current slot - Object current; // current entry, an Entry or a TreeNode - - HashIterator() { - expectedModCount = modCount; - if (size > 0) { // advance to first entry - if (nullKeyEntry != null) { - // assert nullKeyEntry.next == null; - // This works with nextEntry(): nullKeyEntry isa Entry, and - // e.next will be null, so we'll hit the findNextBin() call. - next = nullKeyEntry; - } else { - findNextBin(); - } - } - } - - public final boolean hasNext() { - return next != null; - } - - @SuppressWarnings("unchecked") - final Entry nextEntry() { - if (modCount != expectedModCount) { - throw new ConcurrentModificationException(); - } - Object e = next; - Entry retVal; - - if (e == null) - throw new NoSuchElementException(); - - if (e instanceof TreeNode) { // TreeBin - retVal = (Entry)((TreeNode)e).entry; - next = retVal.next; - } else { - retVal = (Entry)e; - next = ((Entry)e).next; - } - - if (next == null) { // Move to next bin - findNextBin(); - } - current = e; - return retVal; - } - - public void remove() { - if (current == null) - throw new IllegalStateException(); - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - K k; - - if (current instanceof Entry) { - k = ((Entry)current).key; - } else { - k = ((Entry)((TreeNode)current).entry).key; - - } - current = null; - HashMap.this.removeEntryForKey(k); - expectedModCount = modCount; - } - - /* - * Set 'next' to the first entry of the next non-empty bin in the table - */ - private void findNextBin() { - // assert next == null; - Object[] t = table; - - while (index < t.length && (next = t[index++]) == null) - ; - if (next instanceof HashMap.TreeBin) { // Point to the first TreeNode - next = ((TreeBin) next).first; - // assert next != null; // There should be no empty TreeBins - } - } - } - - private final class ValueIterator extends HashIterator { - public V next() { - return nextEntry().value; - } - } - - private final class KeyIterator extends HashIterator { - public K next() { - return nextEntry().getKey(); - } - } - - private final class EntryIterator extends HashIterator> { - public Map.Entry next() { - return nextEntry(); - } - } - - // Subclass overrides these to alter behavior of views' iterator() method - Iterator newKeyIterator() { - return new KeyIterator(); - } - Iterator newValueIterator() { - return new ValueIterator(); - } - Iterator> newEntryIterator() { - return new EntryIterator(); - } - - - // Views - - private transient Set> entrySet = null; - /** * Returns a {@link Set} view of the keys contained in this map. * The set is backed by the map, so changes to the map are @@ -2491,35 +898,38 @@ public class HashMap * removeAll, retainAll, and clear * operations. It does not support the add or addAll * operations. + * + * @return a set view of the keys contained in this map */ public Set keySet() { - Set ks = keySet; - return (ks != null ? ks : (keySet = new KeySet())); + Set ks; + return (ks = keySet) == null ? (keySet = new KeySet()) : ks; } - private final class KeySet extends AbstractSet { - public Iterator iterator() { - return newKeyIterator(); + final class KeySet extends AbstractSet { + public final int size() { return size; } + public final void clear() { HashMap.this.clear(); } + public final Iterator iterator() { return new KeyIterator(); } + public final boolean contains(Object o) { return containsKey(o); } + public final boolean remove(Object key) { + return removeNode(hash(key), key, null, false, true) != null; } - public int size() { - return size; + public final Spliterator spliterator() { + return new KeySpliterator(HashMap.this, 0, -1, 0, 0); } - public boolean contains(Object o) { - return containsKey(o); - } - public boolean remove(Object o) { - return HashMap.this.removeEntryForKey(o) != null; - } - public void clear() { - HashMap.this.clear(); - } - - public Spliterator spliterator() { - if (HashMap.this.getClass() == HashMap.class) - return new KeySpliterator(HashMap.this, 0, -1, 0, 0); - else - return Spliterators.spliterator - (this, Spliterator.SIZED | Spliterator.DISTINCT); + public final void forEach(Consumer action) { + Node[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) + action.accept(e.key); + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } } } @@ -2535,32 +945,35 @@ public class HashMap * Collection.remove, removeAll, * retainAll and clear operations. It does not * support the add or addAll operations. + * + * @return a view of the values contained in this map */ public Collection values() { - Collection vs = values; - return (vs != null ? vs : (values = new Values())); + Collection vs; + return (vs = values) == null ? (values = new Values()) : vs; } - private final class Values extends AbstractCollection { - public Iterator iterator() { - return newValueIterator(); + final class Values extends AbstractCollection { + public final int size() { return size; } + public final void clear() { HashMap.this.clear(); } + public final Iterator iterator() { return new ValueIterator(); } + public final boolean contains(Object o) { return containsValue(o); } + public final Spliterator spliterator() { + return new ValueSpliterator(HashMap.this, 0, -1, 0, 0); } - public int size() { - return size; - } - public boolean contains(Object o) { - return containsValue(o); - } - public void clear() { - HashMap.this.clear(); - } - - public Spliterator spliterator() { - if (HashMap.this.getClass() == HashMap.class) - return new ValueSpliterator(HashMap.this, 0, -1, 0, 0); - else - return Spliterators.spliterator - (this, Spliterator.SIZED); + public final void forEach(Consumer action) { + Node[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) + action.accept(e.value); + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } } } @@ -2581,42 +994,324 @@ public class HashMap * @return a set view of the mappings contained in this map */ public Set> entrySet() { - return entrySet0(); + Set> es; + return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } - private Set> entrySet0() { - Set> es = entrySet; - return es != null ? es : (entrySet = new EntrySet()); - } - - private final class EntrySet extends AbstractSet> { - public Iterator> iterator() { - return newEntryIterator(); + final class EntrySet extends AbstractSet> { + public final int size() { return size; } + public final void clear() { HashMap.this.clear(); } + public final Iterator> iterator() { + return new EntryIterator(); } - public boolean contains(Object o) { + public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry) o; - Entry candidate = getEntry(e.getKey()); + Object key = e.getKey(); + Node candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } - public boolean remove(Object o) { - return removeMapping(o) != null; + public final boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + Object value = e.getValue(); + return removeNode(hash(key), key, value, true, true) != null; + } + return false; } - public int size() { - return size; + public final Spliterator> spliterator() { + return new EntrySpliterator(HashMap.this, 0, -1, 0, 0); } - public void clear() { - HashMap.this.clear(); + public final void forEach(Consumer> action) { + Node[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) + action.accept(e); + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } } + } - public Spliterator> spliterator() { - if (HashMap.this.getClass() == HashMap.class) - return new EntrySpliterator(HashMap.this, 0, -1, 0, 0); - else - return Spliterators.spliterator - (this, Spliterator.SIZED | Spliterator.DISTINCT); + // Overrides of JDK8 Map extension methods + + public V getOrDefault(Object key, V defaultValue) { + Node e; + return (e = getNode(hash(key), key)) == null ? defaultValue : e.value; + } + + public V putIfAbsent(K key, V value) { + return putVal(hash(key), key, value, true, true); + } + + public boolean remove(Object key, Object value) { + return removeNode(hash(key), key, value, true, true) != null; + } + + public boolean replace(K key, V oldValue, V newValue) { + Node e; V v; + if ((e = getNode(hash(key), key)) != null && + ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) { + e.value = newValue; + afterNodeAccess(e); + return true; } + return false; + } + + public V replace(K key, V value) { + Node e; + if ((e = getNode(hash(key), key)) != null) { + V oldValue = e.value; + e.value = value; + afterNodeAccess(e); + return oldValue; + } + return null; + } + + public V computeIfAbsent(K key, + Function mappingFunction) { + if (mappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + Node[] tab; Node first; int n, i; + int binCount = 0; + TreeNode t = null; + Node old = null; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if ((first = tab[i = (n - 1) & hash]) != null) { + if (first instanceof TreeNode) + old = (t = (TreeNode)first).getTreeNode(hash, key); + else { + Node e = first; K k; + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + old = e; + break; + } + ++binCount; + } while ((e = e.next) != null); + } + V oldValue; + if (old != null && (oldValue = old.value) != null) { + afterNodeAccess(old); + return oldValue; + } + } + V v = mappingFunction.apply(key); + if (old != null) { + old.value = v; + afterNodeAccess(old); + return v; + } + else if (v == null) + return null; + else if (t != null) + t.putTreeVal(this, tab, hash, key, v); + else { + tab[i] = newNode(hash, key, v, first); + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash); + } + ++modCount; + ++size; + afterNodeInsertion(true); + return v; + } + + public V computeIfPresent(K key, + BiFunction remappingFunction) { + Node e; V oldValue; + int hash = hash(key); + if ((e = getNode(hash, key)) != null && + (oldValue = e.value) != null) { + V v = remappingFunction.apply(key, oldValue); + if (v != null) { + e.value = v; + afterNodeAccess(e); + return v; + } + else + removeNode(hash, key, null, false, true); + } + return null; + } + + public V compute(K key, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + Node[] tab; Node first; int n, i; + int binCount = 0; + TreeNode t = null; + Node old = null; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if ((first = tab[i = (n - 1) & hash]) != null) { + if (first instanceof TreeNode) + old = (t = (TreeNode)first).getTreeNode(hash, key); + else { + Node e = first; K k; + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + old = e; + break; + } + ++binCount; + } while ((e = e.next) != null); + } + } + V oldValue = (old == null) ? null : old.value; + V v = remappingFunction.apply(key, oldValue); + if (old != null) { + if (v != null) { + old.value = v; + afterNodeAccess(old); + } + else + removeNode(hash, key, null, false, true); + } + else if (v != null) { + if (t != null) + t.putTreeVal(this, tab, hash, key, v); + else { + tab[i] = newNode(hash, key, v, first); + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash); + } + ++modCount; + ++size; + afterNodeInsertion(true); + } + return v; + } + + public V merge(K key, V value, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + Node[] tab; Node first; int n, i; + int binCount = 0; + TreeNode t = null; + Node old = null; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if ((first = tab[i = (n - 1) & hash]) != null) { + if (first instanceof TreeNode) + old = (t = (TreeNode)first).getTreeNode(hash, key); + else { + Node e = first; K k; + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + old = e; + break; + } + ++binCount; + } while ((e = e.next) != null); + } + } + if (old != null) { + V v = remappingFunction.apply(old.value, value); + if (v != null) { + old.value = v; + afterNodeAccess(old); + } + else + removeNode(hash, key, null, false, true); + return v; + } + if (value != null) { + if (t != null) + t.putTreeVal(this, tab, hash, key, value); + else { + tab[i] = newNode(hash, key, value, first); + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash); + } + ++modCount; + ++size; + afterNodeInsertion(true); + } + return value; + } + + public void forEach(BiConsumer action) { + Node[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) + action.accept(e.key, e.value); + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + public void replaceAll(BiFunction function) { + Node[] tab; + if (function == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) { + e.value = function.apply(e.key, e.value); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + /* ------------------------------------------------------------ */ + // Cloning and serialization + + /** + * Returns a shallow copy of this HashMap instance: the keys and + * values themselves are not cloned. + * + * @return a shallow copy of this map + */ + @SuppressWarnings("unchecked") + public Object clone() { + HashMap result; + try { + result = (HashMap)super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + result.reinitialize(); + result.putMapEntries(this, false); + return result; + } + + // These methods are also used when serializing HashSets + final float loadFactor() { return loadFactor; } + final int capacity() { + return (table != null) ? table.length : + (threshold > 0) ? threshold : + DEFAULT_INITIAL_CAPACITY; } /** @@ -2631,118 +1326,143 @@ public class HashMap * emitted in no particular order. */ private void writeObject(java.io.ObjectOutputStream s) - throws IOException - { + throws IOException { + int buckets = capacity(); // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); - - // Write out number of buckets - if (table==EMPTY_TABLE) { - s.writeInt(roundUpToPowerOf2(threshold)); - } else { - s.writeInt(table.length); - } - - // Write out size (number of Mappings) + s.writeInt(buckets); s.writeInt(size); - - // Write out keys and values (alternating) - if (size > 0) { - for(Map.Entry e : entrySet0()) { - s.writeObject(e.getKey()); - s.writeObject(e.getValue()); - } - } + internalWriteEntries(s); } - private static final long serialVersionUID = 362498820763181265L; - /** * Reconstitute the {@code HashMap} instance from a stream (i.e., * deserialize it). */ private void readObject(java.io.ObjectInputStream s) - throws IOException, ClassNotFoundException - { + throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); - if (loadFactor <= 0 || Float.isNaN(loadFactor)) { + reinitialize(); + if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException("Illegal load factor: " + - loadFactor); - } - - // set other fields that need values - if (Holder.USE_HASHSEED) { - int seed = ThreadLocalRandom.current().nextInt(); - Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, - (seed != 0) ? seed : 1); - } - table = EMPTY_TABLE; - - // Read in number of buckets - s.readInt(); // ignored. - - // Read number of mappings - int mappings = s.readInt(); + loadFactor); + s.readInt(); // Read and ignore number of buckets + int mappings = s.readInt(); // Read number of mappings (size) if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + - mappings); + mappings); + else if (mappings > 0) { // (if zero, use defaults) + // Size the table using given load factor only if within + // range of 0.25...4.0 + float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); + float fc = (float)mappings / lf + 1.0f; + int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? + DEFAULT_INITIAL_CAPACITY : + (fc >= MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : + tableSizeFor((int)fc)); + float ft = (float)cap * lf; + threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? + (int)ft : Integer.MAX_VALUE); + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] tab = (Node[])new Node[cap]; + table = tab; - // capacity chosen by number of mappings and desired load (if >= 0.25) - int capacity = (int) Math.min( - mappings * Math.min(1 / loadFactor, 4.0f), - // we have limits... - HashMap.MAXIMUM_CAPACITY); - - // allocate the bucket array; - if (mappings > 0) { - inflateTable(capacity); - } else { - threshold = capacity; - } - - init(); // Give subclass a chance to do its thing. - - // Read the keys and values, and put the mappings in the HashMap - for (int i=0; i next; // next entry to return + Node current; // current entry + int expectedModCount; // for fast-fail + int index; // current slot + + HashIterator() { + expectedModCount = modCount; + Node[] t = table; + current = next = null; + index = 0; + if (t != null && size > 0) { // advance to first entry + do {} while (index < t.length && (next = t[index++]) == null); + } + } + + public final boolean hasNext() { + return next != null; + } + + final Node nextNode() { + Node[] t; + Node e = next; + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (e == null) + throw new NoSuchElementException(); + if ((next = (current = e).next) == null && (t = table) != null) { + do {} while (index < t.length && (next = t[index++]) == null); + } + return e; + } + + public final void remove() { + Node p = current; + if (p == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + current = null; + K key = p.key; + removeNode(hash(key), key, null, false, false); + expectedModCount = modCount; + } + } + + final class KeyIterator extends HashIterator + implements Iterator { + public final K next() { return nextNode().key; } + } + + final class ValueIterator extends HashIterator + implements Iterator { + public final V next() { return nextNode().value; } + } + + final class EntryIterator extends HashIterator + implements Iterator> { + public final Map.Entry next() { return nextNode(); } + } + + /* ------------------------------------------------------------ */ + // spliterators - /** - * Standin until HM overhaul; based loosely on Weak and Identity HM. - */ static class HashMapSpliterator { final HashMap map; - Object current; // current node, can be Entry or TreeNode + Node current; // current node int index; // current index, modified on advance/split int fence; // one past last index int est; // size estimate int expectedModCount; // for comodification checks - boolean acceptedNull; // Have we accepted the null key? - // Without this, we can't distinguish - // between being at the very beginning (and - // needing to accept null), or being at the - // end of the list in bin 0. In both cases, - // current == null && index == 0. HashMapSpliterator(HashMap m, int origin, - int fence, int est, - int expectedModCount) { + int fence, int est, + int expectedModCount) { this.map = m; this.index = origin; this.fence = fence; this.est = est; this.expectedModCount = expectedModCount; - this.acceptedNull = false; } final int getFence() { // initialize fence and size on first use @@ -2751,7 +1471,8 @@ public class HashMap HashMap m = map; est = m.size; expectedModCount = m.modCount; - hi = fence = m.table.length; + Node[] tab = m.table; + hi = fence = (tab == null) ? 0 : tab.length; } return hi; } @@ -2772,56 +1493,33 @@ public class HashMap public KeySpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; - if (lo >= mid || current != null) { - return null; - } else { - KeySpliterator retVal = new KeySpliterator(map, lo, - index = mid, est >>>= 1, expectedModCount); - // Only 'this' Spliterator chould check for null. - retVal.acceptedNull = true; - return retVal; - } + return (lo >= mid || current != null) ? null : + new KeySpliterator(map, lo, index = mid, est >>>= 1, + expectedModCount); } - @SuppressWarnings("unchecked") public void forEachRemaining(Consumer action) { int i, hi, mc; if (action == null) throw new NullPointerException(); HashMap m = map; - Object[] tab = m.table; + Node[] tab = m.table; if ((hi = fence) < 0) { mc = expectedModCount = m.modCount; - hi = fence = tab.length; + hi = fence = (tab == null) ? 0 : tab.length; } else mc = expectedModCount; - - if (!acceptedNull) { - acceptedNull = true; - if (m.nullKeyEntry != null) { - action.accept(m.nullKeyEntry.key); - } - } - if (tab.length >= hi && (i = index) >= 0 && - (i < (index = hi) || current != null)) { - Object p = current; + if (tab != null && tab.length >= hi && + (i = index) >= 0 && (i < (index = hi) || current != null)) { + Node p = current; current = null; do { - if (p == null) { + if (p == null) p = tab[i++]; - if (p instanceof HashMap.TreeBin) { - p = ((HashMap.TreeBin)p).first; - } - } else { - HashMap.Entry entry; - if (p instanceof HashMap.Entry) { - entry = (HashMap.Entry)p; - } else { - entry = (HashMap.Entry)((TreeNode)p).entry; - } - action.accept(entry.key); - p = entry.next; + else { + action.accept(p.key); + p = p.next; } } while (p != null || i < hi); if (m.modCount != mc) @@ -2829,39 +1527,18 @@ public class HashMap } } - @SuppressWarnings("unchecked") public boolean tryAdvance(Consumer action) { int hi; if (action == null) throw new NullPointerException(); - Object[] tab = map.table; - hi = getFence(); - - if (!acceptedNull) { - acceptedNull = true; - if (map.nullKeyEntry != null) { - action.accept(map.nullKeyEntry.key); - if (map.modCount != expectedModCount) - throw new ConcurrentModificationException(); - return true; - } - } - if (tab.length >= hi && index >= 0) { + Node[] tab = map.table; + if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { while (current != null || index < hi) { - if (current == null) { + if (current == null) current = tab[index++]; - if (current instanceof HashMap.TreeBin) { - current = ((HashMap.TreeBin)current).first; - } - } else { - HashMap.Entry entry; - if (current instanceof HashMap.Entry) { - entry = (HashMap.Entry)current; - } else { - entry = (HashMap.Entry)((TreeNode)current).entry; - } - K k = entry.key; - current = entry.next; + else { + K k = current.key; + current = current.next; action.accept(k); if (map.modCount != expectedModCount) throw new ConcurrentModificationException(); @@ -2888,56 +1565,33 @@ public class HashMap public ValueSpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; - if (lo >= mid || current != null) { - return null; - } else { - ValueSpliterator retVal = new ValueSpliterator(map, - lo, index = mid, est >>>= 1, expectedModCount); - // Only 'this' Spliterator chould check for null. - retVal.acceptedNull = true; - return retVal; - } + return (lo >= mid || current != null) ? null : + new ValueSpliterator(map, lo, index = mid, est >>>= 1, + expectedModCount); } - @SuppressWarnings("unchecked") public void forEachRemaining(Consumer action) { int i, hi, mc; if (action == null) throw new NullPointerException(); HashMap m = map; - Object[] tab = m.table; + Node[] tab = m.table; if ((hi = fence) < 0) { mc = expectedModCount = m.modCount; - hi = fence = tab.length; + hi = fence = (tab == null) ? 0 : tab.length; } else mc = expectedModCount; - - if (!acceptedNull) { - acceptedNull = true; - if (m.nullKeyEntry != null) { - action.accept(m.nullKeyEntry.value); - } - } - if (tab.length >= hi && (i = index) >= 0 && - (i < (index = hi) || current != null)) { - Object p = current; + if (tab != null && tab.length >= hi && + (i = index) >= 0 && (i < (index = hi) || current != null)) { + Node p = current; current = null; do { - if (p == null) { + if (p == null) p = tab[i++]; - if (p instanceof HashMap.TreeBin) { - p = ((HashMap.TreeBin)p).first; - } - } else { - HashMap.Entry entry; - if (p instanceof HashMap.Entry) { - entry = (HashMap.Entry)p; - } else { - entry = (HashMap.Entry)((TreeNode)p).entry; - } - action.accept(entry.value); - p = entry.next; + else { + action.accept(p.value); + p = p.next; } } while (p != null || i < hi); if (m.modCount != mc) @@ -2945,39 +1599,18 @@ public class HashMap } } - @SuppressWarnings("unchecked") public boolean tryAdvance(Consumer action) { int hi; if (action == null) throw new NullPointerException(); - Object[] tab = map.table; - hi = getFence(); - - if (!acceptedNull) { - acceptedNull = true; - if (map.nullKeyEntry != null) { - action.accept(map.nullKeyEntry.value); - if (map.modCount != expectedModCount) - throw new ConcurrentModificationException(); - return true; - } - } - if (tab.length >= hi && index >= 0) { + Node[] tab = map.table; + if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { while (current != null || index < hi) { - if (current == null) { + if (current == null) current = tab[index++]; - if (current instanceof HashMap.TreeBin) { - current = ((HashMap.TreeBin)current).first; - } - } else { - HashMap.Entry entry; - if (current instanceof HashMap.Entry) { - entry = (Entry)current; - } else { - entry = (Entry)((TreeNode)current).entry; - } - V v = entry.value; - current = entry.next; + else { + V v = current.value; + current = current.next; action.accept(v); if (map.modCount != expectedModCount) throw new ConcurrentModificationException(); @@ -3003,57 +1636,33 @@ public class HashMap public EntrySpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; - if (lo >= mid || current != null) { - return null; - } else { - EntrySpliterator retVal = new EntrySpliterator(map, - lo, index = mid, est >>>= 1, expectedModCount); - // Only 'this' Spliterator chould check for null. - retVal.acceptedNull = true; - return retVal; - } + return (lo >= mid || current != null) ? null : + new EntrySpliterator(map, lo, index = mid, est >>>= 1, + expectedModCount); } - @SuppressWarnings("unchecked") public void forEachRemaining(Consumer> action) { int i, hi, mc; if (action == null) throw new NullPointerException(); HashMap m = map; - Object[] tab = m.table; + Node[] tab = m.table; if ((hi = fence) < 0) { mc = expectedModCount = m.modCount; - hi = fence = tab.length; + hi = fence = (tab == null) ? 0 : tab.length; } else mc = expectedModCount; - - if (!acceptedNull) { - acceptedNull = true; - if (m.nullKeyEntry != null) { - action.accept(m.nullKeyEntry); - } - } - if (tab.length >= hi && (i = index) >= 0 && - (i < (index = hi) || current != null)) { - Object p = current; + if (tab != null && tab.length >= hi && + (i = index) >= 0 && (i < (index = hi) || current != null)) { + Node p = current; current = null; do { - if (p == null) { + if (p == null) p = tab[i++]; - if (p instanceof HashMap.TreeBin) { - p = ((HashMap.TreeBin)p).first; - } - } else { - HashMap.Entry entry; - if (p instanceof HashMap.Entry) { - entry = (HashMap.Entry)p; - } else { - entry = (HashMap.Entry)((TreeNode)p).entry; - } - action.accept(entry); - p = entry.next; - + else { + action.accept(p); + p = p.next; } } while (p != null || i < hi); if (m.modCount != mc) @@ -3061,38 +1670,18 @@ public class HashMap } } - @SuppressWarnings("unchecked") public boolean tryAdvance(Consumer> action) { int hi; if (action == null) throw new NullPointerException(); - Object[] tab = map.table; - hi = getFence(); - - if (!acceptedNull) { - acceptedNull = true; - if (map.nullKeyEntry != null) { - action.accept(map.nullKeyEntry); - if (map.modCount != expectedModCount) - throw new ConcurrentModificationException(); - return true; - } - } - if (tab.length >= hi && index >= 0) { + Node[] tab = map.table; + if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { while (current != null || index < hi) { - if (current == null) { + if (current == null) current = tab[index++]; - if (current instanceof HashMap.TreeBin) { - current = ((HashMap.TreeBin)current).first; - } - } else { - HashMap.Entry e; - if (current instanceof HashMap.Entry) { - e = (Entry)current; - } else { - e = (Entry)((TreeNode)current).entry; - } - current = e.next; + else { + Node e = current; + current = current.next; action.accept(e); if (map.modCount != expectedModCount) throw new ConcurrentModificationException(); @@ -3108,4 +1697,664 @@ public class HashMap Spliterator.DISTINCT; } } + + /* ------------------------------------------------------------ */ + // LinkedHashMap support + + + /* + * The following package-protected methods are designed to be + * overridden by LinkedHashMap, but not by any other subclass. + * Nearly all other internal methods are also package-protected + * but are declared final, so can be used by LinkedHashMap, view + * classes, and HashSet. + */ + + // Create a regular (non-tree) node + Node newNode(int hash, K key, V value, Node next) { + return new Node(hash, key, value, next); + } + + // For conversion from TreeNodes to plain nodes + Node replacementNode(Node p, Node next) { + return new Node(p.hash, p.key, p.value, next); + } + + // Create a tree bin node + TreeNode newTreeNode(int hash, K key, V value, Node next) { + return new TreeNode(hash, key, value, next); + } + + // For treeifyBin + TreeNode replacementTreeNode(Node p, Node next) { + return new TreeNode(p.hash, p.key, p.value, next); + } + + /** + * Reset to initial default state. Called by clone and readObject. + */ + void reinitialize() { + table = null; + entrySet = null; + keySet = null; + values = null; + modCount = 0; + threshold = 0; + size = 0; + } + + // Callbacks to allow LinkedHashMap post-actions + void afterNodeAccess(Node p) { } + void afterNodeInsertion(boolean evict) { } + void afterNodeRemoval(Node p) { } + + // Called only from writeObject, to ensure compatible ordering. + void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { + Node[] tab; + if (size > 0 && (tab = table) != null) { + for (int i = 0; i < tab.length; ++i) { + for (Node e = tab[i]; e != null; e = e.next) { + s.writeObject(e.key); + s.writeObject(e.value); + } + } + } + } + + /* ------------------------------------------------------------ */ + // Tree bins + + /** + * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn + * extends Node) so can be used as extension of either regular or + * linked node. + */ + static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + TreeNode(int hash, K key, V val, Node next) { + super(hash, key, val, next); + } + + /** + * Returns root of tree containing this node. + */ + final TreeNode root() { + for (TreeNode r = this, p;;) { + if ((p = r.parent) == null) + return r; + r = p; + } + } + + /** + * Ensures that the given root is the first node of its bin. + */ + static void moveRootToFront(Node[] tab, TreeNode root) { + int n; + if (root != null && tab != null && (n = tab.length) > 0) { + int index = (n - 1) & root.hash; + TreeNode first = (TreeNode)tab[index]; + if (root != first) { + Node rn; + tab[index] = root; + TreeNode rp = root.prev; + if ((rn = root.next) != null) + ((TreeNode)rn).prev = rp; + if (rp != null) + rp.next = rn; + if (first != null) + first.prev = root; + root.next = first; + root.prev = null; + } + assert checkInvariants(root); + } + } + + /** + * Finds the node starting at root p with the given hash and key. + * The kc argument caches comparableClassFor(key) upon first use + * comparing keys. + */ + final TreeNode find(int h, Object k, Class kc) { + TreeNode p = this; + do { + int ph, dir; K pk; + TreeNode pl = p.left, pr = p.right, q; + if ((ph = p.hash) > h) + p = pl; + else if (ph < h) + p = pr; + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + return p; + else if (pl == null) + p = pr; + else if (pr == null) + p = pl; + else if ((kc != null || + (kc = comparableClassFor(k)) != null) && + (dir = compareComparables(kc, k, pk)) != 0) + p = (dir < 0) ? pl : pr; + else if ((q = pr.find(h, k, kc)) != null) + return q; + else + p = pl; + } while (p != null); + return null; + } + + /** + * Calls find for root node. + */ + final TreeNode getTreeNode(int h, Object k) { + return ((parent != null) ? root() : this).find(h, k, null); + } + + /** + * Tie-breaking utility for ordering insertions when equal + * hashCodes and non-comparable. We don't require a total + * order, just a consistent insertion rule to maintain + * equivalence across rebalancings. Tie-breaking further than + * necessary simplifies testing a bit. + */ + static int tieBreakOrder(Object a, Object b) { + int d; + if (a == null || b == null || + (d = a.getClass().getName(). + compareTo(b.getClass().getName())) == 0) + d = (System.identityHashCode(a) <= System.identityHashCode(b) ? + -1 : 1); + return d; + } + + /** + * Forms tree of the nodes linked from this node. + * @return root of tree + */ + final void treeify(Node[] tab) { + TreeNode root = null; + for (TreeNode x = this, next; x != null; x = next) { + next = (TreeNode)x.next; + x.left = x.right = null; + if (root == null) { + x.parent = null; + x.red = false; + root = x; + } + else { + K k = x.key; + int h = x.hash; + Class kc = null; + for (TreeNode p = root;;) { + int dir, ph; + K pk = p.key; + if ((ph = p.hash) > h) + dir = -1; + else if (ph < h) + dir = 1; + else if ((kc == null && + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) + dir = tieBreakOrder(k, pk); + + TreeNode xp = p; + if ((p = (dir <= 0) ? p.left : p.right) == null) { + x.parent = xp; + if (dir <= 0) + xp.left = x; + else + xp.right = x; + root = balanceInsertion(root, x); + break; + } + } + } + } + moveRootToFront(tab, root); + } + + /** + * Returns a list of non-TreeNodes replacing those linked from + * this node. + */ + final Node untreeify(HashMap map) { + Node hd = null, tl = null; + for (Node q = this; q != null; q = q.next) { + Node p = map.replacementNode(q, null); + if (tl == null) + hd = p; + else + tl.next = p; + tl = p; + } + return hd; + } + + /** + * Tree version of putVal. + */ + final TreeNode putTreeVal(HashMap map, Node[] tab, + int h, K k, V v) { + Class kc = null; + boolean searched = false; + TreeNode root = (parent != null) ? root() : this; + for (TreeNode p = root;;) { + int dir, ph; K pk; + if ((ph = p.hash) > h) + dir = -1; + else if (ph < h) + dir = 1; + else if ((pk = p.key) == k || (pk != null && k.equals(pk))) + return p; + else if ((kc == null && + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) { + if (!searched) { + TreeNode q, ch; + searched = true; + if (((ch = p.left) != null && + (q = ch.find(h, k, kc)) != null) || + ((ch = p.right) != null && + (q = ch.find(h, k, kc)) != null)) + return q; + } + dir = tieBreakOrder(k, pk); + } + + TreeNode xp = p; + if ((p = (dir <= 0) ? p.left : p.right) == null) { + Node xpn = xp.next; + TreeNode x = map.newTreeNode(h, k, v, xpn); + if (dir <= 0) + xp.left = x; + else + xp.right = x; + xp.next = x; + x.parent = x.prev = xp; + if (xpn != null) + ((TreeNode)xpn).prev = x; + moveRootToFront(tab, balanceInsertion(root, x)); + return null; + } + } + } + + /** + * Removes the given node, that must be present before this call. + * This is messier than typical red-black deletion code because we + * cannot swap the contents of an interior node with a leaf + * successor that is pinned by "next" pointers that are accessible + * independently during traversal. So instead we swap the tree + * linkages. If the current tree appears to have too few nodes, + * the bin is converted back to a plain bin. (The test triggers + * somewhere between 2 and 6 nodes, depending on tree structure). + */ + final void removeTreeNode(HashMap map, Node[] tab, + boolean movable) { + int n; + if (tab == null || (n = tab.length) == 0) + return; + int index = (n - 1) & hash; + TreeNode first = (TreeNode)tab[index], root = first, rl; + TreeNode succ = (TreeNode)next, pred = prev; + if (pred == null) + tab[index] = first = succ; + else + pred.next = succ; + if (succ != null) + succ.prev = pred; + if (first == null) + return; + if (root.parent != null) + root = root.root(); + if (root == null || root.right == null || + (rl = root.left) == null || rl.left == null) { + tab[index] = first.untreeify(map); // too small + return; + } + TreeNode p = this, pl = left, pr = right, replacement; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + if (sr != null) + replacement = sr; + else + replacement = p; + } + else if (pl != null) + replacement = pl; + else if (pr != null) + replacement = pr; + else + replacement = p; + if (replacement != p) { + TreeNode pp = replacement.parent = p.parent; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + + TreeNode r = p.red ? root : balanceDeletion(root, replacement); + + if (replacement == p) { // detach + TreeNode pp = p.parent; + p.parent = null; + if (pp != null) { + if (p == pp.left) + pp.left = null; + else if (p == pp.right) + pp.right = null; + } + } + if (movable) + moveRootToFront(tab, r); + } + + /** + * Splits nodes in a tree bin into lower and upper tree bins, + * or untreeifies if now too small. Called only from resize; + * see above discussion about split bits and indices. + * + * @param map the map + * @param tab the table for recording bin heads + * @param index the index of the table being split + * @param bit the bit of hash to split on + */ + final void split(HashMap map, Node[] tab, int index, int bit) { + TreeNode b = this; + // Relink into lo and hi lists, preserving order + TreeNode loHead = null, loTail = null; + TreeNode hiHead = null, hiTail = null; + int lc = 0, hc = 0; + for (TreeNode e = b, next; e != null; e = next) { + next = (TreeNode)e.next; + e.next = null; + if ((e.hash & bit) == 0) { + if ((e.prev = loTail) == null) + loHead = e; + else + loTail.next = e; + loTail = e; + ++lc; + } + else { + if ((e.prev = hiTail) == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + ++hc; + } + } + + if (loHead != null) { + if (lc <= UNTREEIFY_THRESHOLD) + tab[index] = loHead.untreeify(map); + else { + tab[index] = loHead; + if (hiHead != null) // (else is already treeified) + loHead.treeify(tab); + } + } + if (hiHead != null) { + if (hc <= UNTREEIFY_THRESHOLD) + tab[index + bit] = hiHead.untreeify(map); + else { + tab[index + bit] = hiHead; + if (loHead != null) + hiHead.treeify(tab); + } + } + } + + /* ------------------------------------------------------------ */ + // Red-black tree methods, all adapted from CLR + + static TreeNode rotateLeft(TreeNode root, + TreeNode p) { + TreeNode r, pp, rl; + if (p != null && (r = p.right) != null) { + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + (root = r).red = false; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + return root; + } + + static TreeNode rotateRight(TreeNode root, + TreeNode p) { + TreeNode l, pp, lr; + if (p != null && (l = p.left) != null) { + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + (root = l).red = false; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + return root; + } + + static TreeNode balanceInsertion(TreeNode root, + TreeNode x) { + x.red = true; + for (TreeNode xp, xpp, xppl, xppr;;) { + if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (!xp.red || (xpp = xp.parent) == null) + return root; + if (xp == (xppl = xpp.left)) { + if ((xppr = xpp.right) != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + root = rotateLeft(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateRight(root, xpp); + } + } + } + } + else { + if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + root = rotateRight(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateLeft(root, xpp); + } + } + } + } + } + } + + static TreeNode balanceDeletion(TreeNode root, + TreeNode x) { + for (TreeNode xp, xpl, xpr;;) { + if (x == null || x == root) + return root; + else if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (x.red) { + x.red = false; + return root; + } + else if ((xpl = xp.left) == x) { + if ((xpr = xp.right) != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = rotateLeft(root, xp); + xpr = (xp = x.parent) == null ? null : xp.right; + } + if (xpr == null) + x = xp; + else { + TreeNode sl = xpr.left, sr = xpr.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + xpr.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + xpr.red = true; + root = rotateRight(root, xpr); + xpr = (xp = x.parent) == null ? + null : xp.right; + } + if (xpr != null) { + xpr.red = (xp == null) ? false : xp.red; + if ((sr = xpr.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateLeft(root, xp); + } + x = root; + } + } + } + else { // symmetric + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = rotateRight(root, xp); + xpl = (xp = x.parent) == null ? null : xp.left; + } + if (xpl == null) + x = xp; + else { + TreeNode sl = xpl.left, sr = xpl.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + xpl.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + xpl.red = true; + root = rotateLeft(root, xpl); + xpl = (xp = x.parent) == null ? + null : xp.left; + } + if (xpl != null) { + xpl.red = (xp == null) ? false : xp.red; + if ((sl = xpl.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateRight(root, xp); + } + x = root; + } + } + } + } + } + + /** + * Recursive invariant check + */ + static boolean checkInvariants(TreeNode t) { + TreeNode tp = t.parent, tl = t.left, tr = t.right, + tb = t.prev, tn = (TreeNode)t.next; + if (tb != null && tb.next != t) + return false; + if (tn != null && tn.prev != t) + return false; + if (tp != null && t != tp.left && t != tp.right) + return false; + if (tl != null && (tl.parent != t || tl.hash > t.hash)) + return false; + if (tr != null && (tr.parent != t || tr.hash < t.hash)) + return false; + if (t.red && tl != null && tl.red && tr != null && tr.red) + return false; + if (tl != null && !checkInvariants(tl)) + return false; + if (tr != null && !checkInvariants(tr)) + return false; + return true; + } + } + } diff --git a/jdk/src/share/classes/java/util/LinkedHashMap.java b/jdk/src/share/classes/java/util/LinkedHashMap.java index feb1005b2aa..0e4fc73f0df 100644 --- a/jdk/src/share/classes/java/util/LinkedHashMap.java +++ b/jdk/src/share/classes/java/util/LinkedHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,9 +24,12 @@ */ package java.util; -import java.io.*; + +import java.util.function.Consumer; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.io.Serializable; +import java.io.IOException; /** *

Hash table and linked list implementation of the Map interface, @@ -57,9 +60,9 @@ import java.util.function.BiFunction; * order they were presented.) * *

A special {@link #LinkedHashMap(int,float,boolean) constructor} is - * provided to create a LinkedHashMap whose order of iteration is the - * order in which its entries were last accessed, from least-recently accessed - * to most-recently (access-order). This kind of map is well-suited to + * provided to create a linked hash map whose order of iteration is the order + * in which its entries were last accessed, from least-recently accessed to + * most-recently (access-order). This kind of map is well-suited to * building LRU caches. Invoking the put or get method * results in an access to the corresponding entry (assuming it exists after * the invocation completes). The putAll method generates one entry @@ -155,18 +158,53 @@ import java.util.function.BiFunction; * @see Hashtable * @since 1.4 */ - public class LinkedHashMap extends HashMap implements Map { + /* + * Implementation note. A previous version of this class was + * internally structured a little differently. Because superclass + * HashMap now uses trees for some of its nodes, class + * LinkedHashMap.Entry is now treated as intermediary node class + * that can also be converted to tree form. The name of this + * class, LinkedHashMap.Entry, is confusing in several ways in its + * current context, but cannot be changed. Otherwise, even though + * it is not exported outside this package, some existing source + * code is known to have relied on a symbol resolution corner case + * rule in calls to removeEldestEntry that suppressed compilation + * errors due to ambiguous usages. So, we keep the name to + * preserve unmodified compilability. + * + * The changes in node classes also require using two fields + * (head, tail) rather than a pointer to a header node to maintain + * the doubly-linked before/after list. This class also + * previously used a different style of callback methods upon + * access, insertion, and removal. + */ + + /** + * HashMap.Node subclass for normal LinkedHashMap entries. + */ + static class Entry extends HashMap.Node { + Entry before, after; + Entry(int hash, K key, V value, Node next) { + super(hash, key, value, next); + } + } + private static final long serialVersionUID = 3801124242820219131L; /** - * The head of the doubly linked list. + * The head (eldest) of the doubly linked list. */ - private transient Entry header; + transient LinkedHashMap.Entry head; + + /** + * The tail (youngest) of the doubly linked list. + */ + transient LinkedHashMap.Entry tail; /** * The iteration ordering method for this linked hash map: true @@ -174,7 +212,125 @@ public class LinkedHashMap * * @serial */ - private final boolean accessOrder; + final boolean accessOrder; + + // internal utilities + + // link at the end of list + private void linkNodeLast(LinkedHashMap.Entry p) { + LinkedHashMap.Entry last = tail; + tail = p; + if (last == null) + head = p; + else { + p.before = last; + last.after = p; + } + } + + // apply src's links to dst + private void transferLinks(LinkedHashMap.Entry src, + LinkedHashMap.Entry dst) { + LinkedHashMap.Entry b = dst.before = src.before; + LinkedHashMap.Entry a = dst.after = src.after; + if (b == null) + head = dst; + else + b.after = dst; + if (a == null) + tail = dst; + else + a.before = dst; + } + + // overrides of HashMap hook methods + + void reinitialize() { + super.reinitialize(); + head = tail = null; + } + + Node newNode(int hash, K key, V value, Node e) { + LinkedHashMap.Entry p = + new LinkedHashMap.Entry(hash, key, value, e); + linkNodeLast(p); + return p; + } + + Node replacementNode(Node p, Node next) { + LinkedHashMap.Entry q = (LinkedHashMap.Entry)p; + LinkedHashMap.Entry t = + new LinkedHashMap.Entry(q.hash, q.key, q.value, next); + transferLinks(q, t); + return t; + } + + TreeNode newTreeNode(int hash, K key, V value, Node next) { + TreeNode p = new TreeNode(hash, key, value, next); + linkNodeLast(p); + return p; + } + + TreeNode replacementTreeNode(Node p, Node next) { + LinkedHashMap.Entry q = (LinkedHashMap.Entry)p; + TreeNode t = new TreeNode(q.hash, q.key, q.value, next); + transferLinks(q, t); + return t; + } + + void afterNodeRemoval(Node e) { // unlink + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.before = p.after = null; + if (b == null) + head = a; + else + b.after = a; + if (a == null) + tail = b; + else + a.before = b; + } + + void afterNodeInsertion(boolean evict) { // possibly remove eldest + LinkedHashMap.Entry first; + if (evict && (first = head) != null && removeEldestEntry(first)) { + K key = first.key; + removeNode(hash(key), key, null, false, true); + } + } + + void afterNodeAccess(Node e) { // move node to last + LinkedHashMap.Entry last; + if (accessOrder && (last = tail) != e) { + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.after = null; + if (b == null) + head = a; + else + b.after = a; + if (a != null) + a.before = b; + else + last = b; + if (last == null) + head = p; + else { + p.before = last; + last.after = p; + } + tail = p; + ++modCount; + } + } + + void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) { + s.writeObject(e.key); + s.writeObject(e.value); + } + } /** * Constructs an empty insertion-ordered LinkedHashMap instance @@ -221,8 +377,9 @@ public class LinkedHashMap * @throws NullPointerException if the specified map is null */ public LinkedHashMap(Map m) { - super(m); + super(); accessOrder = false; + putMapEntries(m, false); } /** @@ -243,16 +400,6 @@ public class LinkedHashMap this.accessOrder = accessOrder; } - /** - * Called by superclass constructors and pseudoconstructors (clone, - * readObject) before any entries are inserted into the map. Initializes - * the chain. - */ - @Override - void init() { - header = new Entry<>(-1, null, null, null); - header.before = header.after = header; - } /** * Returns true if this map maps one or more keys to the @@ -263,15 +410,10 @@ public class LinkedHashMap * specified value */ public boolean containsValue(Object value) { - // Overridden to take advantage of faster iterator - if (value==null) { - for (Entry e = header.after; e != header; e = e.after) - if (e.value==null) - return true; - } else { - for (Entry e = header.after; e != header; e = e.after) - if (value.equals(e.value)) - return true; + for (LinkedHashMap.Entry e = head; e != null; e = e.after) { + V v = e.value; + if (v == value || (value != null && value.equals(v))) + return true; } return false; } @@ -292,10 +434,11 @@ public class LinkedHashMap * distinguish these two cases. */ public V get(Object key) { - Entry e = (Entry)getEntry(key); - if (e == null) + Node e; + if ((e = getNode(hash(key), key)) == null) return null; - e.recordAccess(this); + if (accessOrder) + afterNodeAccess(e); return e.value; } @@ -305,163 +448,7 @@ public class LinkedHashMap */ public void clear() { super.clear(); - header.before = header.after = header; - } - - @Override - public void forEach(BiConsumer action) { - Objects.requireNonNull(action); - int expectedModCount = modCount; - for (Entry entry = header.after; entry != header; entry = entry.after) { - action.accept(entry.key, entry.value); - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - } - - @Override - public void replaceAll(BiFunction function) { - Objects.requireNonNull(function); - int expectedModCount = modCount; - for (Entry entry = header.after; entry != header; entry = entry.after) { - entry.value = function.apply(entry.key, entry.value); - - if (expectedModCount != modCount) { - throw new ConcurrentModificationException(); - } - } - } - - /** - * LinkedHashMap entry. - */ - private static class Entry extends HashMap.Entry { - // These fields comprise the doubly linked list used for iteration. - Entry before, after; - - Entry(int hash, K key, V value, Object next) { - super(hash, key, value, next); - } - - /** - * Removes this entry from the linked list. - */ - private void remove() { - before.after = after; - after.before = before; - } - - /** - * Inserts this entry before the specified existing entry in the list. - */ - private void addBefore(Entry existingEntry) { - after = existingEntry; - before = existingEntry.before; - before.after = this; - after.before = this; - } - - /** - * This method is invoked by the superclass whenever the value - * of a pre-existing entry is read by Map.get or modified by Map.put. - * If the enclosing Map is access-ordered, it moves the entry - * to the end of the list; otherwise, it does nothing. - */ - void recordAccess(HashMap m) { - LinkedHashMap lm = (LinkedHashMap)m; - if (lm.accessOrder) { - lm.modCount++; - remove(); - addBefore(lm.header); - } - } - - void recordRemoval(HashMap m) { - remove(); - } - } - - private abstract class LinkedHashIterator implements Iterator { - Entry nextEntry = header.after; - Entry lastReturned = null; - - /** - * The modCount value that the iterator believes that the backing - * List should have. If this expectation is violated, the iterator - * has detected concurrent modification. - */ - int expectedModCount = modCount; - - public boolean hasNext() { - return nextEntry != header; - } - - public void remove() { - if (lastReturned == null) - throw new IllegalStateException(); - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - - LinkedHashMap.this.remove(lastReturned.key); - lastReturned = null; - expectedModCount = modCount; - } - - Entry nextEntry() { - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - if (nextEntry == header) - throw new NoSuchElementException(); - - Entry e = lastReturned = nextEntry; - nextEntry = e.after; - return e; - } - } - - private class KeyIterator extends LinkedHashIterator { - public K next() { return nextEntry().getKey(); } - } - - private class ValueIterator extends LinkedHashIterator { - public V next() { return nextEntry().value; } - } - - private class EntryIterator extends LinkedHashIterator> { - public Map.Entry next() { return nextEntry(); } - } - - // These Overrides alter the behavior of superclass view iterator() methods - Iterator newKeyIterator() { return new KeyIterator(); } - Iterator newValueIterator() { return new ValueIterator(); } - Iterator> newEntryIterator() { return new EntryIterator(); } - - /** - * This override alters behavior of superclass put method. It causes newly - * allocated entry to get inserted at the end of the linked list and - * removes the eldest entry if appropriate. - */ - @Override - void addEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) { - super.addEntry(hash, key, value, bucketIndex, checkIfNeedTree); - - // Remove eldest entry if instructed - Entry eldest = header.after; - if (removeEldestEntry(eldest)) { - removeEntryForKey(eldest.key); - } - } - - /* - * Create a new LinkedHashMap.Entry and setup the before/after pointers - */ - @Override - HashMap.Entry newEntry(int hash, K key, V value, Object next) { - Entry newEntry = new Entry<>(hash, key, value, next); - newEntry.addBefore(header); - return newEntry; + head = tail = null; } /** @@ -475,13 +462,13 @@ public class LinkedHashMap *

Sample use: this override will allow the map to grow up to 100 * entries and then delete the eldest entry each time a new entry is * added, maintaining a steady state of 100 entries. - *

{@code
+     * 
      *     private static final int MAX_ENTRIES = 100;
      *
      *     protected boolean removeEldestEntry(Map.Entry eldest) {
-     *        return size() > MAX_ENTRIES;
+     *        return size() > MAX_ENTRIES;
      *     }
-     * }
+ *
* *

This method typically does not modify the map in any way, * instead allowing the map to modify itself as directed by its @@ -508,4 +495,241 @@ public class LinkedHashMap protected boolean removeEldestEntry(Map.Entry eldest) { return false; } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + * Its {@link Spliterator} typically provides faster sequential + * performance but much poorer parallel performance than that of + * {@code HashMap}. + * + * @return a set view of the keys contained in this map + */ + public Set keySet() { + Set ks; + return (ks = keySet) == null ? (keySet = new LinkedKeySet()) : ks; + } + + final class LinkedKeySet extends AbstractSet { + public final int size() { return size; } + public final void clear() { LinkedHashMap.this.clear(); } + public final Iterator iterator() { + return new LinkedKeyIterator(); + } + public final boolean contains(Object o) { return containsKey(o); } + public final boolean remove(Object key) { + return removeNode(hash(key), key, null, false, true) != null; + } + public final Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.SIZED | + Spliterator.ORDERED | + Spliterator.DISTINCT); + } + public final void forEach(Consumer action) { + if (action == null) + throw new NullPointerException(); + int mc = modCount; + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e.key); + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + * Its {@link Spliterator} typically provides faster sequential + * performance but much poorer parallel performance than that of + * {@code HashMap}. + * + * @return a view of the values contained in this map + */ + public Collection values() { + Collection vs; + return (vs = values) == null ? (values = new LinkedValues()) : vs; + } + + final class LinkedValues extends AbstractCollection { + public final int size() { return size; } + public final void clear() { LinkedHashMap.this.clear(); } + public final Iterator iterator() { + return new LinkedValueIterator(); + } + public final boolean contains(Object o) { return containsValue(o); } + public final Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.SIZED | + Spliterator.ORDERED); + } + public final void forEach(Consumer action) { + if (action == null) + throw new NullPointerException(); + int mc = modCount; + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e.value); + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation, or through the + * setValue operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll and + * clear operations. It does not support the + * add or addAll operations. + * Its {@link Spliterator} typically provides faster sequential + * performance but much poorer parallel performance than that of + * {@code HashMap}. + * + * @return a set view of the mappings contained in this map + */ + public Set> entrySet() { + Set> es; + return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es; + } + + final class LinkedEntrySet extends AbstractSet> { + public final int size() { return size; } + public final void clear() { LinkedHashMap.this.clear(); } + public final Iterator> iterator() { + return new LinkedEntryIterator(); + } + public final boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + Node candidate = getNode(hash(key), key); + return candidate != null && candidate.equals(e); + } + public final boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + Object value = e.getValue(); + return removeNode(hash(key), key, value, true, true) != null; + } + return false; + } + public final Spliterator> spliterator() { + return Spliterators.spliterator(this, Spliterator.SIZED | + Spliterator.ORDERED | + Spliterator.DISTINCT); + } + public final void forEach(Consumer> action) { + if (action == null) + throw new NullPointerException(); + int mc = modCount; + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e); + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + // Map overrides + + public void forEach(BiConsumer action) { + if (action == null) + throw new NullPointerException(); + int mc = modCount; + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + action.accept(e.key, e.value); + if (modCount != mc) + throw new ConcurrentModificationException(); + } + + public void replaceAll(BiFunction function) { + if (function == null) + throw new NullPointerException(); + int mc = modCount; + for (LinkedHashMap.Entry e = head; e != null; e = e.after) + e.value = function.apply(e.key, e.value); + if (modCount != mc) + throw new ConcurrentModificationException(); + } + + // Iterators + + abstract class LinkedHashIterator { + LinkedHashMap.Entry next; + LinkedHashMap.Entry current; + int expectedModCount; + + LinkedHashIterator() { + next = head; + expectedModCount = modCount; + current = null; + } + + public final boolean hasNext() { + return next != null; + } + + final LinkedHashMap.Entry nextNode() { + LinkedHashMap.Entry e = next; + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (e == null) + throw new NoSuchElementException(); + current = e; + next = e.after; + return e; + } + + public final void remove() { + Node p = current; + if (p == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + current = null; + K key = p.key; + removeNode(hash(key), key, null, false, false); + expectedModCount = modCount; + } + } + + final class LinkedKeyIterator extends LinkedHashIterator + implements Iterator { + public final K next() { return nextNode().getKey(); } + } + + final class LinkedValueIterator extends LinkedHashIterator + implements Iterator { + public final V next() { return nextNode().value; } + } + + final class LinkedEntryIterator extends LinkedHashIterator + implements Iterator> { + public final Map.Entry next() { return nextNode(); } + } + + } diff --git a/jdk/test/java/lang/reflect/Generics/Probe.java b/jdk/test/java/lang/reflect/Generics/Probe.java index 1cdd5af3bdd..ebd98465e0c 100644 --- a/jdk/test/java/lang/reflect/Generics/Probe.java +++ b/jdk/test/java/lang/reflect/Generics/Probe.java @@ -50,9 +50,9 @@ import static java.util.Arrays.*; "java.util.HashMap$EntryIterator", "java.util.HashMap$KeyIterator", "java.util.HashMap$ValueIterator", - "java.util.LinkedHashMap$EntryIterator", - "java.util.LinkedHashMap$KeyIterator", - "java.util.LinkedHashMap$ValueIterator"}) + "java.util.LinkedHashMap$LinkedEntryIterator", + "java.util.LinkedHashMap$LinkedKeyIterator", + "java.util.LinkedHashMap$LinkedValueIterator"}) public class Probe { public static void main (String... args) throws Throwable { Classes classesAnnotation = (Probe.class).getAnnotation(Classes.class); diff --git a/jdk/test/java/util/Map/CheckRandomHashSeed.java b/jdk/test/java/util/Map/CheckRandomHashSeed.java index 5395ec999ee..2acf1bc8c9c 100644 --- a/jdk/test/java/util/Map/CheckRandomHashSeed.java +++ b/jdk/test/java/util/Map/CheckRandomHashSeed.java @@ -53,8 +53,6 @@ public class CheckRandomHashSeed { throw new Error("Error in test setup: " + (expectRandom ? "" : "not " ) + "expecting random hashSeed, but " + PROP_NAME + " is " + (propSet ? "" : "not ") + "enabled"); } - testMap(new HashMap()); - testMap(new LinkedHashMap()); testMap(new WeakHashMap()); testMap(new Hashtable()); } diff --git a/jdk/test/java/util/Map/InPlaceOpsCollisions.java b/jdk/test/java/util/Map/InPlaceOpsCollisions.java index 4a755bd4415..e24ba6ac508 100644 --- a/jdk/test/java/util/Map/InPlaceOpsCollisions.java +++ b/jdk/test/java/util/Map/InPlaceOpsCollisions.java @@ -25,7 +25,6 @@ * @test * @bug 8005698 * @run main InPlaceOpsCollisions -shortrun - * @run main/othervm -Djdk.map.randomseed=true InPlaceOpsCollisions -shortrun * @summary Ensure overrides of in-place operations in Maps behave well with lots of collisions. * @author Brent Christian */ diff --git a/jdk/test/java/util/Map/MapBinToFromTreeTest.java b/jdk/test/java/util/Map/MapBinToFromTreeTest.java new file mode 100644 index 00000000000..0b40a4ef16e --- /dev/null +++ b/jdk/test/java/util/Map/MapBinToFromTreeTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.testng.Assert.assertEquals; + +/* + * @test + * @bug 8023463 + * @summary Test the case where a bin is treeified and vice verser + * @run testng MapBinToFromTreeTest + */ + +@Test +public class MapBinToFromTreeTest { + + // Initial capacity of map + // Should be >= the map capacity for treeifiying, see HashMap/ConcurrentMap.MIN_TREEIFY_CAPACITY + static final int INITIAL_CAPACITY = 64; + + // Maximum size of map + // Should be > the treeify threshold, see HashMap/ConcurrentMap.TREEIFY_THRESHOLD + // Should be > INITIAL_CAPACITY to ensure resize occurs + static final int SIZE = 256; + + // Load factor of map + // A value 1.0 will ensure that a new threshold == capacity + static final float LOAD_FACTOR = 1.0f; + + @DataProvider(name = "maps") + static Object[][] mapProvider() { + return new Object[][] { + // Pass in the class name as a description for test reporting + // purposes + { HashMap.class.getName(), new HashMap(INITIAL_CAPACITY, LOAD_FACTOR) }, + { LinkedHashMap.class.getName(), new LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR) }, + { ConcurrentHashMap.class.getName(), new ConcurrentHashMap(INITIAL_CAPACITY, LOAD_FACTOR) }, + }; + } + + @Test(dataProvider = "maps") + public void testPutThenGet(String d, Map m) { + put(SIZE, m, (i, s) -> { + for (int j = 0; j < s; j++) { + assertEquals(m.get(new HashCodeInteger(j)).intValue(), j, + String.format("Map.get(%d)", j)); + } + }); + } + + @Test(dataProvider = "maps") + public void testPutThenTraverse(String d, Map m) { + Collector> c = getCollector(m); + + put(SIZE, m, (i, s) -> { + // Note that it is OK to collect to a Set (HashSet) as long as + // integer values are used since these tests only check for + // collisions and other tests will verify more general functionality + Collection actual = m.keySet().stream().map(e -> e.value).collect(c); + Collection expected = IntStream.range(0, s).boxed().collect(c); + assertEquals(actual, expected, "Map.keySet()"); + }); + } + + @Test(dataProvider = "maps") + public void testRemoveThenGet(String d, Map m) { + put(SIZE, m, (i, s) -> { }); + + remove(m, (i, s) -> { + for (int j = i + 1; j < SIZE; j++) { + assertEquals(m.get(new HashCodeInteger(j)).intValue(), j, + String.format("Map.get(%d)", j)); + } + }); + } + + @Test(dataProvider = "maps") + public void testRemoveThenTraverse(String d, Map m) { + put(SIZE, m, (i, s) -> { }); + + Collector> c = getCollector(m); + + remove(m, (i, s) -> { + Collection actual = m.keySet().stream().map(e -> e.value).collect(c); + Collection expected = IntStream.range(i + 1, SIZE).boxed().collect(c); + assertEquals(actual, expected, "Map.keySet()"); + }); + } + + @Test(dataProvider = "maps") + public void testUntreeifyOnResizeWithGet(String d, Map m) { + // Fill the map with 64 entries grouped into 4 buckets + put(INITIAL_CAPACITY, m, (i, s) -> { }); + + for (int i = INITIAL_CAPACITY; i < SIZE; i++) { + // Add further entries in the 0'th bucket so as not to disturb + // other buckets, entries of which may be distributed and/or + // the bucket untreeified on resize + m.put(new HashCodeInteger(i, 0), i); + + for (int j = 0; j < INITIAL_CAPACITY; j++) { + assertEquals(m.get(new HashCodeInteger(j)).intValue(), j, + String.format("Map.get(%d) < INITIAL_CAPACITY", j)); + } + for (int j = INITIAL_CAPACITY; j <= i; j++) { + assertEquals(m.get(new HashCodeInteger(j, 0)).intValue(), j, + String.format("Map.get(%d) >= INITIAL_CAPACITY", j)); + } + } + } + + @Test(dataProvider = "maps") + public void testUntreeifyOnResizeWithTraverse(String d, Map m) { + // Fill the map with 64 entries grouped into 4 buckets + put(INITIAL_CAPACITY, m, (i, s) -> { }); + + Collector> c = getCollector(m); + + for (int i = INITIAL_CAPACITY; i < SIZE; i++) { + // Add further entries in the 0'th bucket so as not to disturb + // other buckets, entries of which may be distributed and/or + // the bucket untreeified on resize + m.put(new HashCodeInteger(i, 0), i); + + Collection actual = m.keySet().stream().map(e -> e.value).collect(c); + Collection expected = IntStream.rangeClosed(0, i).boxed().collect(c); + assertEquals(actual, expected, "Key set"); + } + } + + Collector> getCollector(Map m) { + Collector> collector = m instanceof LinkedHashMap + ? Collectors.toList() + : Collectors.toSet(); + return collector; + } + + void put(int size, Map m, BiConsumer c) { + for (int i = 0; i < size; i++) { + m.put(new HashCodeInteger(i), i); + + c.accept(i, m.size()); + } + } + + void remove(Map m, BiConsumer c) { + int size = m.size(); + // Remove all elements thus ensuring at some point trees will be + // converting back to bins + for (int i = 0; i < size; i++) { + m.remove(new HashCodeInteger(i)); + + c.accept(i, m.size()); + } + } + + final static class HashCodeInteger implements Comparable { + final int value; + + final int hashcode; + + HashCodeInteger(int value) { + this(value, hash(value)); + } + + HashCodeInteger(int value, int hashcode) { + this.value = value; + this.hashcode = hashcode; + } + + static int hash(int i) { + // Assuming 64 entries with keys from 0 to 63 then a map: + // - of capacity 64 will have 4 buckets with 16 entries per-bucket + // - of capacity 128 will have 8 buckets with 8 entries per-bucket + // - of capacity 256 will have 16 buckets with 4 entries per-bucket + // + // Re-sizing will result in re-distribution, doubling the buckets + // and reducing the entries by half. This will result in + // untreeifying when the number of entries is less than untreeify + // threshold (see HashMap/ConcurrentMap.UNTREEIFY_THRESHOLD) + return (i % 4) + (i / 4) * INITIAL_CAPACITY; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof HashCodeInteger) { + HashCodeInteger other = (HashCodeInteger) obj; + return other.value == value; + } + return false; + } + + @Override + public int hashCode() { + return hashcode; + } + + @Override + public int compareTo(HashCodeInteger o) { + return value - o.value; + } + + @Override + public String toString() { + return Integer.toString(value); + } + } +} diff --git a/jdk/test/java/util/Map/TreeBinSplitBackToEntries.java b/jdk/test/java/util/Map/TreeBinSplitBackToEntries.java deleted file mode 100644 index 6093147a24d..00000000000 --- a/jdk/test/java/util/Map/TreeBinSplitBackToEntries.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -import java.util.*; -import java.lang.reflect.Field; - -/* - * @test - * @bug 8005698 - * @summary Test the case where TreeBin.splitTreeBin() converts a bin back to an Entry list - * @run main TreeBinSplitBackToEntries unused - * @author Brent Christian - */ - -public class TreeBinSplitBackToEntries { - private static int EXPECTED_TREE_THRESHOLD = 16; - - // Easiest if this covers one bit higher then 'bit' in splitTreeBin() on the - // call where the TreeBin is converted back to an Entry list - private static int HASHMASK = 0x7F; - private static boolean verbose = false; - private static boolean fastFail = false; - private static boolean failed = false; - - static void printlnIfVerbose(String msg) { - if (verbose) {System.out.println(msg); } - } - - public static void main(String[] args) { - for (String arg : args) { - switch(arg) { - case "-verbose": - verbose = true; - break; - case "-fastfail": - fastFail = true; - break; - } - } - checkTreeThreshold(); - testMapHiTree(); - testMapLoTree(); - if (failed) { - System.out.println("Test Failed"); - System.exit(1); - } else { - System.out.println("Test Passed"); - } - } - - public static void checkTreeThreshold() { - int threshold = -1; - try { - Class treeBinClass = Class.forName("java.util.HashMap$TreeBin"); - Field treeThreshold = treeBinClass.getDeclaredField("TREE_THRESHOLD"); - treeThreshold.setAccessible(true); - threshold = treeThreshold.getInt(treeBinClass); - } catch (ClassNotFoundException|NoSuchFieldException|IllegalAccessException e) { - e.printStackTrace(); - throw new Error("Problem accessing TreeBin.TREE_THRESHOLD", e); - } - check("Expected TREE_THRESHOLD: " + EXPECTED_TREE_THRESHOLD +", found: " + threshold, - threshold == EXPECTED_TREE_THRESHOLD); - printlnIfVerbose("TREE_THRESHOLD: " + threshold); - } - - public static void testMapHiTree() { - Object[][] mapKeys = makeHiTreeTestData(); - testMapsForKeys(mapKeys, "hiTree"); - } - - public static void testMapLoTree() { - Object[][] mapKeys = makeLoTreeTestData(); - - testMapsForKeys(mapKeys, "loTree"); - } - - public static void testMapsForKeys(Object[][] mapKeys, String desc) { - // loop through data sets - for (Object[] keys_desc : mapKeys) { - Map[] maps = (Map[]) new Map[]{ - new HashMap<>(4, 0.8f), - new LinkedHashMap<>(4, 0.8f), - }; - // for each map type. - for (Map map : maps) { - Object[] keys = (Object[]) keys_desc[1]; - System.out.println(desc + ": testPutThenGet() for " + map.getClass()); - testPutThenGet(map, keys); - } - } - } - - private static void testPutThenGet(Map map, T[] keys) { - for (T key : keys) { - printlnIfVerbose("put()ing 0x" + Integer.toHexString(Integer.parseInt(key.toString())) + ", hashCode=" + Integer.toHexString(key.hashCode())); - map.put(key, key); - } - for (T key : keys) { - check("key: 0x" + Integer.toHexString(Integer.parseInt(key.toString())) + " not found in resulting " + map.getClass().getSimpleName(), map.get(key) != null); - } - } - - /* Data to force a non-empty loTree in TreeBin.splitTreeBin() to be converted back - * into an Entry list - */ - private static Object[][] makeLoTreeTestData() { - HashableInteger COLLIDING_OBJECTS[] = new HashableInteger[] { - new HashableInteger( 0x23, HASHMASK), - new HashableInteger( 0x123, HASHMASK), - new HashableInteger( 0x323, HASHMASK), - new HashableInteger( 0x523, HASHMASK), - - new HashableInteger( 0x723, HASHMASK), - new HashableInteger( 0x923, HASHMASK), - new HashableInteger( 0xB23, HASHMASK), - new HashableInteger( 0xD23, HASHMASK), - - new HashableInteger( 0xF23, HASHMASK), - new HashableInteger( 0xF123, HASHMASK), - new HashableInteger( 0x1023, HASHMASK), - new HashableInteger( 0x1123, HASHMASK), - - new HashableInteger( 0x1323, HASHMASK), - new HashableInteger( 0x1523, HASHMASK), - new HashableInteger( 0x1723, HASHMASK), - new HashableInteger( 0x1923, HASHMASK), - - new HashableInteger( 0x1B23, HASHMASK), - new HashableInteger( 0x1D23, HASHMASK), - new HashableInteger( 0x3123, HASHMASK), - new HashableInteger( 0x3323, HASHMASK), - new HashableInteger( 0x3523, HASHMASK), - - new HashableInteger( 0x3723, HASHMASK), - new HashableInteger( 0x1001, HASHMASK), - new HashableInteger( 0x4001, HASHMASK), - new HashableInteger( 0x1, HASHMASK), - }; - return new Object[][] { - new Object[]{"Colliding Objects", COLLIDING_OBJECTS}, - }; - } - - /* Data to force the hiTree in TreeBin.splitTreeBin() to be converted back - * into an Entry list - */ - private static Object[][] makeHiTreeTestData() { - HashableInteger COLLIDING_OBJECTS[] = new HashableInteger[] { - new HashableInteger( 0x1, HASHMASK), - new HashableInteger( 0x101, HASHMASK), - new HashableInteger( 0x301, HASHMASK), - new HashableInteger( 0x501, HASHMASK), - new HashableInteger( 0x701, HASHMASK), - - new HashableInteger( 0x1001, HASHMASK), - new HashableInteger( 0x1101, HASHMASK), - new HashableInteger( 0x1301, HASHMASK), - - new HashableInteger( 0x1501, HASHMASK), - new HashableInteger( 0x1701, HASHMASK), - new HashableInteger( 0x4001, HASHMASK), - new HashableInteger( 0x4101, HASHMASK), - new HashableInteger( 0x4301, HASHMASK), - - new HashableInteger( 0x4501, HASHMASK), - new HashableInteger( 0x4701, HASHMASK), - new HashableInteger( 0x8001, HASHMASK), - new HashableInteger( 0x8101, HASHMASK), - - - new HashableInteger( 0x8301, HASHMASK), - new HashableInteger( 0x8501, HASHMASK), - new HashableInteger( 0x8701, HASHMASK), - new HashableInteger( 0x9001, HASHMASK), - - new HashableInteger( 0x23, HASHMASK), - new HashableInteger( 0x123, HASHMASK), - new HashableInteger( 0x323, HASHMASK), - new HashableInteger( 0x523, HASHMASK), - }; - return new Object[][] { - new Object[]{"Colliding Objects", COLLIDING_OBJECTS}, - }; - } - - static void check(String desc, boolean cond) { - if (!cond) { - fail(desc); - } - } - - static void fail(String msg) { - failed = true; - (new Error("Failure: " + msg)).printStackTrace(System.err); - if (fastFail) { - System.exit(1); - } - } - - final static class HashableInteger implements Comparable { - final int value; - final int hashmask; //yes duplication - - HashableInteger(int value, int hashmask) { - this.value = value; - this.hashmask = hashmask; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof HashableInteger) { - HashableInteger other = (HashableInteger) obj; - return other.value == value; - } - return false; - } - - @Override - public int hashCode() { - // This version ANDs the mask - return value & hashmask; - } - - @Override - public int compareTo(HashableInteger o) { - return value - o.value; - } - - @Override - public String toString() { - return Integer.toString(value); - } - } -} diff --git a/jdk/test/java/util/Spliterator/SpliteratorCharacteristics.java b/jdk/test/java/util/Spliterator/SpliteratorCharacteristics.java index bb0b7ecd976..3c74ce29dc1 100644 --- a/jdk/test/java/util/Spliterator/SpliteratorCharacteristics.java +++ b/jdk/test/java/util/Spliterator/SpliteratorCharacteristics.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8020156 8020009 8022326 + * @bug 8020156 8020009 8022326 8012913 * @run testng SpliteratorCharacteristics */ @@ -32,6 +32,10 @@ import org.testng.annotations.Test; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.SortedMap; @@ -47,7 +51,27 @@ import static org.testng.Assert.*; @Test public class SpliteratorCharacteristics { - // TreeMap + public void testHashMap() { + assertMapCharacteristics(new HashMap<>(), + Spliterator.SIZED | Spliterator.DISTINCT); + } + + public void testHashSet() { + assertSetCharacteristics(new HashSet<>(), + Spliterator.SIZED | Spliterator.DISTINCT); + } + + public void testLinkedHashMap() { + assertMapCharacteristics(new LinkedHashMap<>(), + Spliterator.SIZED | Spliterator.DISTINCT | + Spliterator.ORDERED); + } + + public void testLinkedHashSet() { + assertSetCharacteristics(new LinkedHashSet<>(), + Spliterator.SIZED | Spliterator.DISTINCT | + Spliterator.ORDERED); + } public void testTreeMap() { assertSortedMapCharacteristics(new TreeMap<>(), @@ -61,9 +85,6 @@ public class SpliteratorCharacteristics { Spliterator.SORTED | Spliterator.ORDERED); } - - // TreeSet - public void testTreeSet() { assertSortedSetCharacteristics(new TreeSet<>(), Spliterator.SIZED | Spliterator.DISTINCT | @@ -76,9 +97,6 @@ public class SpliteratorCharacteristics { Spliterator.SORTED | Spliterator.ORDERED); } - - // ConcurrentSkipListMap - public void testConcurrentSkipListMap() { assertSortedMapCharacteristics(new ConcurrentSkipListMap<>(), Spliterator.CONCURRENT | Spliterator.NONNULL | @@ -93,9 +111,6 @@ public class SpliteratorCharacteristics { Spliterator.ORDERED); } - - // ConcurrentSkipListSet - public void testConcurrentSkipListSet() { assertSortedSetCharacteristics(new ConcurrentSkipListSet<>(), Spliterator.CONCURRENT | Spliterator.NONNULL | @@ -113,35 +128,58 @@ public class SpliteratorCharacteristics { // - void assertSortedMapCharacteristics(SortedMap m, int keyCharacteristics) { + + void assertMapCharacteristics(Map m, int keyCharacteristics) { + assertMapCharacteristics(m, keyCharacteristics, 0); + } + + void assertMapCharacteristics(Map m, int keyCharacteristics, int notValueCharacteristics) { initMap(m); - boolean hasComparator = m.comparator() != null; + assertCharacteristics(m.keySet(), keyCharacteristics); + + assertCharacteristics(m.values(), + keyCharacteristics & ~(Spliterator.DISTINCT | notValueCharacteristics)); + + assertCharacteristics(m.entrySet(), keyCharacteristics); + + if ((keyCharacteristics & Spliterator.SORTED) == 0) { + assertISEComparator(m.keySet()); + assertISEComparator(m.values()); + assertISEComparator(m.entrySet()); + } + } + + void assertSetCharacteristics(Set s, int keyCharacteristics) { + initSet(s); + + assertCharacteristics(s, keyCharacteristics); + + if ((keyCharacteristics & Spliterator.SORTED) == 0) { + assertISEComparator(s); + } + } + + void assertSortedMapCharacteristics(SortedMap m, int keyCharacteristics) { + assertMapCharacteristics(m, keyCharacteristics, Spliterator.SORTED); Set keys = m.keySet(); - assertCharacteristics(keys, keyCharacteristics); - if (hasComparator) { + if (m.comparator() != null) { assertNotNullComparator(keys); } else { assertNullComparator(keys); } - assertCharacteristics(m.values(), - keyCharacteristics & ~(Spliterator.DISTINCT | Spliterator.SORTED)); assertISEComparator(m.values()); - assertCharacteristics(m.entrySet(), keyCharacteristics); assertNotNullComparator(m.entrySet()); } void assertSortedSetCharacteristics(SortedSet s, int keyCharacteristics) { - initSet(s); + assertSetCharacteristics(s, keyCharacteristics); - boolean hasComparator = s.comparator() != null; - - assertCharacteristics(s, keyCharacteristics); - if (hasComparator) { + if (s.comparator() != null) { assertNotNullComparator(s); } else { @@ -161,27 +199,18 @@ public class SpliteratorCharacteristics { } void assertCharacteristics(Collection c, int expectedCharacteristics) { - assertCharacteristics(c.spliterator(), expectedCharacteristics); - } - - void assertCharacteristics(Spliterator s, int expectedCharacteristics) { - assertTrue(s.hasCharacteristics(expectedCharacteristics)); + assertTrue(c.spliterator().hasCharacteristics(expectedCharacteristics), + "Spliterator characteristics"); } void assertNullComparator(Collection c) { - assertNullComparator(c.spliterator()); - } - - void assertNullComparator(Spliterator s) { - assertNull(s.getComparator()); + assertNull(c.spliterator().getComparator(), + "Comparator of Spliterator of Collection"); } void assertNotNullComparator(Collection c) { - assertNotNullComparator(c.spliterator()); - } - - void assertNotNullComparator(Spliterator s) { - assertNotNull(s.getComparator()); + assertNotNull(c.spliterator().getComparator(), + "Comparator of Spliterator of Collection"); } void assertISEComparator(Collection c) { @@ -196,6 +225,6 @@ public class SpliteratorCharacteristics { catch (IllegalStateException e) { caught = true; } - assertTrue(caught); + assertTrue(caught, "Throwing IllegalStateException"); } } From cc84e69bec97b188b4b93a74a3d9f104d71cda08 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Wed, 4 Sep 2013 11:40:23 +0100 Subject: [PATCH 18/75] 8008981: Deprecate SecurityManager checkTopLevelWindow, checkSystemClipboardAccess, checkAwtEventQueueAccess Reviewed-by: anthony, art, mchung --- .../macosx/classes/sun/lwawt/LWToolkit.java | 3 +- .../share/classes/java/awt/TextComponent.java | 3 +- jdk/src/share/classes/java/awt/Toolkit.java | 36 ++---- jdk/src/share/classes/java/awt/Window.java | 80 ++++++-------- .../classes/java/awt/event/InputEvent.java | 3 +- .../classes/java/lang/SecurityManager.java | 21 ++++ .../classes/sun/applet/AppletSecurity.java | 2 +- .../sun/awt/dnd/SunDropTargetContextPeer.java | 3 +- .../classes/sun/swing/SwingUtilities2.java | 2 +- .../solaris/classes/sun/awt/X11/XToolkit.java | 5 +- .../classes/sun/awt/windows/WToolkit.java | 3 +- jdk/test/java/awt/security/Permissions.java | 103 ++++++++++++++++++ 12 files changed, 181 insertions(+), 83 deletions(-) create mode 100644 jdk/test/java/awt/security/Permissions.java diff --git a/jdk/src/macosx/classes/sun/lwawt/LWToolkit.java b/jdk/src/macosx/classes/sun/lwawt/LWToolkit.java index fb7032f0b74..9765066d9d2 100644 --- a/jdk/src/macosx/classes/sun/lwawt/LWToolkit.java +++ b/jdk/src/macosx/classes/sun/lwawt/LWToolkit.java @@ -38,6 +38,7 @@ import java.util.*; import sun.awt.*; import sun.lwawt.macosx.*; import sun.print.*; +import sun.security.util.SecurityConstants; public abstract class LWToolkit extends SunToolkit implements Runnable { @@ -502,7 +503,7 @@ public abstract class LWToolkit extends SunToolkit implements Runnable { public Clipboard getSystemClipboard() { SecurityManager security = System.getSecurityManager(); if (security != null) { - security.checkSystemClipboardAccess(); + security.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); } synchronized (this) { diff --git a/jdk/src/share/classes/java/awt/TextComponent.java b/jdk/src/share/classes/java/awt/TextComponent.java index 7a6cd26bbf3..483657fd4ce 100644 --- a/jdk/src/share/classes/java/awt/TextComponent.java +++ b/jdk/src/share/classes/java/awt/TextComponent.java @@ -35,6 +35,7 @@ import java.text.BreakIterator; import javax.swing.text.AttributeSet; import javax.accessibility.*; import java.awt.im.InputMethodRequests; +import sun.security.util.SecurityConstants; /** * The TextComponent class is the superclass of @@ -728,7 +729,7 @@ public class TextComponent extends Component implements Accessible { SecurityManager sm = System.getSecurityManager(); if (sm == null) return true; try { - sm.checkSystemClipboardAccess(); + sm.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); return true; } catch (SecurityException e) {} return false; diff --git a/jdk/src/share/classes/java/awt/Toolkit.java b/jdk/src/share/classes/java/awt/Toolkit.java index 92bedb7580b..32f0adab3bc 100644 --- a/jdk/src/share/classes/java/awt/Toolkit.java +++ b/jdk/src/share/classes/java/awt/Toolkit.java @@ -1270,12 +1270,8 @@ public abstract class Toolkit { *

* Each actual implementation of this method should first check if there * is a security manager installed. If there is, the method should call - * the security manager's checkSystemClipboardAccess method - * to ensure it's ok to to access the system clipboard. If the default - * implementation of checkSystemClipboardAccess is used (that - * is, that method is not overriden), then this results in a call to the - * security manager's checkPermission method with an - * AWTPermission("accessClipboard") permission. + * the security manager's {@link SecurityManager#checkPermission + * checkPermission} method to check {@code AWTPermission("accessClipboard")}. * * @return the system Clipboard * @exception HeadlessException if GraphicsEnvironment.isHeadless() @@ -1318,14 +1314,9 @@ public abstract class Toolkit { * system selection Clipboard as described above. *

* Each actual implementation of this method should first check if there - * is a SecurityManager installed. If there is, the method - * should call the SecurityManager's - * checkSystemClipboardAccess method to ensure that client - * code has access the system selection. If the default implementation of - * checkSystemClipboardAccess is used (that is, if the method - * is not overridden), then this results in a call to the - * SecurityManager's checkPermission method with - * an AWTPermission("accessClipboard") permission. + * is a security manager installed. If there is, the method should call + * the security manager's {@link SecurityManager#checkPermission + * checkPermission} method to check {@code AWTPermission("accessClipboard")}. * * @return the system selection as a Clipboard, or * null if the native platform does not support a @@ -1699,25 +1690,20 @@ public abstract class Toolkit { * therefore not assume that the EventQueue instance returned * by this method will be shared by other applets or the system. * - *

First, if there is a security manager, its - * checkAwtEventQueueAccess - * method is called. - * If the default implementation of checkAwtEventQueueAccess - * is used (that is, that method is not overriden), then this results in - * a call to the security manager's checkPermission method - * with an AWTPermission("accessEventQueue") permission. + *

If there is a security manager then its + * {@link SecurityManager#checkPermission checkPermission} method + * is called to check {@code AWTPermission("accessEventQueue")}. * * @return the EventQueue object * @throws SecurityException - * if a security manager exists and its {@link - * java.lang.SecurityManager#checkAwtEventQueueAccess} - * method denies access to the EventQueue + * if a security manager is set and it denies access to + * the {@code EventQueue} * @see java.awt.AWTPermission */ public final EventQueue getSystemEventQueue() { SecurityManager security = System.getSecurityManager(); if (security != null) { - security.checkAwtEventQueueAccess(); + security.checkPermission(SecurityConstants.AWT.CHECK_AWT_EVENTQUEUE_PERMISSION); } return getSystemEventQueueImpl(); } diff --git a/jdk/src/share/classes/java/awt/Window.java b/jdk/src/share/classes/java/awt/Window.java index 4076939c7f0..c10a3b277a8 100644 --- a/jdk/src/share/classes/java/awt/Window.java +++ b/jdk/src/share/classes/java/awt/Window.java @@ -195,10 +195,9 @@ public class Window extends Container implements Accessible { /** * This represents the warning message that is * to be displayed in a non secure window. ie : - * a window that has a security manager installed for - * which calling SecurityManager.checkTopLevelWindow() - * is false. This message can be displayed anywhere in - * the window. + * a window that has a security manager installed that denies + * {@code AWTPermission("showWindowWithoutWarningBanner")}. + * This message can be displayed anywhere in the window. * * @serial * @see #getWarningString @@ -417,11 +416,10 @@ public class Window extends Container implements Accessible { * Constructs a new, initially invisible window in default size with the * specified {@code GraphicsConfiguration}. *

- * If there is a security manager, this method first calls - * the security manager's {@code checkTopLevelWindow} - * method with {@code this} - * as its argument to determine whether or not the window - * must be displayed with a warning banner. + * If there is a security manager, then it is invoked to check + * {@code AWTPermission("showWindowWithoutWarningBanner")} + * to determine whether or not the window must be displayed with + * a warning banner. * * @param gc the {@code GraphicsConfiguration} of the target screen * device. If {@code gc} is {@code null}, the system default @@ -432,7 +430,6 @@ public class Window extends Container implements Accessible { * {@code GraphicsEnvironment.isHeadless()} returns {@code true} * * @see java.awt.GraphicsEnvironment#isHeadless - * @see java.lang.SecurityManager#checkTopLevelWindow */ Window(GraphicsConfiguration gc) { init(gc); @@ -511,25 +508,16 @@ public class Window extends Container implements Accessible { /** * Constructs a new, initially invisible window in the default size. - * - *

First, if there is a security manager, its - * {@code checkTopLevelWindow} - * method is called with {@code this} - * as its argument - * to see if it's ok to display the window without a warning banner. - * If the default implementation of {@code checkTopLevelWindow} - * is used (that is, that method is not overriden), then this results in - * a call to the security manager's {@code checkPermission} method - * with an {@code AWTPermission("showWindowWithoutWarningBanner")} - * permission. It that method raises a SecurityException, - * {@code checkTopLevelWindow} returns false, otherwise it - * returns true. If it returns false, a warning banner is created. + *

+ * If there is a security manager set, it is invoked to check + * {@code AWTPermission("showWindowWithoutWarningBanner")}. + * If that check fails with a {@code SecurityException} then a warning + * banner is created. * * @exception HeadlessException when * {@code GraphicsEnvironment.isHeadless()} returns {@code true} * * @see java.awt.GraphicsEnvironment#isHeadless - * @see java.lang.SecurityManager#checkTopLevelWindow */ Window() throws HeadlessException { GraphicsEnvironment.checkHeadless(); @@ -541,11 +529,10 @@ public class Window extends Container implements Accessible { * {@code Frame} as its owner. The window will not be focusable * unless its owner is showing on the screen. *

- * If there is a security manager, this method first calls - * the security manager's {@code checkTopLevelWindow} - * method with {@code this} - * as its argument to determine whether or not the window - * must be displayed with a warning banner. + * If there is a security manager set, it is invoked to check + * {@code AWTPermission("showWindowWithoutWarningBanner")}. + * If that check fails with a {@code SecurityException} then a warning + * banner is created. * * @param owner the {@code Frame} to act as owner or {@code null} * if this window has no owner @@ -555,7 +542,6 @@ public class Window extends Container implements Accessible { * {@code GraphicsEnvironment.isHeadless} returns {@code true} * * @see java.awt.GraphicsEnvironment#isHeadless - * @see java.lang.SecurityManager#checkTopLevelWindow * @see #isShowing */ public Window(Frame owner) { @@ -570,11 +556,10 @@ public class Window extends Container implements Accessible { * unless its nearest owning {@code Frame} or {@code Dialog} * is showing on the screen. *

- * If there is a security manager, this method first calls - * the security manager's {@code checkTopLevelWindow} - * method with {@code this} - * as its argument to determine whether or not the window - * must be displayed with a warning banner. + * If there is a security manager set, it is invoked to check + * {@code AWTPermission("showWindowWithoutWarningBanner")}. + * If that check fails with a {@code SecurityException} then a + * warning banner is created. * * @param owner the {@code Window} to act as owner or * {@code null} if this window has no owner @@ -585,7 +570,6 @@ public class Window extends Container implements Accessible { * {@code true} * * @see java.awt.GraphicsEnvironment#isHeadless - * @see java.lang.SecurityManager#checkTopLevelWindow * @see #isShowing * * @since 1.2 @@ -603,11 +587,10 @@ public class Window extends Container implements Accessible { * its nearest owning {@code Frame} or {@code Dialog} * is showing on the screen. *

- * If there is a security manager, this method first calls - * the security manager's {@code checkTopLevelWindow} - * method with {@code this} - * as its argument to determine whether or not the window - * must be displayed with a warning banner. + * If there is a security manager set, it is invoked to check + * {@code AWTPermission("showWindowWithoutWarningBanner")}. If that + * check fails with a {@code SecurityException} then a warning banner + * is created. * * @param owner the window to act as owner or {@code null} * if this window has no owner @@ -621,7 +604,6 @@ public class Window extends Container implements Accessible { * {@code true} * * @see java.awt.GraphicsEnvironment#isHeadless - * @see java.lang.SecurityManager#checkTopLevelWindow * @see GraphicsConfiguration#getBounds * @see #isShowing * @since 1.3 @@ -1362,10 +1344,9 @@ public class Window extends Container implements Accessible { * Gets the warning string that is displayed with this window. * If this window is insecure, the warning string is displayed * somewhere in the visible area of the window. A window is - * insecure if there is a security manager, and the security - * manager's {@code checkTopLevelWindow} method returns - * {@code false} when this window is passed to it as an - * argument. + * insecure if there is a security manager and the security + * manager denies + * {@code AWTPermission("showWindowWithoutWarningBanner")}. *

* If the window is secure, then {@code getWarningString} * returns {@code null}. If the window is insecure, this @@ -1373,7 +1354,6 @@ public class Window extends Container implements Accessible { * {@code awt.appletWarning} * and returns the string value of that property. * @return the warning string for this window. - * @see java.lang.SecurityManager#checkTopLevelWindow(java.lang.Object) */ public final String getWarningString() { return warningString; @@ -1383,10 +1363,12 @@ public class Window extends Container implements Accessible { warningString = null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { - if (!sm.checkTopLevelWindow(this)) { + try { + sm.checkPermission(SecurityConstants.AWT.TOPLEVEL_WINDOW_PERMISSION); + } catch (SecurityException se) { // make sure the privileged action is only // for getting the property! We don't want the - // above checkTopLevelWindow call to always succeed! + // above checkPermission call to always succeed! warningString = AccessController.doPrivileged( new GetPropertyAction("awt.appletWarning", "Java Applet Window")); diff --git a/jdk/src/share/classes/java/awt/event/InputEvent.java b/jdk/src/share/classes/java/awt/event/InputEvent.java index 078b1a16d6a..24965d20caf 100644 --- a/jdk/src/share/classes/java/awt/event/InputEvent.java +++ b/jdk/src/share/classes/java/awt/event/InputEvent.java @@ -33,6 +33,7 @@ import java.util.Arrays; import sun.awt.AWTAccessor; import sun.util.logging.PlatformLogger; +import sun.security.util.SecurityConstants; /** * The root event class for all component-level input events. @@ -350,7 +351,7 @@ public abstract class InputEvent extends ComponentEvent { SecurityManager sm = System.getSecurityManager(); if (sm != null) { try { - sm.checkSystemClipboardAccess(); + sm.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); b = true; } catch (SecurityException se) { if (logger.isLoggable(PlatformLogger.Level.FINE)) { diff --git a/jdk/src/share/classes/java/lang/SecurityManager.java b/jdk/src/share/classes/java/lang/SecurityManager.java index 34be905bd02..3565082033c 100644 --- a/jdk/src/share/classes/java/lang/SecurityManager.java +++ b/jdk/src/share/classes/java/lang/SecurityManager.java @@ -1336,9 +1336,16 @@ class SecurityManager { * top-level windows; false otherwise. * @exception NullPointerException if the window argument is * null. + * @deprecated The dependency on {@code AWTPermission} creates an + * impediment to future modularization of the Java platform. + * Users of this method should instead invoke + * {@link #checkPermission} directly. + * This method will be changed in a future release to check + * the permission {@code java.security.AllPermission}. * @see java.awt.Window * @see #checkPermission(java.security.Permission) checkPermission */ + @Deprecated public boolean checkTopLevelWindow(Object window) { if (window == null) { throw new NullPointerException("window can't be null"); @@ -1398,8 +1405,15 @@ class SecurityManager { * @since JDK1.1 * @exception SecurityException if the calling thread does not have * permission to access the system clipboard. + * @deprecated The dependency on {@code AWTPermission} creates an + * impediment to future modularization of the Java platform. + * Users of this method should instead invoke + * {@link #checkPermission} directly. + * This method will be changed in a future release to check + * the permission {@code java.security.AllPermission}. * @see #checkPermission(java.security.Permission) checkPermission */ + @Deprecated public void checkSystemClipboardAccess() { Permission perm = SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION; if (perm == null) { @@ -1427,8 +1441,15 @@ class SecurityManager { * @since JDK1.1 * @exception SecurityException if the calling thread does not have * permission to access the AWT event queue. + * @deprecated The dependency on {@code AWTPermission} creates an + * impediment to future modularization of the Java platform. + * Users of this method should instead invoke + * {@link #checkPermission} directly. + * This method will be changed in a future release to check + * the permission {@code java.security.AllPermission}. * @see #checkPermission(java.security.Permission) checkPermission */ + @Deprecated public void checkAwtEventQueueAccess() { Permission perm = SecurityConstants.AWT.CHECK_AWT_EVENTQUEUE_PERMISSION; if (perm == null) { diff --git a/jdk/src/share/classes/sun/applet/AppletSecurity.java b/jdk/src/share/classes/sun/applet/AppletSecurity.java index 4bd67decbcc..b6a71f7b910 100644 --- a/jdk/src/share/classes/sun/applet/AppletSecurity.java +++ b/jdk/src/share/classes/sun/applet/AppletSecurity.java @@ -314,7 +314,7 @@ class AppletSecurity extends AWTSecurityManager { // If we're about to allow access to the main EventQueue, // and anything untrusted is on the class context stack, // disallow access. - super.checkAwtEventQueueAccess(); + super.checkPermission(SecurityConstants.AWT.CHECK_AWT_EVENTQUEUE_PERMISSION); } } // checkAwtEventQueueAccess() diff --git a/jdk/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java b/jdk/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java index efbf14f1a6a..ed5b2b8b97b 100644 --- a/jdk/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java +++ b/jdk/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java @@ -57,6 +57,7 @@ import sun.awt.AppContext; import sun.awt.SunToolkit; import sun.awt.datatransfer.DataTransferer; import sun.awt.datatransfer.ToolkitThreadBlockedHandler; +import sun.security.util.SecurityConstants; /** *

@@ -225,7 +226,7 @@ public abstract class SunDropTargetContextPeer implements DropTargetContextPeer, SecurityManager sm = System.getSecurityManager(); try { if (!dropInProcess && sm != null) { - sm.checkSystemClipboardAccess(); + sm.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); } } catch (Exception e) { Thread currentThread = Thread.currentThread(); diff --git a/jdk/src/share/classes/sun/swing/SwingUtilities2.java b/jdk/src/share/classes/sun/swing/SwingUtilities2.java index a1903f2bdd2..6bbbf0a5687 100644 --- a/jdk/src/share/classes/sun/swing/SwingUtilities2.java +++ b/jdk/src/share/classes/sun/swing/SwingUtilities2.java @@ -1184,7 +1184,7 @@ public class SwingUtilities2 { canAccess = true; } else { try { - sm.checkSystemClipboardAccess(); + sm.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); canAccess = true; } catch (SecurityException e) { } diff --git a/jdk/src/solaris/classes/sun/awt/X11/XToolkit.java b/jdk/src/solaris/classes/sun/awt/X11/XToolkit.java index 32eceaae3be..9b4daa1cef6 100644 --- a/jdk/src/solaris/classes/sun/awt/X11/XToolkit.java +++ b/jdk/src/solaris/classes/sun/awt/X11/XToolkit.java @@ -54,6 +54,7 @@ import sun.print.PrintJob2D; import sun.security.action.GetPropertyAction; import sun.security.action.GetBooleanAction; import sun.util.logging.PlatformLogger; +import sun.security.util.SecurityConstants; public final class XToolkit extends UNIXToolkit implements Runnable { private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XToolkit"); @@ -1152,7 +1153,7 @@ public final class XToolkit extends UNIXToolkit implements Runnable { public Clipboard getSystemClipboard() { SecurityManager security = System.getSecurityManager(); if (security != null) { - security.checkSystemClipboardAccess(); + security.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); } synchronized (this) { if (clipboard == null) { @@ -1165,7 +1166,7 @@ public final class XToolkit extends UNIXToolkit implements Runnable { public Clipboard getSystemSelection() { SecurityManager security = System.getSecurityManager(); if (security != null) { - security.checkSystemClipboardAccess(); + security.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); } synchronized (this) { if (selection == null) { diff --git a/jdk/src/windows/classes/sun/awt/windows/WToolkit.java b/jdk/src/windows/classes/sun/awt/windows/WToolkit.java index 25b88f384cf..0f67ffe0a50 100644 --- a/jdk/src/windows/classes/sun/awt/windows/WToolkit.java +++ b/jdk/src/windows/classes/sun/awt/windows/WToolkit.java @@ -64,6 +64,7 @@ import sun.font.FontManagerFactory; import sun.font.SunFontManager; import sun.misc.PerformanceLogger; import sun.util.logging.PlatformLogger; +import sun.security.util.SecurityConstants; public class WToolkit extends SunToolkit implements Runnable { @@ -681,7 +682,7 @@ public class WToolkit extends SunToolkit implements Runnable { public Clipboard getSystemClipboard() { SecurityManager security = System.getSecurityManager(); if (security != null) { - security.checkSystemClipboardAccess(); + security.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION); } synchronized (this) { if (clipboard == null) { diff --git a/jdk/test/java/awt/security/Permissions.java b/jdk/test/java/awt/security/Permissions.java new file mode 100644 index 00000000000..3f0a3ad24e3 --- /dev/null +++ b/jdk/test/java/awt/security/Permissions.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8008981 + * @summary Test that selected Toolkit and Window methods/constructors do + * the appropriate permission check + * @run main/othervm Permissions + */ + +import java.awt.AWTPermission; +import java.awt.Frame; +import java.awt.GraphicsConfiguration; +import java.awt.Toolkit; +import java.awt.Window; +import java.util.ArrayList; +import java.util.List; +import java.security.Permission; + +public class Permissions { + + static class MySecurityManager extends SecurityManager { + private List permissionsChecked = new ArrayList<>(); + + static MySecurityManager install() { + MySecurityManager sm = new MySecurityManager(); + System.setSecurityManager(sm); + return sm; + } + + @Override + public void checkPermission(Permission perm) { + permissionsChecked.add(perm); + } + + void prepare(String msg) { + System.out.println(msg); + permissionsChecked.clear(); + } + + /** + * Checks the security manager's checkPermission method was invoked + * to check the given permission and target name. + */ + void assertChecked(Class type, String name) { + for (Permission perm: permissionsChecked) { + if (type.isInstance(perm) && perm.getName().equals(name)) + return; + } + throw new RuntimeException(type.getName() + "(\"" + name + "\") not checked"); + } + } + + public static void main(String[] args) { + MySecurityManager sm = MySecurityManager.install(); + + Toolkit toolkit = Toolkit.getDefaultToolkit(); + + sm.prepare("Toolkit.getSystemClipboard()"); + toolkit.getSystemClipboard(); + sm.assertChecked(AWTPermission.class, "accessClipboard"); + + sm.prepare("Toolkit.getSystemEventQueue()"); + toolkit.getSystemEventQueue(); + sm.assertChecked(AWTPermission.class, "accessEventQueue"); + + sm.prepare("Toolkit.getSystemSelection()"); + toolkit.getSystemSelection(); + //sm.assertChecked(AWTPermission.class, "accessClipboard"); + + sm.prepare("Window(Frame)"); + new Window((Frame)null); + sm.assertChecked(AWTPermission.class, "showWindowWithoutWarningBanner"); + + sm.prepare("Window(Window)"); + new Window((Window)null); + sm.assertChecked(AWTPermission.class, "showWindowWithoutWarningBanner"); + + sm.prepare("Window(Window,GraphicsConfiguration)"); + new Window((Window)null, (GraphicsConfiguration)null); + sm.assertChecked(AWTPermission.class, "showWindowWithoutWarningBanner"); + } +} From ac101a9d997181c3be91383172841301401091f2 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 4 Sep 2013 15:32:59 +0200 Subject: [PATCH 19/75] 6823527: java.util.logging.Handler has thread safety issues Reviewed-by: dholmes, mchung --- .../java/util/logging/ConsoleHandler.java | 5 ++- .../java/util/logging/FileHandler.java | 13 +++++-- .../classes/java/util/logging/Handler.java | 34 ++++++++++++------- .../java/util/logging/MemoryHandler.java | 11 +++--- .../java/util/logging/SocketHandler.java | 3 +- .../java/util/logging/StreamHandler.java | 10 ++++-- 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/jdk/src/share/classes/java/util/logging/ConsoleHandler.java b/jdk/src/share/classes/java/util/logging/ConsoleHandler.java index a8b4bc4f0cd..36ef6be6801 100644 --- a/jdk/src/share/classes/java/util/logging/ConsoleHandler.java +++ b/jdk/src/share/classes/java/util/logging/ConsoleHandler.java @@ -26,9 +26,6 @@ package java.util.logging; -import java.io.*; -import java.net.*; - /** * This Handler publishes log records to System.err. * By default the SimpleFormatter is used to generate brief summaries. @@ -114,6 +111,7 @@ public class ConsoleHandler extends StreamHandler { * @param record description of the log event. A null record is * silently ignored and is not published */ + @Override public void publish(LogRecord record) { super.publish(record); flush(); @@ -124,6 +122,7 @@ public class ConsoleHandler extends StreamHandler { * to close the output stream. That is, we do not * close System.err. */ + @Override public void close() { flush(); } diff --git a/jdk/src/share/classes/java/util/logging/FileHandler.java b/jdk/src/share/classes/java/util/logging/FileHandler.java index 52360c206ee..b2ac0d9d3e0 100644 --- a/jdk/src/share/classes/java/util/logging/FileHandler.java +++ b/jdk/src/share/classes/java/util/logging/FileHandler.java @@ -149,7 +149,7 @@ public class FileHandler extends StreamHandler { private FileChannel lockFileChannel; private File files[]; private static final int MAX_LOCKS = 100; - private static java.util.HashMap locks = new java.util.HashMap<>(); + private static final java.util.HashMap locks = new java.util.HashMap<>(); /** * A metered stream is a subclass of OutputStream that @@ -157,7 +157,7 @@ public class FileHandler extends StreamHandler { * (b) keeps track of how many bytes have been written */ private class MeteredStream extends OutputStream { - OutputStream out; + final OutputStream out; int written; MeteredStream(OutputStream out, int written) { @@ -165,25 +165,30 @@ public class FileHandler extends StreamHandler { this.written = written; } + @Override public void write(int b) throws IOException { out.write(b); written++; } + @Override public void write(byte buff[]) throws IOException { out.write(buff); written += buff.length; } + @Override public void write(byte buff[], int off, int len) throws IOException { out.write(buff,off,len); written += len; } + @Override public void flush() throws IOException { out.flush(); } + @Override public void close() throws IOException { out.close(); } @@ -607,6 +612,7 @@ public class FileHandler extends StreamHandler { * @param record description of the log event. A null record is * silently ignored and is not published */ + @Override public synchronized void publish(LogRecord record) { if (!isLoggable(record)) { return; @@ -620,6 +626,7 @@ public class FileHandler extends StreamHandler { // currently being called from untrusted code. // So it is safe to raise privilege here. AccessController.doPrivileged(new PrivilegedAction() { + @Override public Object run() { rotate(); return null; @@ -634,6 +641,7 @@ public class FileHandler extends StreamHandler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ + @Override public synchronized void close() throws SecurityException { super.close(); // Unlock any lock file. @@ -656,6 +664,7 @@ public class FileHandler extends StreamHandler { private static class InitializationErrorManager extends ErrorManager { Exception lastException; + @Override public void error(String msg, Exception ex, int code) { lastException = ex; } diff --git a/jdk/src/share/classes/java/util/logging/Handler.java b/jdk/src/share/classes/java/util/logging/Handler.java index a8c3eb4b036..1cc7b43c7de 100644 --- a/jdk/src/share/classes/java/util/logging/Handler.java +++ b/jdk/src/share/classes/java/util/logging/Handler.java @@ -47,12 +47,20 @@ import java.io.UnsupportedEncodingException; public abstract class Handler { private static final int offValue = Level.OFF.intValue(); - private LogManager manager = LogManager.getLogManager(); - private Filter filter; - private Formatter formatter; - private Level logLevel = Level.ALL; - private ErrorManager errorManager = new ErrorManager(); - private String encoding; + private final LogManager manager = LogManager.getLogManager(); + + // We're using volatile here to avoid synchronizing getters, which + // would prevent other threads from calling isLoggable() + // while publish() is executing. + // On the other hand, setters will be synchronized to exclude concurrent + // execution with more complex methods, such as StreamHandler.publish(). + // We wouldn't want 'level' to be changed by another thread in the middle + // of the execution of a 'publish' call. + private volatile Filter filter; + private volatile Formatter formatter; + private volatile Level logLevel = Level.ALL; + private volatile ErrorManager errorManager = new ErrorManager(); + private volatile String encoding; // Package private support for security checking. When sealed // is true, we access check updates to the class. @@ -110,7 +118,7 @@ public abstract class Handler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ - public void setFormatter(Formatter newFormatter) throws SecurityException { + public synchronized void setFormatter(Formatter newFormatter) throws SecurityException { checkPermission(); // Check for a null pointer: newFormatter.getClass(); @@ -138,7 +146,7 @@ public abstract class Handler { * @exception UnsupportedEncodingException if the named encoding is * not supported. */ - public void setEncoding(String encoding) + public synchronized void setEncoding(String encoding) throws SecurityException, java.io.UnsupportedEncodingException { checkPermission(); if (encoding != null) { @@ -174,7 +182,7 @@ public abstract class Handler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ - public void setFilter(Filter newFilter) throws SecurityException { + public synchronized void setFilter(Filter newFilter) throws SecurityException { checkPermission(); filter = newFilter; } @@ -198,7 +206,7 @@ public abstract class Handler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ - public void setErrorManager(ErrorManager em) { + public synchronized void setErrorManager(ErrorManager em) { checkPermission(); if (em == null) { throw new NullPointerException(); @@ -264,7 +272,7 @@ public abstract class Handler { * than this level will be discarded. * @return the level of messages being logged. */ - public synchronized Level getLevel() { + public Level getLevel() { return logLevel; } @@ -282,11 +290,11 @@ public abstract class Handler { * */ public boolean isLoggable(LogRecord record) { - int levelValue = getLevel().intValue(); + final int levelValue = getLevel().intValue(); if (record.getLevel().intValue() < levelValue || levelValue == offValue) { return false; } - Filter filter = getFilter(); + final Filter filter = getFilter(); if (filter == null) { return true; } diff --git a/jdk/src/share/classes/java/util/logging/MemoryHandler.java b/jdk/src/share/classes/java/util/logging/MemoryHandler.java index ddf72c2a1e1..684ff8f8573 100644 --- a/jdk/src/share/classes/java/util/logging/MemoryHandler.java +++ b/jdk/src/share/classes/java/util/logging/MemoryHandler.java @@ -88,7 +88,7 @@ package java.util.logging; public class MemoryHandler extends Handler { private final static int DEFAULT_SIZE = 1000; - private Level pushLevel; + private volatile Level pushLevel; private int size; private Handler target; private LogRecord buffer[]; @@ -188,6 +188,7 @@ public class MemoryHandler extends Handler { * @param record description of the log event. A null record is * silently ignored and is not published */ + @Override public synchronized void publish(LogRecord record) { if (!isLoggable(record)) { return; @@ -227,6 +228,7 @@ public class MemoryHandler extends Handler { * Note that the current contents of the MemoryHandler * buffer are not written out. That requires a "push". */ + @Override public void flush() { target.flush(); } @@ -238,6 +240,7 @@ public class MemoryHandler extends Handler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ + @Override public void close() throws SecurityException { target.close(); setLevel(Level.OFF); @@ -252,11 +255,10 @@ public class MemoryHandler extends Handler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ - public void setPushLevel(Level newLevel) throws SecurityException { + public synchronized void setPushLevel(Level newLevel) throws SecurityException { if (newLevel == null) { throw new NullPointerException(); } - LogManager manager = LogManager.getLogManager(); checkPermission(); pushLevel = newLevel; } @@ -266,7 +268,7 @@ public class MemoryHandler extends Handler { * * @return the value of the pushLevel */ - public synchronized Level getPushLevel() { + public Level getPushLevel() { return pushLevel; } @@ -283,6 +285,7 @@ public class MemoryHandler extends Handler { * @return true if the LogRecord would be logged. * */ + @Override public boolean isLoggable(LogRecord record) { return super.isLoggable(record); } diff --git a/jdk/src/share/classes/java/util/logging/SocketHandler.java b/jdk/src/share/classes/java/util/logging/SocketHandler.java index d7f2f31adbd..0d7f2ceedf4 100644 --- a/jdk/src/share/classes/java/util/logging/SocketHandler.java +++ b/jdk/src/share/classes/java/util/logging/SocketHandler.java @@ -82,7 +82,6 @@ public class SocketHandler extends StreamHandler { private Socket sock; private String host; private int port; - private String portProperty; // Private method to configure a SocketHandler from LogManager // properties and/or default values as specified in the class @@ -177,6 +176,7 @@ public class SocketHandler extends StreamHandler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ + @Override public synchronized void close() throws SecurityException { super.close(); if (sock != null) { @@ -195,6 +195,7 @@ public class SocketHandler extends StreamHandler { * @param record description of the log event. A null record is * silently ignored and is not published */ + @Override public synchronized void publish(LogRecord record) { if (!isLoggable(record)) { return; diff --git a/jdk/src/share/classes/java/util/logging/StreamHandler.java b/jdk/src/share/classes/java/util/logging/StreamHandler.java index 96dff8c9df0..b3f23743611 100644 --- a/jdk/src/share/classes/java/util/logging/StreamHandler.java +++ b/jdk/src/share/classes/java/util/logging/StreamHandler.java @@ -73,10 +73,9 @@ import java.io.*; */ public class StreamHandler extends Handler { - private LogManager manager = LogManager.getLogManager(); private OutputStream output; private boolean doneHeader; - private Writer writer; + private volatile Writer writer; // Private method to configure a StreamHandler from LogManager // properties and/or default values as specified in the class @@ -169,7 +168,8 @@ public class StreamHandler extends Handler { * @exception UnsupportedEncodingException if the named encoding is * not supported. */ - public void setEncoding(String encoding) + @Override + public synchronized void setEncoding(String encoding) throws SecurityException, java.io.UnsupportedEncodingException { super.setEncoding(encoding); if (output == null) { @@ -201,6 +201,7 @@ public class StreamHandler extends Handler { * @param record description of the log event. A null record is * silently ignored and is not published */ + @Override public synchronized void publish(LogRecord record) { if (!isLoggable(record)) { return; @@ -240,6 +241,7 @@ public class StreamHandler extends Handler { * @return true if the LogRecord would be logged. * */ + @Override public boolean isLoggable(LogRecord record) { if (writer == null || record == null) { return false; @@ -250,6 +252,7 @@ public class StreamHandler extends Handler { /** * Flush any buffered messages. */ + @Override public synchronized void flush() { if (writer != null) { try { @@ -294,6 +297,7 @@ public class StreamHandler extends Handler { * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ + @Override public synchronized void close() throws SecurityException { flushAndClose(); } From a0c3d88fba62b486273b94a1b742fba9d2199cd7 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 4 Sep 2013 16:22:22 +0200 Subject: [PATCH 20/75] 8019853: Break logging and AWT circular dependency Break logging and AWT circular dependency, which was at the root cause for 8023258 - Logger.getLogger() after ImageIO.read() returns different logger instance Reviewed-by: mchung, art --- .../classes/java/util/logging/LogManager.java | 38 +++++----- jdk/src/share/classes/sun/awt/AppContext.java | 66 ++++++++++++---- .../share/classes/sun/misc/JavaAWTAccess.java | 14 ++-- .../share/classes/sun/misc/SharedSecrets.java | 2 +- .../util/logging/TestAppletLoggerContext.java | 19 ++--- .../TestLoggingWithMainAppContext.java | 75 +++++++++++++++++++ 6 files changed, 159 insertions(+), 55 deletions(-) create mode 100644 jdk/test/java/util/logging/TestLoggingWithMainAppContext.java diff --git a/jdk/src/share/classes/java/util/logging/LogManager.java b/jdk/src/share/classes/java/util/logging/LogManager.java index 0d63468b3cc..717e52990e1 100644 --- a/jdk/src/share/classes/java/util/logging/LogManager.java +++ b/jdk/src/share/classes/java/util/logging/LogManager.java @@ -391,6 +391,9 @@ public class LogManager { } } + // LoggerContext maps from AppContext + private static WeakHashMap contextsMap = null; + // Returns the LoggerContext for the user code (i.e. application or AppContext). // Loggers are isolated from each AppContext. private LoggerContext getUserContext() { @@ -399,33 +402,28 @@ public class LogManager { SecurityManager sm = System.getSecurityManager(); JavaAWTAccess javaAwtAccess = SharedSecrets.getJavaAWTAccess(); if (sm != null && javaAwtAccess != null) { + // for each applet, it has its own LoggerContext isolated from others synchronized (javaAwtAccess) { - // AppContext.getAppContext() returns the system AppContext if called - // from a system thread but Logger.getLogger might be called from - // an applet code. Instead, find the AppContext of the applet code - // from the execution stack. - Object ecx = javaAwtAccess.getExecutionContext(); - if (ecx == null) { - // fall back to thread group seach of AppContext - ecx = javaAwtAccess.getContext(); - } + // find the AppContext of the applet code + // will be null if we are in the main app context. + final Object ecx = javaAwtAccess.getAppletContext(); if (ecx != null) { - context = (LoggerContext)javaAwtAccess.get(ecx, LoggerContext.class); + if (contextsMap == null) { + contextsMap = new WeakHashMap<>(); + } + context = contextsMap.get(ecx); if (context == null) { - if (javaAwtAccess.isMainAppContext()) { - context = userContext; - } else { - // Create a new LoggerContext for the applet. - // The new logger context has its requiresDefaultLoggers - // flag set to true - so that these loggers will be - // lazily added when the context is firt accessed. - context = new LoggerContext(true); - } - javaAwtAccess.put(ecx, LoggerContext.class, context); + // Create a new LoggerContext for the applet. + // The new logger context has its requiresDefaultLoggers + // flag set to true - so that these loggers will be + // lazily added when the context is firt accessed. + context = new LoggerContext(true); + contextsMap.put(ecx, context); } } } } + // for standalone app, return userContext return context != null ? context : userContext; } diff --git a/jdk/src/share/classes/sun/awt/AppContext.java b/jdk/src/share/classes/sun/awt/AppContext.java index d4ed6525ad8..dbdc920dd5f 100644 --- a/jdk/src/share/classes/sun/awt/AppContext.java +++ b/jdk/src/share/classes/sun/awt/AppContext.java @@ -838,21 +838,59 @@ public final class AppContext { public boolean isMainAppContext() { return (numAppContexts.get() == 1 && mainAppContext != null); } - public Object getContext() { - return getAppContext(); - } - public Object getExecutionContext() { - return getExecutionAppContext(); - } - public Object get(Object context, Object key) { - return ((AppContext)context).get(key); - } - public void put(Object context, Object key, Object value) { - ((AppContext)context).put(key, value); - } - public void remove(Object context, Object key) { - ((AppContext)context).remove(key); + + /** + * Returns the AppContext used for applet logging isolation, or null if + * the default global context can be used. + * If there's no applet, or if the caller is a stand alone application, + * or running in the main app context, returns null. + * Otherwise, returns the AppContext of the calling applet. + * @return null if the global default context can be used, + * an AppContext otherwise. + **/ + public Object getAppletContext() { + // There's no AppContext: return null. + // No need to call getAppContext() if numAppContext == 0: + // it means that no AppContext has been created yet, and + // we don't want to trigger the creation of a main app + // context since we don't need it. + if (numAppContexts.get() == 0) return null; + + // Get the context from the security manager + AppContext ecx = getExecutionAppContext(); + + // Not sure we really need to re-check numAppContexts here. + // If all applets have gone away then we could have a + // numAppContexts coming back to 0. So we recheck + // it here because we don't want to trigger the + // creation of a main AppContext in that case. + // This is probably not 100% MT-safe but should reduce + // the window of opportunity in which that issue could + // happen. + if (numAppContexts.get() > 0) { + // Defaults to thread group caching. + // This is probably not required as we only really need + // isolation in a deployed applet environment, in which + // case ecx will not be null when we reach here + // However it helps emulate the deployed environment, + // in tests for instance. + ecx = ecx != null ? ecx : getAppContext(); + } + + // getAppletContext() may be called when initializing the main + // app context - in which case mainAppContext will still be + // null. To work around this issue we simply use + // AppContext.threadGroup.getParent() == null instead, since + // mainAppContext is the only AppContext which should have + // the root TG as its thread group. + // See: JDK-8023258 + final boolean isMainAppContext = ecx == null + || mainAppContext == ecx + || mainAppContext == null && ecx.threadGroup.getParent() == null; + + return isMainAppContext ? null : ecx; } + }); } } diff --git a/jdk/src/share/classes/sun/misc/JavaAWTAccess.java b/jdk/src/share/classes/sun/misc/JavaAWTAccess.java index e64a38b22c9..e0d3c384584 100644 --- a/jdk/src/share/classes/sun/misc/JavaAWTAccess.java +++ b/jdk/src/share/classes/sun/misc/JavaAWTAccess.java @@ -26,14 +26,16 @@ package sun.misc; public interface JavaAWTAccess { - public Object getContext(); - public Object getExecutionContext(); - public Object get(Object context, Object key); - public void put(Object context, Object key, Object value); - public void remove(Object context, Object key); + // Returns the AppContext used for applet logging isolation, or null if + // no isolation is required. + // If there's no applet, or if the caller is a stand alone application, + // or running in the main app context, returns null. + // Otherwise, returns the AppContext of the calling applet. + public Object getAppletContext(); - // convenience methods whose context is the object returned by getContext() + // convenience methods to cache objects in the current thread group's + // AppContext public Object get(Object key); public void put(Object key, Object value); public void remove(Object key); diff --git a/jdk/src/share/classes/sun/misc/SharedSecrets.java b/jdk/src/share/classes/sun/misc/SharedSecrets.java index 9248afa5ed3..bc2ab2e9ea6 100644 --- a/jdk/src/share/classes/sun/misc/SharedSecrets.java +++ b/jdk/src/share/classes/sun/misc/SharedSecrets.java @@ -170,7 +170,7 @@ public class SharedSecrets { public static JavaAWTAccess getJavaAWTAccess() { // this may return null in which case calling code needs to // provision for. - if (javaAWTAccess == null || javaAWTAccess.getContext() == null) { + if (javaAWTAccess == null) { return null; } return javaAWTAccess; diff --git a/jdk/test/java/util/logging/TestAppletLoggerContext.java b/jdk/test/java/util/logging/TestAppletLoggerContext.java index c7f3d4f4991..82c39381fe8 100644 --- a/jdk/test/java/util/logging/TestAppletLoggerContext.java +++ b/jdk/test/java/util/logging/TestAppletLoggerContext.java @@ -110,28 +110,19 @@ public class TestAppletLoggerContext { } TestExc exc; - TestExc global = new TestExc(); @Override - public Object getContext() { return active ? global : null; } + public Object getAppletContext() { return active ? exc : null; } @Override - public Object getExecutionContext() { return active ? exc : null; } + public Object get(Object o) { return exc.get(o); } @Override - public Object get(Object o, Object o1) { return TestExc.exc(o).get(o1); } + public void put(Object o, Object o1) { exc.put(o, o1); } @Override - public void put(Object o, Object o1, Object o2) { TestExc.exc(o).put(o1, o2); } - @Override - public void remove(Object o, Object o1) { TestExc.exc(o).remove(o1); } - @Override - public Object get(Object o) { return global.get(o); } - @Override - public void put(Object o, Object o1) { global.put(o, o1); } - @Override - public void remove(Object o) { global.remove(o); } + public void remove(Object o) { exc.remove(o); } @Override public boolean isDisposed() { return false; } @Override - public boolean isMainAppContext() { return exc == null; } + public boolean isMainAppContext() { return !active || exc == null; } } final static JavaAWTAccessStub javaAwtAccess = new JavaAWTAccessStub(); diff --git a/jdk/test/java/util/logging/TestLoggingWithMainAppContext.java b/jdk/test/java/util/logging/TestLoggingWithMainAppContext.java new file mode 100644 index 00000000000..5489acee22a --- /dev/null +++ b/jdk/test/java/util/logging/TestLoggingWithMainAppContext.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.logging.Logger; +import javax.imageio.ImageIO; + +/** + * @test + * @bug 8019853 8023258 + * @summary Test that the default user context is used when in the main + * application context. This test must not be run in same VM or agent + * VM mode: it would not test the intended behavior. + * @run main/othervm TestLoggingWithMainAppContext + */ +public class TestLoggingWithMainAppContext { + + public static void main(String[] args) throws IOException { + System.out.println("Creating loggers."); + + // These loggers will be created in the default user context. + final Logger foo1 = Logger.getLogger( "foo" ); + final Logger bar1 = Logger.getLogger( "foo.bar" ); + if (bar1.getParent() != foo1) { + throw new RuntimeException("Parent logger of bar1 "+bar1+" is not "+foo1); + } + System.out.println("bar1.getParent() is the same as foo1"); + + // Set a security manager + System.setSecurityManager(new SecurityManager()); + System.out.println("Now running with security manager"); + + // Triggers the creation of the main AppContext + ByteArrayInputStream is = new ByteArrayInputStream(new byte[] { 0, 1 }); + ImageIO.read(is); // triggers calls to system loggers & creation of main AppContext + + // verify that we're still using the default user context + final Logger bar2 = Logger.getLogger( "foo.bar" ); + if (bar1 != bar2) { + throw new RuntimeException("bar2 "+bar2+" is not the same as bar1 "+bar1); + } + System.out.println("bar2 is the same as bar1"); + if (bar2.getParent() != foo1) { + throw new RuntimeException("Parent logger of bar2 "+bar2+" is not foo1 "+foo1); + } + System.out.println("bar2.getParent() is the same as foo1"); + final Logger foo2 = Logger.getLogger("foo"); + if (foo1 != foo2) { + throw new RuntimeException("foo2 "+foo2+" is not the same as foo1 "+foo1); + } + System.out.println("foo2 is the same as foo1"); + + System.out.println("Test passed."); + } +} From e447689d41cc514db2c7f292785f6a22d7540609 Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Wed, 4 Sep 2013 12:35:22 -0700 Subject: [PATCH 21/75] 6341345: (spec) Console.reader() should make it clear that the reader requires line termination To clarify the spec Reviewed-by: alanb --- jdk/src/share/classes/java/io/Console.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jdk/src/share/classes/java/io/Console.java b/jdk/src/share/classes/java/io/Console.java index c100f8a0ccd..292e42eec7c 100644 --- a/jdk/src/share/classes/java/io/Console.java +++ b/jdk/src/share/classes/java/io/Console.java @@ -124,9 +124,11 @@ public final class Console implements Flushable * {@link java.io.Reader#read(java.nio.CharBuffer) read(java.nio.CharBuffer)} * on the returned object will not read in characters beyond the line * bound for each invocation, even if the destination buffer has space for - * more characters. A line bound is considered to be any one of a line feed - * ('\n'), a carriage return ('\r'), a carriage return - * followed immediately by a linefeed, or an end of stream. + * more characters. The {@code Reader}'s {@code read} methods may block if a + * line bound has not been entered or reached on the console's input device. + * A line bound is considered to be any one of a line feed ('\n'), + * a carriage return ('\r'), a carriage return followed immediately + * by a linefeed, or an end of stream. * * @return The reader associated with this console */ From db444fae3e243cfce00515d993f582d83e5f8768 Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Wed, 4 Sep 2013 12:37:41 -0700 Subject: [PATCH 22/75] 7186632: NLS t13y issue on jar.properties file To remove the redundant backslash Reviewed-by: naoto --- jdk/src/share/classes/sun/tools/jar/resources/jar.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk/src/share/classes/sun/tools/jar/resources/jar.properties b/jdk/src/share/classes/sun/tools/jar/resources/jar.properties index 852cac2889a..c8e8d9aecec 100644 --- a/jdk/src/share/classes/sun/tools/jar/resources/jar.properties +++ b/jdk/src/share/classes/sun/tools/jar/resources/jar.properties @@ -61,7 +61,7 @@ out.create=\ out.extracted=\ extracted: {0} out.inflated=\ - \ \inflated: {0} + \ inflated: {0} out.size=\ (in = {0}) (out= {1}) From b5cd24ccc9fda567c7a25c8088974b3a77800551 Mon Sep 17 00:00:00 2001 From: Robert Field Date: Wed, 4 Sep 2013 19:47:26 -0700 Subject: [PATCH 23/75] 8020816: Metafactory crashes on code with method reference 8021050: MethodHandleInfo throws exception when method handle is to a method with @CallerSensitive Fixed by 8008688 - this is a test to confirm the above fixed Reviewed-by: vlivanov --- .../MethodReferenceTestCallerSensitive.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 jdk/test/jdk/lambda/MethodReferenceTestCallerSensitive.java diff --git a/jdk/test/jdk/lambda/MethodReferenceTestCallerSensitive.java b/jdk/test/jdk/lambda/MethodReferenceTestCallerSensitive.java new file mode 100644 index 00000000000..805a6a203cb --- /dev/null +++ b/jdk/test/jdk/lambda/MethodReferenceTestCallerSensitive.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.annotations.Test; + +import java.lang.reflect.Field; +import java.util.function.Function; + + +/** + * @author Robert Field + */ + +@Test +public class MethodReferenceTestCallerSensitive { + + private static void getF(T arg) { + Function,Field[]> firstFunction = Class::getFields; + } + + public void testConstructorReferenceVarArgs() { + getF("Hello World"); + } + +} From 02d81bbc68127319cde94e6183557571484b6773 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Thu, 5 Sep 2013 13:04:17 +0200 Subject: [PATCH 24/75] 8023464: test/closed/sun/tracing/ProviderProxyTest.java failing Don't rely on assertions when an Exception suits better Reviewed-by: alanb, dfuchs, sjiang --- jdk/src/share/classes/sun/tracing/ProviderSkeleton.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jdk/src/share/classes/sun/tracing/ProviderSkeleton.java b/jdk/src/share/classes/sun/tracing/ProviderSkeleton.java index 2a5bc360430..f68f2650b35 100644 --- a/jdk/src/share/classes/sun/tracing/ProviderSkeleton.java +++ b/jdk/src/share/classes/sun/tracing/ProviderSkeleton.java @@ -164,7 +164,10 @@ public abstract class ProviderSkeleton implements InvocationHandler, Provider { declaringClass == Object.class) { return method.invoke(this, args); } else { - assert false; + // assert false : "this should never happen" + // reaching here would indicate a breach + // in security in the higher layers + throw new SecurityException(); } } catch (IllegalAccessException e) { assert false; From ea3200b7843e90ac8280feb04ce25a9482be548e Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Thu, 5 Sep 2013 14:34:22 +0200 Subject: [PATCH 25/75] 8004179: Few of test/java/lang/management/ThreadMXBean/* tests don't clean up the created threads Just run those tests in "othervm" mode. Reviewed-by: alanb, dfuchs, sjiang --- jdk/test/java/lang/management/ThreadMXBean/LockedMonitors.java | 2 +- .../java/lang/management/ThreadMXBean/LockedSynchronizers.java | 2 +- .../java/lang/management/ThreadMXBean/MyOwnSynchronizer.java | 2 +- .../java/lang/management/ThreadMXBean/SharedSynchronizer.java | 2 +- .../lang/management/ThreadMXBean/SynchronizationStatistics.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jdk/test/java/lang/management/ThreadMXBean/LockedMonitors.java b/jdk/test/java/lang/management/ThreadMXBean/LockedMonitors.java index 5c9747eaf9e..a693d71a841 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/LockedMonitors.java +++ b/jdk/test/java/lang/management/ThreadMXBean/LockedMonitors.java @@ -37,7 +37,7 @@ * @build Barrier * @build LockingThread * @build ThreadDump - * @run main LockedMonitors + * @run main/othervm LockedMonitors */ import java.lang.management.*; diff --git a/jdk/test/java/lang/management/ThreadMXBean/LockedSynchronizers.java b/jdk/test/java/lang/management/ThreadMXBean/LockedSynchronizers.java index b732d43f311..0fc35325926 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/LockedSynchronizers.java +++ b/jdk/test/java/lang/management/ThreadMXBean/LockedSynchronizers.java @@ -33,7 +33,7 @@ * @build Barrier * @build SynchronizerLockingThread * @build ThreadDump - * @run main LockedSynchronizers + * @run main/othervm LockedSynchronizers */ import java.lang.management.*; diff --git a/jdk/test/java/lang/management/ThreadMXBean/MyOwnSynchronizer.java b/jdk/test/java/lang/management/ThreadMXBean/MyOwnSynchronizer.java index 3968a5aab6f..c102583ddbf 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/MyOwnSynchronizer.java +++ b/jdk/test/java/lang/management/ThreadMXBean/MyOwnSynchronizer.java @@ -30,7 +30,7 @@ * * @build Barrier * @build ThreadDump - * @run main MyOwnSynchronizer + * @run main/othervm MyOwnSynchronizer */ import java.lang.management.*; diff --git a/jdk/test/java/lang/management/ThreadMXBean/SharedSynchronizer.java b/jdk/test/java/lang/management/ThreadMXBean/SharedSynchronizer.java index 6f5065dda03..6fc9e9ee0e5 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/SharedSynchronizer.java +++ b/jdk/test/java/lang/management/ThreadMXBean/SharedSynchronizer.java @@ -28,7 +28,7 @@ * in shared mode which has no owner when a thread is parked. * @author Mandy Chung * - * @run main SharedSynchronizer + * @run main/othervm SharedSynchronizer */ diff --git a/jdk/test/java/lang/management/ThreadMXBean/SynchronizationStatistics.java b/jdk/test/java/lang/management/ThreadMXBean/SynchronizationStatistics.java index ae65ca924a2..e03042864c9 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/SynchronizationStatistics.java +++ b/jdk/test/java/lang/management/ThreadMXBean/SynchronizationStatistics.java @@ -30,7 +30,7 @@ * * @ignore 6309226 * @build Semaphore - * @run main SynchronizationStatistics + * @run main/othervm SynchronizationStatistics */ import java.lang.management.*; From b1127783de054ea3c5ee6b719aa8aa9fe739003c Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 5 Sep 2013 10:14:53 -0700 Subject: [PATCH 26/75] 8023943: Method description fix for String.toLower/UpperCase() methods Reviewed-by: okutsu --- jdk/src/share/classes/java/lang/String.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdk/src/share/classes/java/lang/String.java b/jdk/src/share/classes/java/lang/String.java index e9d75afbbce..2231aa3bb71 100644 --- a/jdk/src/share/classes/java/lang/String.java +++ b/jdk/src/share/classes/java/lang/String.java @@ -2652,7 +2652,7 @@ public final class String * returns {@code "t\u005Cu0131tle"}, where '\u005Cu0131' is the * LATIN SMALL LETTER DOTLESS I character. * To obtain correct results for locale insensitive strings, use - * {@code toLowerCase(Locale.ENGLISH)}. + * {@code toLowerCase(Locale.ROOT)}. *

* @return the {@code String}, converted to lowercase. * @see java.lang.String#toLowerCase(Locale) @@ -2815,7 +2815,7 @@ public final class String * returns {@code "T\u005Cu0130TLE"}, where '\u005Cu0130' is the * LATIN CAPITAL LETTER I WITH DOT ABOVE character. * To obtain correct results for locale insensitive strings, use - * {@code toUpperCase(Locale.ENGLISH)}. + * {@code toUpperCase(Locale.ROOT)}. *

* @return the {@code String}, converted to uppercase. * @see java.lang.String#toUpperCase(Locale) From a277d40ead556c15e1869475da44cb00cd1ccf78 Mon Sep 17 00:00:00 2001 From: Vlaidmir Ivanov Date: Thu, 5 Sep 2013 14:58:49 -0700 Subject: [PATCH 27/75] 8024283: 10 nashorn tests fail with similar stack trace InternalError with cause being NoClassDefFoundError Fix pre-existing 292 bug tickled by combo of nashorn code and MethodHandleInfo changes Reviewed-by: jrose --- .../classes/java/lang/invoke/InvokerBytecodeGenerator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java index 3ddf5d4c1c5..6228c07b7b0 100644 --- a/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java +++ b/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java @@ -612,6 +612,12 @@ class InvokerBytecodeGenerator { return false; // inner class of some sort if (cls.getClassLoader() != MethodHandle.class.getClassLoader()) return false; // not on BCP + MethodType mtype = member.getMethodOrFieldType(); + if (!isStaticallyNameable(mtype.returnType())) + return false; + for (Class ptype : mtype.parameterArray()) + if (!isStaticallyNameable(ptype)) + return false; if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls)) return true; // in java.lang.invoke package if (member.isPublic() && isStaticallyNameable(cls)) From 37709e319182570f4f11fca0726a1d530ee7aba8 Mon Sep 17 00:00:00 2001 From: John Rose Date: Fri, 6 Sep 2013 00:43:00 -0700 Subject: [PATCH 28/75] 8024260: 10 closed/java/lang/invoke/* tests failing after overhaul to MethodHandleInfo Reviewed-by: vlivanov, briangoetz --- .../share/classes/java/lang/invoke/MethodHandle.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java index 408cbd08abc..613d223fe2a 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java @@ -1286,7 +1286,17 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString()); /*non-public*/ MethodHandle withInternalMemberName(MemberName member) { - return MethodHandleImpl.makeWrappedMember(this, member); + if (member != null) { + return MethodHandleImpl.makeWrappedMember(this, member); + } else if (internalMemberName() == null) { + // The required internaMemberName is null, and this MH (like most) doesn't have one. + return this; + } else { + // The following case is rare. Mask the internalMemberName by wrapping the MH in a BMH. + MethodHandle result = rebind(); + assert (result.internalMemberName() == null); + return result; + } } /*non-public*/ From 61000f0c4b9dda6152dc37e0f49fd5bceb8eb530 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Fri, 6 Sep 2013 10:03:16 +0200 Subject: [PATCH 29/75] 6815130: Intermittent ThreadMXBean/Locks.java test failure Preventing stale reads from ThreadExecutionSynchronizer.waiting flag Reviewed-by: dholmes, mchung, dfuchs --- .../lang/management/ThreadMXBean/Locks.java | 23 +++++++++---------- .../ThreadExecutionSynchronizer.java | 6 ++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/jdk/test/java/lang/management/ThreadMXBean/Locks.java b/jdk/test/java/lang/management/ThreadMXBean/Locks.java index 5969aebc6d0..c078dccd62d 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/Locks.java +++ b/jdk/test/java/lang/management/ThreadMXBean/Locks.java @@ -193,16 +193,18 @@ public class Locks { public CheckerThread() { super("CheckerThread"); } + + private void waitForState(Thread.State state) { + thrsync.waitForSignal(); + while (waiter.getState() != state) { + goSleep(10); + } + } + public void run() { synchronized (ready) { // wait until WaitingThread about to wait for objC - thrsync.waitForSignal(); - - int retryCount = 0; - while (waiter.getState() != Thread.State.WAITING - && retryCount++ < 500) { - goSleep(100); - } + waitForState(Thread.State.WAITING); checkBlockedObject(waiter, objC, null, Thread.State.WAITING); synchronized (objC) { @@ -211,16 +213,13 @@ public class Locks { // wait for waiter thread to about to enter // synchronized object ready. - thrsync.waitForSignal(); - // give chance for waiter thread to get blocked on - // object ready. - goSleep(50); + waitForState(Thread.State.BLOCKED); checkBlockedObject(waiter, ready, this, Thread.State.BLOCKED); } // wait for signal from waiting thread that it is about // wait for objC. - thrsync.waitForSignal(); + waitForState(Thread.State.WAITING); synchronized(objC) { checkBlockedObject(waiter, objC, Thread.currentThread(), Thread.State.WAITING); objC.notify(); diff --git a/jdk/test/java/lang/management/ThreadMXBean/ThreadExecutionSynchronizer.java b/jdk/test/java/lang/management/ThreadMXBean/ThreadExecutionSynchronizer.java index dc887829bdf..6cba7e73521 100644 --- a/jdk/test/java/lang/management/ThreadMXBean/ThreadExecutionSynchronizer.java +++ b/jdk/test/java/lang/management/ThreadMXBean/ThreadExecutionSynchronizer.java @@ -23,7 +23,7 @@ /* * - * @summary Thiseclass is used to synchronize execution off two threads. + * @summary This class is used to synchronize execution of two threads. * @author Swamy Venkataramanappa */ @@ -31,8 +31,8 @@ import java.util.concurrent.Semaphore; public class ThreadExecutionSynchronizer { - private boolean waiting; - private Semaphore semaphore; + private volatile boolean waiting; + private final Semaphore semaphore; public ThreadExecutionSynchronizer() { semaphore = new Semaphore(1); From 8007590d3bc9e355ca133f54d05f6e183d78c60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Borggr=C3=A9n-Franck?= Date: Fri, 6 Sep 2013 14:20:12 +0200 Subject: [PATCH 30/75] 5047859: (reflect) Class.getField can't find String[].length Reviewed-by: darcy, mchung --- jdk/src/share/classes/java/lang/Class.java | 71 ++++++++++++------- .../java/lang/Class/getField/ArrayLength.java | 64 +++++++++++++++++ 2 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 jdk/test/java/lang/Class/getField/ArrayLength.java diff --git a/jdk/src/share/classes/java/lang/Class.java b/jdk/src/share/classes/java/lang/Class.java index a3c962e0838..b8af59b7ae5 100644 --- a/jdk/src/share/classes/java/lang/Class.java +++ b/jdk/src/share/classes/java/lang/Class.java @@ -1484,22 +1484,24 @@ public final class Class implements java.io.Serializable, /** * Returns an array containing {@code Field} objects reflecting all * the accessible public fields of the class or interface represented by - * this {@code Class} object. The elements in the array returned are - * not sorted and are not in any particular order. This method returns an - * array of length 0 if the class or interface has no accessible public - * fields, or if it represents an array class, a primitive type, or void. + * this {@code Class} object. * - *

Specifically, if this {@code Class} object represents a class, - * this method returns the public fields of this class and of all its - * superclasses. If this {@code Class} object represents an - * interface, this method returns the fields of this interface and of all - * its superinterfaces. + *

If this {@code Class} object represents a class or interface with no + * no accessible public fields, then this method returns an array of length + * 0. * - *

The implicit length field for array class is not reflected by this - * method. User code should use the methods of class {@code Array} to - * manipulate arrays. + *

If this {@code Class} object represents a class, then this method + * returns the public fields of the class and of all its superclasses. * - *

See The Java Language Specification, sections 8.2 and 8.3. + *

If this {@code Class} object represents an interface, then this + * method returns the fields of the interface and of all its + * superinterfaces. + * + *

If this {@code Class} object represents an array type, a primitive + * type, or void, then this method returns an array of length 0. + * + *

The elements in the array returned are not sorted and are not in any + * particular order. * * @return the array of {@code Field} objects representing the * public fields @@ -1512,6 +1514,8 @@ public final class Class implements java.io.Serializable, * of this class. * * @since JDK1.1 + * @jls 8.2 Class Members + * @jls 8.3 Field Declarations */ @CallerSensitive public Field[] getFields() throws SecurityException { @@ -1595,13 +1599,14 @@ public final class Class implements java.io.Serializable, /** - * Returns a {@code Field} object that reflects the specified public - * member field of the class or interface represented by this - * {@code Class} object. The {@code name} parameter is a - * {@code String} specifying the simple name of the desired field. + * Returns a {@code Field} object that reflects the specified public member + * field of the class or interface represented by this {@code Class} + * object. The {@code name} parameter is a {@code String} specifying the + * simple name of the desired field. * *

The field to be reflected is determined by the algorithm that - * follows. Let C be the class represented by this object: + * follows. Let C be the class or interface represented by this object: + * *

    *
  1. If C declares a public field with the name specified, that is the * field to be reflected.
  2. @@ -1614,7 +1619,8 @@ public final class Class implements java.io.Serializable, * is thrown. *
* - *

See The Java Language Specification, sections 8.2 and 8.3. + *

If this {@code Class} object represents an array type, then this + * method does not find the {@code length} field of the array type. * * @param name the field name * @return the {@code Field} object of this class specified by @@ -1631,6 +1637,8 @@ public final class Class implements java.io.Serializable, * of this class. * * @since JDK1.1 + * @jls 8.2 Class Members + * @jls 8.3 Field Declarations */ @CallerSensitive public Field getField(String name) @@ -1800,12 +1808,15 @@ public final class Class implements java.io.Serializable, * declared by the class or interface represented by this * {@code Class} object. This includes public, protected, default * (package) access, and private fields, but excludes inherited fields. - * The elements in the array returned are not sorted and are not in any - * particular order. This method returns an array of length 0 if the class - * or interface declares no fields, or if this {@code Class} object - * represents a primitive type, an array class, or void. * - *

See The Java Language Specification, sections 8.2 and 8.3. + *

If this {@code Class} object represents a class or interface with no + * declared fields, then this method returns an array of length 0. + * + *

If this {@code Class} object represents an array type, a primitive + * type, or void, then this method returns an array of length 0. + * + *

The elements in the array returned are not sorted and are not in any + * particular order. * * @return the array of {@code Field} objects representing all the * declared fields of this class @@ -1831,6 +1842,8 @@ public final class Class implements java.io.Serializable, * * * @since JDK1.1 + * @jls 8.2 Class Members + * @jls 8.3 Field Declarations */ @CallerSensitive public Field[] getDeclaredFields() throws SecurityException { @@ -1935,9 +1948,11 @@ public final class Class implements java.io.Serializable, /** * Returns a {@code Field} object that reflects the specified declared * field of the class or interface represented by this {@code Class} - * object. The {@code name} parameter is a {@code String} that - * specifies the simple name of the desired field. Note that this method - * will not reflect the {@code length} field of an array class. + * object. The {@code name} parameter is a {@code String} that specifies + * the simple name of the desired field. + * + *

If this {@code Class} object represents an array type, then this + * method does not find the {@code length} field of the array type. * * @param name the name of the field * @return the {@code Field} object for the specified field in this @@ -1967,6 +1982,8 @@ public final class Class implements java.io.Serializable, * * * @since JDK1.1 + * @jls 8.2 Class Members + * @jls 8.3 Field Declarations */ @CallerSensitive public Field getDeclaredField(String name) diff --git a/jdk/test/java/lang/Class/getField/ArrayLength.java b/jdk/test/java/lang/Class/getField/ArrayLength.java new file mode 100644 index 00000000000..c03a772c043 --- /dev/null +++ b/jdk/test/java/lang/Class/getField/ArrayLength.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 5047859 + * @summary verify that for an array type class instance, getField("length") + * throws an exception, and getFields() does not contain a Field for + * 'length' + */ + +import java.lang.reflect.Field; + +public class ArrayLength { + public static void main(String [] args) { + int failed = 0; + + try { + new String[0].getClass().getField("length"); + failed++; + System.out.println("getField(\"length\") should throw NoSuchFieldException"); + } catch (NoSuchFieldException e) { + } + try { + new String[0].getClass().getDeclaredField("length"); + failed++; + System.out.println("getDeclaredField(\"length\") should throw NoSuchFieldException"); + } catch (NoSuchFieldException e) { + } + + if (new String[0].getClass().getFields().length != 0) { + failed++; + System.out.println("getFields() for an array type should return a zero length array"); + } + + if (new String[0].getClass().getDeclaredFields().length != 0) { + failed++; + System.out.println("getDeclaredFields() for an array type should return a zero length array"); + } + + if (failed != 0) + throw new RuntimeException("Test failed see log for details"); + } +} From 8b9c8247a80ff8d9954024e12177adf1eba71fff Mon Sep 17 00:00:00 2001 From: Xue-Lei Andrew Fan Date: Sat, 7 Sep 2013 17:05:22 -0700 Subject: [PATCH 31/75] 7188657: There should be a way to reorder the JSSE ciphers Reviewed-by: weijun, wetmore --- .../classes/javax/net/ssl/SSLParameters.java | 36 +- .../classes/sun/security/ssl/Handshaker.java | 31 +- .../sun/security/ssl/SSLEngineImpl.java | 10 + .../sun/security/ssl/SSLServerSocketImpl.java | 13 +- .../sun/security/ssl/SSLSocketImpl.java | 14 +- .../sun/security/ssl/ServerHandshaker.java | 17 +- .../SSLParameters/UseCipherSuitesOrder.java | 357 ++++++++++++++++++ 7 files changed, 466 insertions(+), 12 deletions(-) create mode 100644 jdk/test/sun/security/ssl/javax/net/ssl/SSLParameters/UseCipherSuitesOrder.java diff --git a/jdk/src/share/classes/javax/net/ssl/SSLParameters.java b/jdk/src/share/classes/javax/net/ssl/SSLParameters.java index 1dc6f3315d0..5e0d403cd39 100644 --- a/jdk/src/share/classes/javax/net/ssl/SSLParameters.java +++ b/jdk/src/share/classes/javax/net/ssl/SSLParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,7 @@ import java.util.LinkedHashMap; * the list of protocols to be allowed, the endpoint identification * algorithm during SSL/TLS handshaking, the Server Name Indication (SNI), * the algorithm constraints and whether SSL/TLS servers should request - * or require client authentication. + * or require client authentication, etc. *

* SSLParameters can be created via the constructors in this class. * Objects can also be obtained using the getSSLParameters() @@ -73,13 +73,14 @@ public class SSLParameters { private AlgorithmConstraints algorithmConstraints; private Map sniNames = null; private Map sniMatchers = null; + private boolean preferLocalCipherSuites; /** * Constructs SSLParameters. *

* The values of cipherSuites, protocols, cryptographic algorithm * constraints, endpoint identification algorithm, server names and - * server name matchers are set to null, + * server name matchers are set to null, useCipherSuitesOrder, * wantClientAuth and needClientAuth are set to false. */ public SSLParameters() { @@ -434,5 +435,34 @@ public class SSLParameters { return null; } + + /** + * Sets whether the local cipher suites preference should be honored. + * + * @param honorOrder whether local cipher suites order in + * {@code #getCipherSuites} should be honored during + * SSL/TLS handshaking. + * + * @see #getUseCipherSuitesOrder() + * + * @since 1.8 + */ + public final void setUseCipherSuitesOrder(boolean honorOrder) { + this.preferLocalCipherSuites = honorOrder; + } + + /** + * Returns whether the local cipher suites preference should be honored. + * + * @return whether local cipher suites order in {@code #getCipherSuites} + * should be honored during SSL/TLS handshaking. + * + * @see #setUseCipherSuitesOrder(boolean) + * + * @since 1.8 + */ + public final boolean getUseCipherSuitesOrder() { + return preferLocalCipherSuites; + } } diff --git a/jdk/src/share/classes/sun/security/ssl/Handshaker.java b/jdk/src/share/classes/sun/security/ssl/Handshaker.java index 17b1f92ac74..a92320451e0 100644 --- a/jdk/src/share/classes/sun/security/ssl/Handshaker.java +++ b/jdk/src/share/classes/sun/security/ssl/Handshaker.java @@ -145,6 +145,14 @@ abstract class Handshaker { /* True if it's OK to start a new SSL session */ boolean enableNewSession; + // Whether local cipher suites preference should be honored during + // handshaking? + // + // Note that in this provider, this option only applies to server side. + // Local cipher suites preference is always honored in client side in + // this provider. + boolean preferLocalCipherSuites = false; + // Temporary storage for the individual keys. Set by // calculateConnectionKeys() and cleared once the ciphers are // activated. @@ -462,6 +470,13 @@ abstract class Handshaker { this.sniMatchers = sniMatchers; } + /** + * Sets the cipher suites preference. + */ + void setUseCipherSuitesOrder(boolean on) { + this.preferLocalCipherSuites = on; + } + /** * Prior to handshaking, activate the handshake and initialize the version, * input stream and output stream. @@ -533,7 +548,9 @@ abstract class Handshaker { } /** - * Check if the given ciphersuite is enabled and available. + * Check if the given ciphersuite is enabled and available within the + * current active cipher suites. + * * Does not check if the required server certificates are available. */ boolean isNegotiable(CipherSuite s) { @@ -541,7 +558,17 @@ abstract class Handshaker { activeCipherSuites = getActiveCipherSuites(); } - return activeCipherSuites.contains(s) && s.isNegotiable(); + return isNegotiable(activeCipherSuites, s); + } + + /** + * Check if the given ciphersuite is enabled and available within the + * proposed cipher suite list. + * + * Does not check if the required server certificates are available. + */ + final static boolean isNegotiable(CipherSuiteList proposed, CipherSuite s) { + return proposed.contains(s) && s.isNegotiable(); } /** diff --git a/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java index c2e94f3b401..5302b6147b4 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java @@ -319,6 +319,12 @@ final public class SSLEngineImpl extends SSLEngine { */ private boolean isFirstAppOutputRecord = true; + /* + * Whether local cipher suites preference in server side should be + * honored during handshaking? + */ + private boolean preferLocalCipherSuites = false; + /* * Class and subclass dynamic debugging support */ @@ -470,6 +476,7 @@ final public class SSLEngineImpl extends SSLEngine { protocolVersion, connectionState == cs_HANDSHAKE, secureRenegotiation, clientVerifyData, serverVerifyData); handshaker.setSNIMatchers(sniMatchers); + handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites); } else { handshaker = new ClientHandshaker(this, sslContext, enabledProtocols, @@ -2074,6 +2081,7 @@ final public class SSLEngineImpl extends SSLEngine { params.setAlgorithmConstraints(algorithmConstraints); params.setSNIMatchers(sniMatchers); params.setServerNames(serverNames); + params.setUseCipherSuitesOrder(preferLocalCipherSuites); return params; } @@ -2088,6 +2096,7 @@ final public class SSLEngineImpl extends SSLEngine { // the super implementation does not handle the following parameters identificationProtocol = params.getEndpointIdentificationAlgorithm(); algorithmConstraints = params.getAlgorithmConstraints(); + preferLocalCipherSuites = params.getUseCipherSuitesOrder(); List sniNames = params.getServerNames(); if (sniNames != null) { @@ -2104,6 +2113,7 @@ final public class SSLEngineImpl extends SSLEngine { handshaker.setAlgorithmConstraints(algorithmConstraints); if (roleIsServer) { handshaker.setSNIMatchers(sniMatchers); + handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites); } else { handshaker.setSNIServerNames(serverNames); } diff --git a/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java index 0e0edf18e4a..464bab23ad4 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLServerSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,6 +92,12 @@ class SSLServerSocketImpl extends SSLServerSocket Collection sniMatchers = Collections.emptyList(); + /* + * Whether local cipher suites preference in server side should be + * honored during handshaking? + */ + private boolean preferLocalCipherSuites = false; + /** * Create an SSL server socket on a port, using a non-default * authentication context and a specified connection backlog. @@ -304,6 +310,8 @@ class SSLServerSocketImpl extends SSLServerSocket params.setEndpointIdentificationAlgorithm(identificationProtocol); params.setAlgorithmConstraints(algorithmConstraints); params.setSNIMatchers(sniMatchers); + params.setUseCipherSuitesOrder(preferLocalCipherSuites); + return params; } @@ -318,6 +326,7 @@ class SSLServerSocketImpl extends SSLServerSocket // the super implementation does not handle the following parameters identificationProtocol = params.getEndpointIdentificationAlgorithm(); algorithmConstraints = params.getAlgorithmConstraints(); + preferLocalCipherSuites = params.getUseCipherSuitesOrder(); Collection matchers = params.getSNIMatchers(); if (matchers != null) { sniMatchers = params.getSNIMatchers(); @@ -334,7 +343,7 @@ class SSLServerSocketImpl extends SSLServerSocket SSLSocketImpl s = new SSLSocketImpl(sslContext, useServerMode, enabledCipherSuites, doClientAuth, enableSessionCreation, enabledProtocols, identificationProtocol, algorithmConstraints, - sniMatchers); + sniMatchers, preferLocalCipherSuites); implAccept(s); s.doneConnect(); diff --git a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java index dfe612966e0..4d591e3d7a5 100644 --- a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -377,6 +377,12 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { */ private ByteArrayOutputStream heldRecordBuffer = null; + /* + * Whether local cipher suites preference in server side should be + * honored during handshaking? + */ + private boolean preferLocalCipherSuites = false; + // // CONSTRUCTORS AND INITIALIZATION CODE // @@ -482,7 +488,8 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { boolean sessionCreation, ProtocolList protocols, String identificationProtocol, AlgorithmConstraints algorithmConstraints, - Collection sniMatchers) throws IOException { + Collection sniMatchers, + boolean preferLocalCipherSuites) throws IOException { super(); doClientAuth = clientAuth; @@ -490,6 +497,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { this.identificationProtocol = identificationProtocol; this.algorithmConstraints = algorithmConstraints; this.sniMatchers = sniMatchers; + this.preferLocalCipherSuites = preferLocalCipherSuites; init(context, serverMode); /* @@ -1284,6 +1292,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { protocolVersion, connectionState == cs_HANDSHAKE, secureRenegotiation, clientVerifyData, serverVerifyData); handshaker.setSNIMatchers(sniMatchers); + handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites); } else { handshaker = new ClientHandshaker(this, sslContext, enabledProtocols, @@ -2502,6 +2511,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { params.setAlgorithmConstraints(algorithmConstraints); params.setSNIMatchers(sniMatchers); params.setServerNames(serverNames); + params.setUseCipherSuitesOrder(preferLocalCipherSuites); return params; } @@ -2516,6 +2526,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { // the super implementation does not handle the following parameters identificationProtocol = params.getEndpointIdentificationAlgorithm(); algorithmConstraints = params.getAlgorithmConstraints(); + preferLocalCipherSuites = params.getUseCipherSuitesOrder(); List sniNames = params.getServerNames(); if (sniNames != null) { @@ -2532,6 +2543,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl { handshaker.setAlgorithmConstraints(algorithmConstraints); if (roleIsServer) { handshaker.setSNIMatchers(sniMatchers); + handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites); } else { handshaker.setSNIServerNames(serverNames); } diff --git a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java index e317e1f18c5..23b806f33e2 100644 --- a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java +++ b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java @@ -932,8 +932,18 @@ final class ServerHandshaker extends Handshaker { * the cipherSuite and keyExchange variables. */ private void chooseCipherSuite(ClientHello mesg) throws IOException { - for (CipherSuite suite : mesg.getCipherSuites().collection()) { - if (isNegotiable(suite) == false) { + CipherSuiteList prefered; + CipherSuiteList proposed; + if (preferLocalCipherSuites) { + prefered = getActiveCipherSuites(); + proposed = mesg.getCipherSuites(); + } else { + prefered = mesg.getCipherSuites(); + proposed = getActiveCipherSuites(); + } + + for (CipherSuite suite : prefered.collection()) { + if (isNegotiable(proposed, suite) == false) { continue; } @@ -948,8 +958,7 @@ final class ServerHandshaker extends Handshaker { } return; } - fatalSE(Alerts.alert_handshake_failure, - "no cipher suites in common"); + fatalSE(Alerts.alert_handshake_failure, "no cipher suites in common"); } /** diff --git a/jdk/test/sun/security/ssl/javax/net/ssl/SSLParameters/UseCipherSuitesOrder.java b/jdk/test/sun/security/ssl/javax/net/ssl/SSLParameters/UseCipherSuitesOrder.java new file mode 100644 index 00000000000..c25d74c7d2c --- /dev/null +++ b/jdk/test/sun/security/ssl/javax/net/ssl/SSLParameters/UseCipherSuitesOrder.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 7188657 + * @summary There should be a way to reorder the JSSE ciphers + * @run main/othervm UseCipherSuitesOrder + * TLS_RSA_WITH_AES_128_CBC_SHA,SSL_RSA_WITH_RC4_128_SHA + */ + +import java.io.*; +import java.net.*; +import javax.net.ssl.*; +import java.util.Arrays; + +public class UseCipherSuitesOrder { + + /* + * ============================================================= + * Set the various variables needed for the tests, then + * specify what tests to run on each side. + */ + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + static boolean separateServerThread = false; + + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + /* + * Is the server ready to serve? + */ + volatile static boolean serverReady = false; + + /* + * Turn on SSL debugging? + */ + static boolean debug = false; + + /* + * If the client or server is doing some kind of object creation + * that the other side depends on, and that thread prematurely + * exits, you may experience a hang. The test harness will + * terminate all hung threads after its timeout has expired, + * currently 3 minutes by default, but you might try to be + * smart about it.... + */ + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + SSLServerSocketFactory sslssf = + (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket(serverPort); + serverPort = sslServerSocket.getLocalPort(); + + // use local cipher suites preference + SSLParameters params = sslServerSocket.getSSLParameters(); + params.setUseCipherSuitesOrder(true); + params.setCipherSuites(srvEnabledCipherSuites); + sslServerSocket.setSSLParameters(params); + + /* + * Signal Client, we're ready for his connect. + */ + serverReady = true; + + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + + SSLSession session = sslSocket.getSession(); + if (!srvEnabledCipherSuites[0].equals(session.getCipherSuite())) { + throw new Exception( + "Expected to negotiate " + srvEnabledCipherSuites[0] + + " , but not " + session.getCipherSuite()); + } + + sslSocket.close(); + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + while (!serverReady) { + Thread.sleep(50); + } + + SSLSocketFactory sslsf = + (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) + sslsf.createSocket("localhost", serverPort); + sslSocket.setEnabledCipherSuites(cliEnabledCipherSuites); + + InputStream sslIS = sslSocket.getInputStream(); + OutputStream sslOS = sslSocket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + + sslSocket.close(); + } + + // client enabled cipher suites + private static String[] cliEnabledCipherSuites; + + // server enabled cipher suites + private static String[] srvEnabledCipherSuites; + + private static void parseArguments(String[] args) throws Exception { + if (args.length != 1) { + System.out.println("Usage: java UseCipherSuitesOrder ciphersuites"); + System.out.println("\tciphersuites: " + + "a list of enabled cipher suites, separated with comma"); + throw new Exception("Incorrect usage"); + } + + cliEnabledCipherSuites = args[0].split(","); + + if (cliEnabledCipherSuites.length < 2) { + throw new Exception("Need to enable at least two cipher suites"); + } + + // Only need to use 2 cipher suites in server side. + srvEnabledCipherSuites = Arrays.copyOf( + cliEnabledCipherSuites, 2); + + // Reverse the cipher suite preference in server side. + srvEnabledCipherSuites[0] = cliEnabledCipherSuites[1]; + srvEnabledCipherSuites[1] = cliEnabledCipherSuites[0]; + } + + /* + * ============================================================= + * The remainder is just support stuff + */ + + // use any free port by default + volatile int serverPort = 0; + + volatile Exception serverException = null; + volatile Exception clientException = null; + + public static void main(String[] args) throws Exception { + // parse the arguments + parseArguments(args); + + String keyFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", ".") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new UseCipherSuitesOrder(); + } + + Thread clientThread = null; + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + UseCipherSuitesOrder() throws Exception { + Exception startException = null; + try { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + } catch (Exception e) { + startException = e; + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + if (serverThread != null) { + serverThread.join(); + } + } else { + if (clientThread != null) { + clientThread.join(); + } + } + + /* + * When we get here, the test is pretty much over. + * Which side threw the error? + */ + Exception local; + Exception remote; + + if (separateServerThread) { + remote = serverException; + local = clientException; + } else { + remote = clientException; + local = serverException; + } + + Exception exception = null; + + /* + * Check various exception conditions. + */ + if ((local != null) && (remote != null)) { + // If both failed, return the curthread's exception. + local.initCause(remote); + exception = local; + } else if (local != null) { + exception = local; + } else if (remote != null) { + exception = remote; + } else if (startException != null) { + exception = startException; + } + + /* + * If there was an exception *AND* a startException, + * output it. + */ + if (exception != null) { + if (exception != startException && startException != null) { + exception.addSuppressed(startException); + } + throw exception; + } + + // Fall-through: no exception to throw! + } + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + doServerSide(); + } catch (Exception e) { + serverException = e; + } finally { + serverReady = true; + } + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + doClientSide(); + } catch (Exception e) { + clientException = e; + } + } + } +} From a4c7971bdb559d5b0e8d09f145da3a25c78f5dce Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Mon, 9 Sep 2013 11:08:20 +0800 Subject: [PATCH 32/75] 8024046: Test sun/security/krb5/runNameEquals.sh failed on 7u45 Embedded linux-ppc* Reviewed-by: xuelei --- jdk/test/sun/security/krb5/runNameEquals.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/jdk/test/sun/security/krb5/runNameEquals.sh b/jdk/test/sun/security/krb5/runNameEquals.sh index 6db90c35315..b9cfb08d8b5 100644 --- a/jdk/test/sun/security/krb5/runNameEquals.sh +++ b/jdk/test/sun/security/krb5/runNameEquals.sh @@ -22,7 +22,7 @@ # # @test -# @bug 6317711 6944847 +# @bug 6317711 6944847 8024046 # @summary Ensure the GSSName has the correct impl which respects # the contract for equals and hashCode across different configurations. @@ -56,6 +56,15 @@ case "$OS" in PATHSEP=":" FILESEP="/" NATIVE=true + # Not all *nix has native GSS libs installed + krb5-config --libs gssapi 2> /dev/null + if [ $? != 0 ]; then + # Fedora has a different path + /usr/kerberos/bin/krb5-config --libs gssapi 2> /dev/null + if [ $? != 0 ]; then + NATIVE=false + fi + fi ;; CYGWIN* ) PATHSEP=";" From 33dbc2d51c62d6e6657ffbf64c026426bd6f072c Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 9 Sep 2013 13:59:51 +0200 Subject: [PATCH 33/75] 8023168: Cleanup LogManager class initialization and LogManager/LoggerContext relationship 8021003: java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java fails intermittently 8019945: test/java/util/logging/LogManagerInstanceTest.java failing intermittently This fix untangles the class initialization of Logger and LogManager, and also cleans up the relationship between LogManager, LoggerContext, and Logger, which were at the root cause of some intermittent test failures. Reviewed-by: mchung, martin, plevart --- .../classes/java/util/logging/LogManager.java | 310 ++++++++++++------ .../classes/java/util/logging/Logger.java | 42 ++- .../Logger/getGlobal/TestGetGlobal.java | 10 +- .../getGlobal/TestGetGlobalConcurrent.java | 26 +- .../java/util/logging/Logger/getGlobal/policy | 1 + .../java/util/logging/ParentLoggersTest.java | 2 +- .../util/logging/TestAppletLoggerContext.java | 151 ++++----- 7 files changed, 334 insertions(+), 208 deletions(-) diff --git a/jdk/src/share/classes/java/util/logging/LogManager.java b/jdk/src/share/classes/java/util/logging/LogManager.java index 717e52990e1..8596cbeb8fe 100644 --- a/jdk/src/share/classes/java/util/logging/LogManager.java +++ b/jdk/src/share/classes/java/util/logging/LogManager.java @@ -144,7 +144,7 @@ import sun.misc.SharedSecrets; public class LogManager { // The global LogManager object - private static LogManager manager; + private static final LogManager manager; private Properties props = new Properties(); private final static Level defaultLevel = Level.INFO; @@ -156,8 +156,10 @@ public class LogManager { // LoggerContext for system loggers and user loggers private final LoggerContext systemContext = new SystemLoggerContext(); private final LoggerContext userContext = new LoggerContext(); - private Logger rootLogger; - + // non final field - make it volatile to make sure that other threads + // will see the new value once ensureLogManagerInitialized() has finished + // executing. + private volatile Logger rootLogger; // Have we done the primordial reading of the configuration file? // (Must be done after a suitable amount of java.lang.System // initialization has been done) @@ -169,58 +171,35 @@ public class LogManager { private boolean deathImminent; static { - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - String cname = null; - try { - cname = System.getProperty("java.util.logging.manager"); - if (cname != null) { - try { - Class clz = ClassLoader.getSystemClassLoader().loadClass(cname); - manager = (LogManager) clz.newInstance(); - } catch (ClassNotFoundException ex) { - Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname); - manager = (LogManager) clz.newInstance(); - } + manager = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public LogManager run() { + LogManager mgr = null; + String cname = null; + try { + cname = System.getProperty("java.util.logging.manager"); + if (cname != null) { + try { + Class clz = ClassLoader.getSystemClassLoader() + .loadClass(cname); + mgr = (LogManager) clz.newInstance(); + } catch (ClassNotFoundException ex) { + Class clz = Thread.currentThread() + .getContextClassLoader().loadClass(cname); + mgr = (LogManager) clz.newInstance(); } - } catch (Exception ex) { - System.err.println("Could not load Logmanager \"" + cname + "\""); - ex.printStackTrace(); } - if (manager == null) { - manager = new LogManager(); - } - - // Create and retain Logger for the root of the namespace. - manager.rootLogger = manager.new RootLogger(); - // since by design the global manager's userContext and - // systemContext don't have their requiresDefaultLoggers - // flag set - we make sure to add the root logger to - // the global manager's default contexts here. - manager.addLogger(manager.rootLogger); - manager.systemContext.addLocalLogger(manager.rootLogger, false); - manager.userContext.addLocalLogger(manager.rootLogger, false); - - // Adding the global Logger. Doing so in the Logger. - // would deadlock with the LogManager.. - // Do not call Logger.getGlobal() here as this might trigger - // the deadlock too. - @SuppressWarnings("deprecation") - final Logger global = Logger.global; - global.setLogManager(manager); - - // Make sure the global logger will be registered in the - // global manager's default contexts. - manager.addLogger(global); - manager.systemContext.addLocalLogger(global, false); - manager.userContext.addLocalLogger(global, false); - - // We don't call readConfiguration() here, as we may be running - // very early in the JVM startup sequence. Instead readConfiguration - // will be called lazily in getLogManager(). - return null; + } catch (Exception ex) { + System.err.println("Could not load Logmanager \"" + cname + "\""); + ex.printStackTrace(); } - }); + if (mgr == null) { + mgr = new LogManager(); + } + return mgr; + + } + }); } @@ -235,6 +214,7 @@ public class LogManager { this.setContextClassLoader(null); } + @Override public void run() { // This is to ensure the LogManager. is completed // before synchronized block. Otherwise deadlocks are possible. @@ -270,13 +250,104 @@ public class LogManager { } } + /** + * Lazy initialization: if this instance of manager is the global + * manager then this method will read the initial configuration and + * add the root logger and global logger by calling addLogger(). + * + * Note that it is subtly different from what we do in LoggerContext. + * In LoggerContext we're patching up the logger context tree in order to add + * the root and global logger *to the context tree*. + * + * For this to work, addLogger() must have already have been called + * once on the LogManager instance for the default logger being + * added. + * + * This is why ensureLogManagerInitialized() needs to be called before + * any logger is added to any logger context. + * + */ + private boolean initializedCalled = false; + private volatile boolean initializationDone = false; + final void ensureLogManagerInitialized() { + final LogManager owner = this; + if (initializationDone || owner != manager) { + // we don't want to do this twice, and we don't want to do + // this on private manager instances. + return; + } + + // Maybe another thread has called ensureLogManagerInitialized() + // before us and is still executing it. If so we will block until + // the log manager has finished initialized, then acquire the monitor, + // notice that initializationDone is now true and return. + // Otherwise - we have come here first! We will acquire the monitor, + // see that initializationDone is still false, and perform the + // initialization. + // + synchronized(this) { + // If initializedCalled is true it means that we're already in + // the process of initializing the LogManager in this thread. + // There has been a recursive call to ensureLogManagerInitialized(). + final boolean isRecursiveInitialization = (initializedCalled == true); + + assert initializedCalled || !initializationDone + : "Initialization can't be done if initialized has not been called!"; + + if (isRecursiveInitialization || initializationDone) { + // If isRecursiveInitialization is true it means that we're + // already in the process of initializing the LogManager in + // this thread. There has been a recursive call to + // ensureLogManagerInitialized(). We should not proceed as + // it would lead to infinite recursion. + // + // If initializationDone is true then it means the manager + // has finished initializing; just return: we're done. + return; + } + // Calling addLogger below will in turn call requiresDefaultLogger() + // which will call ensureLogManagerInitialized(). + // We use initializedCalled to break the recursion. + initializedCalled = true; + try { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + assert rootLogger == null; + assert initializedCalled && !initializationDone; + + // Read configuration. + owner.readPrimordialConfiguration(); + + // Create and retain Logger for the root of the namespace. + owner.rootLogger = owner.new RootLogger(); + owner.addLogger(owner.rootLogger); + + // Adding the global Logger. + // Do not call Logger.getGlobal() here as this might trigger + // subtle inter-dependency issues. + @SuppressWarnings("deprecation") + final Logger global = Logger.global; + + // Make sure the global logger will be registered in the + // global manager + owner.addLogger(global); + return null; + } + }); + } finally { + initializationDone = true; + } + } + } + /** * Returns the global LogManager object. * @return the global LogManager object */ public static LogManager getLogManager() { if (manager != null) { - manager.readPrimordialConfiguration(); + manager.ensureLogManagerInitialized(); } return manager; } @@ -295,6 +366,7 @@ public class LogManager { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { readConfiguration(); @@ -304,8 +376,7 @@ public class LogManager { } }); } catch (Exception ex) { - // System.err.println("Can't read logging configuration:"); - // ex.printStackTrace(); + assert false : "Exception raised while reading logging configuration: " + ex; } } } @@ -392,7 +463,7 @@ public class LogManager { } // LoggerContext maps from AppContext - private static WeakHashMap contextsMap = null; + private WeakHashMap contextsMap = null; // Returns the LoggerContext for the user code (i.e. application or AppContext). // Loggers are isolated from each AppContext. @@ -414,10 +485,7 @@ public class LogManager { context = contextsMap.get(ecx); if (context == null) { // Create a new LoggerContext for the applet. - // The new logger context has its requiresDefaultLoggers - // flag set to true - so that these loggers will be - // lazily added when the context is firt accessed. - context = new LoggerContext(true); + context = new LoggerContext(); contextsMap.put(ecx, context); } } @@ -427,9 +495,14 @@ public class LogManager { return context != null ? context : userContext; } + // The system context. + final LoggerContext getSystemContext() { + return systemContext; + } + private List contexts() { List cxs = new ArrayList<>(); - cxs.add(systemContext); + cxs.add(getSystemContext()); cxs.add(getUserContext()); return cxs; } @@ -450,7 +523,7 @@ public class LogManager { Logger result = getLogger(name); if (result == null) { // only allocate the new logger once - Logger newLogger = new Logger(name, resourceBundleName, caller); + Logger newLogger = new Logger(name, resourceBundleName, caller, this); do { if (addLogger(newLogger)) { // We successfully added the new Logger that we @@ -477,7 +550,7 @@ public class LogManager { Logger demandSystemLogger(String name, String resourceBundleName) { // Add a system logger in the system context's namespace - final Logger sysLogger = systemContext.demandLogger(name, resourceBundleName); + final Logger sysLogger = getSystemContext().demandLogger(name, resourceBundleName); // Add the system logger to the LogManager's namespace if not exist // so that there is only one single logger of the given name. @@ -501,6 +574,7 @@ public class LogManager { // if logger already exists but handlers not set final Logger l = logger; AccessController.doPrivileged(new PrivilegedAction() { + @Override public Void run() { for (Handler hdl : l.getHandlers()) { sysLogger.addHandler(hdl); @@ -519,24 +593,52 @@ public class LogManager { // doesn't exist in the user context, it'll also be added to the user context. // The user context is queried by the user code and all other loggers are // added in the user context. - static class LoggerContext { + class LoggerContext { // Table of named Loggers that maps names to Loggers. private final Hashtable namedLoggers = new Hashtable<>(); // Tree of named Loggers private final LogNode root; - private final boolean requiresDefaultLoggers; private LoggerContext() { - this(false); - } - private LoggerContext(boolean requiresDefaultLoggers) { this.root = new LogNode(null, this); - this.requiresDefaultLoggers = requiresDefaultLoggers; + } + + + // Tells whether default loggers are required in this context. + // If true, the default loggers will be lazily added. + final boolean requiresDefaultLoggers() { + final boolean requiresDefaultLoggers = (getOwner() == manager); + if (requiresDefaultLoggers) { + getOwner().ensureLogManagerInitialized(); + } + return requiresDefaultLoggers; + } + + // This context's LogManager. + final LogManager getOwner() { + return LogManager.this; + } + + // This context owner's root logger, which if not null, and if + // the context requires default loggers, will be added to the context + // logger's tree. + final Logger getRootLogger() { + return getOwner().rootLogger; + } + + // The global logger, which if not null, and if + // the context requires default loggers, will be added to the context + // logger's tree. + final Logger getGlobalLogger() { + @SuppressWarnings("deprecated") // avoids initialization cycles. + final Logger global = Logger.global; + return global; } Logger demandLogger(String name, String resourceBundleName) { // a LogManager subclass may have its own implementation to add and // get a Logger. So delegate to the LogManager to do the work. - return manager.demandLogger(name, resourceBundleName, null); + final LogManager owner = getOwner(); + return owner.demandLogger(name, resourceBundleName, null); } @@ -548,10 +650,10 @@ public class LogManager { // or getLoggerNames() // private void ensureInitialized() { - if (requiresDefaultLoggers) { + if (requiresDefaultLoggers()) { // Ensure that the root and global loggers are set. - ensureDefaultLogger(manager.rootLogger); - ensureDefaultLogger(Logger.global); + ensureDefaultLogger(getRootLogger()); + ensureDefaultLogger(getGlobalLogger()); } } @@ -580,13 +682,13 @@ public class LogManager { // before adding 'logger'. // private void ensureAllDefaultLoggers(Logger logger) { - if (requiresDefaultLoggers) { + if (requiresDefaultLoggers()) { final String name = logger.getName(); if (!name.isEmpty()) { - ensureDefaultLogger(manager.rootLogger); - } - if (!Logger.GLOBAL_LOGGER_NAME.equals(name)) { - ensureDefaultLogger(Logger.global); + ensureDefaultLogger(getRootLogger()); + if (!Logger.GLOBAL_LOGGER_NAME.equals(name)) { + ensureDefaultLogger(getGlobalLogger()); + } } } } @@ -598,8 +700,8 @@ public class LogManager { // This check is simple sanity: we do not want that this // method be called for anything else than Logger.global // or owner.rootLogger. - if (!requiresDefaultLoggers || logger == null - || logger != Logger.global && logger != manager.rootLogger) { + if (!requiresDefaultLoggers() || logger == null + || logger != Logger.global && logger != LogManager.this.rootLogger) { // the case where we have a non null logger which is neither // Logger.global nor manager.rootLogger indicates a serious @@ -625,7 +727,7 @@ public class LogManager { boolean addLocalLogger(Logger logger) { // no need to add default loggers if it's not required - return addLocalLogger(logger, requiresDefaultLoggers); + return addLocalLogger(logger, requiresDefaultLoggers()); } // Add a logger to this context. This method will only set its level @@ -663,11 +765,13 @@ public class LogManager { // We're adding a new logger. // Note that we are creating a weak reference here. - ref = manager.new LoggerWeakRef(logger); + final LogManager owner = getOwner(); + logger.setLogManager(owner); + ref = owner.new LoggerWeakRef(logger); namedLoggers.put(name, ref); // Apply any initial level defined for the new logger. - Level level = manager.getLevelProperty(name + ".level", null); + Level level = owner.getLevelProperty(name + ".level", null); if (level != null) { doSetLevel(logger, level); } @@ -719,10 +823,12 @@ public class LogManager { // If logger.getUseParentHandlers() returns 'true' and any of the logger's // parents have levels or handlers defined, make sure they are instantiated. private void processParentHandlers(final Logger logger, final String name) { + final LogManager owner = getOwner(); AccessController.doPrivileged(new PrivilegedAction() { + @Override public Void run() { - if (logger != manager.rootLogger) { - boolean useParent = manager.getBooleanProperty(name + ".useParentHandlers", true); + if (logger != owner.rootLogger) { + boolean useParent = owner.getBooleanProperty(name + ".useParentHandlers", true); if (!useParent) { logger.setUseParentHandlers(false); } @@ -738,8 +844,8 @@ public class LogManager { break; } String pname = name.substring(0, ix2); - if (manager.getProperty(pname + ".level") != null || - manager.getProperty(pname + ".handlers") != null) { + if (owner.getProperty(pname + ".level") != null || + owner.getProperty(pname + ".handlers") != null) { // This pname has a level/handlers definition. // Make sure it exists. demandLogger(pname, null); @@ -779,16 +885,17 @@ public class LogManager { } } - static class SystemLoggerContext extends LoggerContext { + final class SystemLoggerContext extends LoggerContext { // Add a system logger in the system context's namespace as well as // in the LogManager's namespace if not exist so that there is only // one single logger of the given name. System loggers are visible // to applications unless a logger of the same name has been added. + @Override Logger demandLogger(String name, String resourceBundleName) { Logger result = findLogger(name); if (result == null) { // only allocate the new system logger once - Logger newLogger = new Logger(name, resourceBundleName); + Logger newLogger = new Logger(name, resourceBundleName, null, getOwner()); do { if (addLocalLogger(newLogger)) { // We successfully added the new Logger that we @@ -822,6 +929,7 @@ public class LogManager { final String handlersPropertyName) { AccessController.doPrivileged(new PrivilegedAction() { + @Override public Object run() { String names[] = parseClassNames(handlersPropertyName); for (int i = 0; i < names.length; i++) { @@ -1014,6 +1122,7 @@ public class LogManager { // There is a security manager. Raise privilege before // calling setLevel. AccessController.doPrivileged(new PrivilegedAction() { + @Override public Object run() { logger.setLevel(level); return null; @@ -1032,6 +1141,7 @@ public class LogManager { // There is a security manager. Raise privilege before // calling setParent. AccessController.doPrivileged(new PrivilegedAction() { + @Override public Object run() { logger.setParent(parent); return null; @@ -1129,14 +1239,9 @@ public class LogManager { f = new File(f, "logging.properties"); fname = f.getCanonicalPath(); } - InputStream in = new FileInputStream(fname); - BufferedInputStream bin = new BufferedInputStream(in); - try { + try (final InputStream in = new FileInputStream(fname)) { + final BufferedInputStream bin = new BufferedInputStream(in); readConfiguration(bin); - } finally { - if (in != null) { - in.close(); - } } } @@ -1201,7 +1306,7 @@ public class LogManager { } hands = hands.trim(); int ix = 0; - Vector result = new Vector<>(); + final List result = new ArrayList<>(); while (ix < hands.length()) { int end = ix; while (end < hands.length()) { @@ -1471,28 +1576,35 @@ public class LogManager { // We use a subclass of Logger for the root logger, so // that we only instantiate the global handlers when they // are first needed. - private class RootLogger extends Logger { + private final class RootLogger extends Logger { private RootLogger() { - super("", null); + // We do not call the protected Logger two args constructor here, + // to avoid calling LogManager.getLogManager() from within the + // RootLogger constructor. + super("", null, null, LogManager.this); setLevel(defaultLevel); } + @Override public void log(LogRecord record) { // Make sure that the global handlers have been instantiated. initializeGlobalHandlers(); super.log(record); } + @Override public void addHandler(Handler h) { initializeGlobalHandlers(); super.addHandler(h); } + @Override public void removeHandler(Handler h) { initializeGlobalHandlers(); super.removeHandler(h); } + @Override public Handler[] getHandlers() { initializeGlobalHandlers(); return super.getHandlers(); diff --git a/jdk/src/share/classes/java/util/logging/Logger.java b/jdk/src/share/classes/java/util/logging/Logger.java index a7f0cc2cd4a..1393ba2aa0d 100644 --- a/jdk/src/share/classes/java/util/logging/Logger.java +++ b/jdk/src/share/classes/java/util/logging/Logger.java @@ -245,14 +245,26 @@ public class Logger { // In order to finish the initialization of the global logger, we // will therefore call LogManager.getLogManager() here. // - // Care must be taken *not* to call Logger.getGlobal() in - // LogManager static initializers in order to avoid such - // deadlocks. - // - if (global != null && global.manager == null) { - // Complete initialization of the global Logger. - global.manager = LogManager.getLogManager(); - } + // To prevent race conditions we also need to call + // LogManager.getLogManager() unconditionally here. + // Indeed we cannot rely on the observed value of global.manager, + // because global.manager will become not null somewhere during + // the initialization of LogManager. + // If two threads are calling getGlobal() concurrently, one thread + // will see global.manager null and call LogManager.getLogManager(), + // but the other thread could come in at a time when global.manager + // is already set although ensureLogManagerInitialized is not finished + // yet... + // Calling LogManager.getLogManager() unconditionally will fix that. + + LogManager.getLogManager(); + + // Now the global LogManager should be initialized, + // and the global logger should have been added to + // it, unless we were called within the constructor of a LogManager + // subclass installed as LogManager, in which case global.manager + // would still be null, and global will be lazily initialized later on. + return global; } @@ -298,11 +310,11 @@ public class Logger { * no corresponding resource can be found. */ protected Logger(String name, String resourceBundleName) { - this(name, resourceBundleName, null); + this(name, resourceBundleName, null, LogManager.getLogManager()); } - Logger(String name, String resourceBundleName, Class caller) { - this.manager = LogManager.getLogManager(); + Logger(String name, String resourceBundleName, Class caller, LogManager manager) { + this.manager = manager; setupResourceInfo(resourceBundleName, caller); this.name = name; levelValue = Level.INFO.intValue(); @@ -332,8 +344,8 @@ public class Logger { levelValue = Level.INFO.intValue(); } - // It is called from the LogManager. to complete - // initialization of the global Logger. + // It is called from LoggerContext.addLocalLogger() when the logger + // is actually added to a LogManager. void setLogManager(LogManager manager) { this.manager = manager; } @@ -558,7 +570,7 @@ public class Logger { // cleanup some Loggers that have been GC'ed manager.drainLoggerRefQueueBounded(); Logger result = new Logger(null, resourceBundleName, - Reflection.getCallerClass()); + Reflection.getCallerClass(), manager); result.anonymous = true; Logger root = manager.getLogger(""); result.doSetParent(root); @@ -1798,7 +1810,7 @@ public class Logger { if (parent == null) { throw new NullPointerException(); } - manager.checkPermission(); + checkPermission(); doSetParent(parent); } diff --git a/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java b/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java index dd901ed5f92..4c6b39b0acd 100644 --- a/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java +++ b/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java @@ -57,6 +57,12 @@ public class TestGetGlobal { } public static void main(String... args) { + final String manager = System.getProperty("java.util.logging.manager", null); + + final String description = "TestGetGlobal" + + (System.getSecurityManager() == null ? " " : + " -Djava.security.manager ") + + (manager == null ? "" : "-Djava.util.logging.manager=" + manager); Logger.global.info(messages[0]); // at this point LogManager is not // initialized yet, so this message should not appear. @@ -67,7 +73,9 @@ public class TestGetGlobal { final List expected = Arrays.asList(Arrays.copyOfRange(messages, 1, messages.length)); if (!testgetglobal.HandlerImpl.received.equals(expected)) { - throw new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected); + System.err.println("Test case failed: " + description); + throw new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected + + "\n\t"+description); } } } diff --git a/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java b/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java index 4ef38ce3611..e3f9d1d8872 100644 --- a/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java +++ b/jdk/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java @@ -22,17 +22,18 @@ */ import java.util.Arrays; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; /** * @test - * @bug 7184195 - * @summary checks that java.util.logging.Logger.getGlobal().info() logs without configuration + * @bug 7184195 8021003 + * @summary Test that the global logger can log with no configuration when accessed from multiple threads. * @build TestGetGlobalConcurrent testgetglobal.HandlerImpl testgetglobal.LogManagerImpl1 testgetglobal.LogManagerImpl2 testgetglobal.LogManagerImpl3 testgetglobal.BadLogManagerImpl testgetglobal.DummyLogManagerImpl * @run main/othervm/timeout=10 TestGetGlobalConcurrent * @run main/othervm/timeout=10/policy=policy -Djava.security.manager TestGetGlobalConcurrent - * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl TestGetGlobalConcurrent - * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl TestGetGlobalConcurrent + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl1 TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl1 TestGetGlobalConcurrent * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobalConcurrent * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobalConcurrent * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobalConcurrent @@ -69,7 +70,6 @@ public class TestGetGlobalConcurrent { // initialize the LogManager - and thus this message should appear. Logger.global.info(messages[i+1]); // Now that the LogManager is // initialized, this message should appear too. - final List expected = Arrays.asList(Arrays.copyOfRange(messages, i, i+2)); if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); @@ -82,7 +82,6 @@ public class TestGetGlobalConcurrent { // initialize the LogManager - and thus this message should appear. Logger.global.info(messages[i+1]); // Now that the LogManager is // initialized, this message should appear too. - final List expected = Arrays.asList(Arrays.copyOfRange(messages, i, i+2)); if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); @@ -96,7 +95,6 @@ public class TestGetGlobalConcurrent { // initialize the LogManager - and thus this message should appear. Logger.global.info(messages[i+1]); // Now that the LogManager is // initialized, this message should appear too. - final List expected = Arrays.asList(Arrays.copyOfRange(messages, i, i+2)); if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); @@ -150,8 +148,17 @@ public class TestGetGlobalConcurrent { public void run() { test4(); } } + static String description = "Unknown"; + public static void main(String... args) throws Exception { + final String manager = System.getProperty("java.util.logging.manager", null); + + description = "TestGetGlobalConcurrent" + + (System.getSecurityManager() == null ? " " : + " -Djava.security.manager ") + + (manager == null ? "" : "-Djava.util.logging.manager=" + manager); + final Thread t1 = new Thread(new WaitAndRun(new Run1()), "test1"); final Thread t2 = new Thread(new WaitAndRun(new Run2()), "test2"); final Thread t3 = new Thread(new WaitAndRun(new Run3()), "test3"); @@ -169,14 +176,13 @@ public class TestGetGlobalConcurrent { final List expected = Arrays.asList(Arrays.copyOfRange(messages, 1, 3)); if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { - throw new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected); + fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); } - t1.join(); t2.join(); t3.join(); t4.join(); if (failed != null) { - throw new Error("Test failed.", failed); + throw new Error("Test failed: "+description, failed); } System.out.println("Test passed"); diff --git a/jdk/test/java/util/logging/Logger/getGlobal/policy b/jdk/test/java/util/logging/Logger/getGlobal/policy index bcb7cde0da3..fad24d0b814 100644 --- a/jdk/test/java/util/logging/Logger/getGlobal/policy +++ b/jdk/test/java/util/logging/Logger/getGlobal/policy @@ -1,6 +1,7 @@ grant { permission java.util.PropertyPermission "java.util.logging.config.file", "write"; permission java.util.PropertyPermission "test.src", "read"; + permission java.util.PropertyPermission "java.util.logging.manager", "read"; permission java.lang.RuntimePermission "setContextClassLoader"; permission java.lang.RuntimePermission "shutdownHooks"; permission java.util.logging.LoggingPermission "control"; diff --git a/jdk/test/java/util/logging/ParentLoggersTest.java b/jdk/test/java/util/logging/ParentLoggersTest.java index 72d5ddadc0f..a5070a375e3 100644 --- a/jdk/test/java/util/logging/ParentLoggersTest.java +++ b/jdk/test/java/util/logging/ParentLoggersTest.java @@ -29,7 +29,7 @@ * @author ss45998 * * @build ParentLoggersTest - * @run main/othervm ParentLoggersTest + * @run main ParentLoggersTest */ /* diff --git a/jdk/test/java/util/logging/TestAppletLoggerContext.java b/jdk/test/java/util/logging/TestAppletLoggerContext.java index 82c39381fe8..3db67543245 100644 --- a/jdk/test/java/util/logging/TestAppletLoggerContext.java +++ b/jdk/test/java/util/logging/TestAppletLoggerContext.java @@ -38,7 +38,7 @@ import sun.misc.SharedSecrets; /* * @test - * @bug 8017174 8010727 + * @bug 8017174 8010727 8019945 * @summary NPE when using Logger.getAnonymousLogger or * LogManager.getLogManager().getLogger * @@ -432,45 +432,36 @@ public class TestAppletLoggerContext { assertNull(manager.getLogger("")); assertNull(manager.getLogger("")); - Bridge.changeContext(); + for (int j = 0; j<3; j++) { + Bridge.changeContext(); - // this is not a supported configuration: - // We are in an applet context with several log managers. - // We however need to check our assumptions... + // this is not a supported configuration: + // We are in an applet context with several log managers. + // We however need to check our assumptions... - // Applet context => root logger and global logger are not null. - // root == LogManager.getLogManager().rootLogger - // global == Logger.global + // Applet context => root logger and global logger should also be null. - Logger logger3 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger3b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger3); - assertNotNull(logger3b); - Logger expected = (System.getSecurityManager() != null - ? Logger.getGlobal() - : global); - assertEquals(logger3, expected); // in applet context, we will not see - // the LogManager's custom global logger added above... - assertEquals(logger3b, expected); // in applet context, we will not see - // the LogManager's custom global logger added above... - Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); - manager.addLogger(global2); // adding a global logger will not work in applet context - // we will always get back the global logger. - // this could be considered as a bug... - Logger logger4 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger4b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger4); - assertNotNull(logger4b); - assertEquals(logger4, expected); // adding a global logger will not work in applet context - assertEquals(logger4b, expected); // adding a global logger will not work in applet context + Logger expected = (System.getSecurityManager() == null ? global : null); + Logger logger3 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger3b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertEquals(expected, logger3); + assertEquals(expected, logger3b); + Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); + manager.addLogger(global2); + Logger logger4 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger4b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger4); + assertNotNull(logger4b); + expected = (System.getSecurityManager() == null ? global : global2);; + assertEquals(logger4, expected); + assertEquals(logger4b, expected); - Logger logger5 = manager.getLogger(""); - Logger logger5b = manager.getLogger(""); - Logger expectedRoot = (System.getSecurityManager() != null - ? LogManager.getLogManager().getLogger("") - : null); - assertEquals(logger5, expectedRoot); - assertEquals(logger5b, expectedRoot); + Logger logger5 = manager.getLogger(""); + Logger logger5b = manager.getLogger(""); + Logger expectedRoot = null; + assertEquals(logger5, expectedRoot); + assertEquals(logger5b, expectedRoot); + } } } @@ -511,57 +502,53 @@ public class TestAppletLoggerContext { assertEquals(logger4, root); assertEquals(logger4b, root); - Bridge.changeContext(); + for (int j = 0 ; j < 3 ; j++) { + Bridge.changeContext(); - // this is not a supported configuration: - // We are in an applet context with several log managers. - // We haowever need to check our assumptions... + // this is not a supported configuration: + // We are in an applet context with several log managers. + // We however need to check our assumptions... - // Applet context => root logger and global logger are not null. - // root == LogManager.getLogManager().rootLogger - // global == Logger.global + // Applet context => root logger and global logger should also be null. - Logger logger5 = manager.getLogger(""); - Logger logger5b = manager.getLogger(""); - Logger expectedRoot = (System.getSecurityManager() != null - ? LogManager.getLogManager().getLogger("") - : root); + Logger logger5 = manager.getLogger(""); + Logger logger5b = manager.getLogger(""); + Logger expectedRoot = (System.getSecurityManager() == null ? root : null); + assertEquals(logger5, expectedRoot); + assertEquals(logger5b, expectedRoot); - assertNotNull(logger5); - assertNotNull(logger5b); - assertEquals(logger5, expectedRoot); - assertEquals(logger5b, expectedRoot); - if (System.getSecurityManager() != null) { - assertNotEquals(logger5, root); - assertNotEquals(logger5b, root); + if (System.getSecurityManager() != null) { + assertNull(manager.getLogger(Logger.GLOBAL_LOGGER_NAME)); + } else { + assertEquals(global, manager.getLogger(Logger.GLOBAL_LOGGER_NAME)); + } + + Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); + manager.addLogger(global2); + Logger logger6 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger6b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger expectedGlobal = (System.getSecurityManager() == null ? global : global2); + + assertNotNull(logger6); + assertNotNull(logger6b); + assertEquals(logger6, expectedGlobal); + assertEquals(logger6b, expectedGlobal); + if (System.getSecurityManager() != null) { + assertNull(manager.getLogger("")); + } else { + assertEquals(root, manager.getLogger("")); + } + + Logger root2 = new Bridge.CustomLogger(""); + manager.addLogger(root2); + expectedRoot = (System.getSecurityManager() == null ? root : root2); + Logger logger7 = manager.getLogger(""); + Logger logger7b = manager.getLogger(""); + assertNotNull(logger7); + assertNotNull(logger7b); + assertEquals(logger7, expectedRoot); + assertEquals(logger7b, expectedRoot); } - - Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); - manager.addLogger(global2); // adding a global logger will not work in applet context - // we will always get back the global logger. - // this could be considered as a bug... - Logger logger6 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger6b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger expectedGlobal = (System.getSecurityManager() != null - ? Logger.getGlobal() - : global); - assertNotNull(logger6); - assertNotNull(logger6b); - assertEquals(logger6, expectedGlobal); // adding a global logger will not work in applet context - assertEquals(logger6b, expectedGlobal); // adding a global logger will not work in applet context - - Logger root2 = new Bridge.CustomLogger(""); - manager.addLogger(root2); // adding a root logger will not work in applet context - // we will always get back the default manager's root logger. - // this could be considered as a bug... - Logger logger7 = manager.getLogger(""); - Logger logger7b = manager.getLogger(""); - assertNotNull(logger7); - assertNotNull(logger7b); - assertEquals(logger7, expectedRoot); // adding a global logger will not work in applet context - assertEquals(logger7b, expectedRoot); // adding a global logger will not work in applet context - assertNotEquals(logger7, root2); - assertNotEquals(logger7b, root2); } } From 92c43dcb6a22965b79e8617764056f5c9d6faff5 Mon Sep 17 00:00:00 2001 From: Mark Sheppard Date: Fri, 6 Sep 2013 15:00:59 +0100 Subject: [PATCH 34/75] 8023326: [TESTBUG] java/net/CookieHandler/LocalHostCookie.java misplaced try/finally Amended test to be more robust to set of potential exceptions thrown Reviewed-by: chegar, khazra --- jdk/test/java/net/CookieHandler/LocalHostCookie.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jdk/test/java/net/CookieHandler/LocalHostCookie.java b/jdk/test/java/net/CookieHandler/LocalHostCookie.java index ac8d61622ef..56db947126e 100644 --- a/jdk/test/java/net/CookieHandler/LocalHostCookie.java +++ b/jdk/test/java/net/CookieHandler/LocalHostCookie.java @@ -72,7 +72,9 @@ public class LocalHostCookie { } } } finally { - s.stopServer(); + if (s != null) { + s.stopServer(); + } } } @@ -96,7 +98,9 @@ public class LocalHostCookie { } public void stopServer() { - server.stop(0); + if (server != null) { + server.stop(0); + } } } From 23d61e9b454d1b6d27a2337193f54831f36a62d1 Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Fri, 6 Sep 2013 12:04:18 -0400 Subject: [PATCH 35/75] 8023362: Don't allow soft-fail behavior if OCSP responder returns "unauthorized" Reviewed-by: vinnie, xuelei --- .../security/cert/PKIXRevocationChecker.java | 3 +- .../provider/certpath/OCSPResponse.java | 2 +- .../OcspUnauthorized.java | 103 ++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 jdk/test/java/security/cert/PKIXRevocationChecker/OcspUnauthorized.java diff --git a/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java b/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java index d0e2ee02986..b667397c8e5 100644 --- a/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java +++ b/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java @@ -300,8 +300,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker { *
  • The CRL or OCSP response cannot be obtained because of a * network error. *
  • The OCSP responder returns one of the following errors - * specified in section 2.3 of RFC 2560: internalError, tryLater, - * or unauthorized. + * specified in section 2.3 of RFC 2560: internalError or tryLater. *
    * Note that these conditions apply to both OCSP and CRLs, and unless * the {@code NO_FALLBACK} option is set, the revocation check is diff --git a/jdk/src/share/classes/sun/security/provider/certpath/OCSPResponse.java b/jdk/src/share/classes/sun/security/provider/certpath/OCSPResponse.java index 5580dc70b88..955d63b57f3 100644 --- a/jdk/src/share/classes/sun/security/provider/certpath/OCSPResponse.java +++ b/jdk/src/share/classes/sun/security/provider/certpath/OCSPResponse.java @@ -385,12 +385,12 @@ public final class OCSPResponse { switch (responseStatus) { case SUCCESSFUL: break; - case UNAUTHORIZED: case TRY_LATER: case INTERNAL_ERROR: throw new CertPathValidatorException( "OCSP response error: " + responseStatus, null, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); + case UNAUTHORIZED: default: throw new CertPathValidatorException("OCSP response error: " + responseStatus); diff --git a/jdk/test/java/security/cert/PKIXRevocationChecker/OcspUnauthorized.java b/jdk/test/java/security/cert/PKIXRevocationChecker/OcspUnauthorized.java new file mode 100644 index 00000000000..83f19248959 --- /dev/null +++ b/jdk/test/java/security/cert/PKIXRevocationChecker/OcspUnauthorized.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8023362 + * @summary Make sure Ocsp UNAUTHORIZED response is treated as failure when + * SOFT_FAIL option is set + */ + +import java.io.ByteArrayInputStream; +import java.security.cert.*; +import java.security.cert.PKIXRevocationChecker.Option; +import java.util.Base64; +import java.util.Collections; +import java.util.EnumSet; + +public class OcspUnauthorized { + + private final static String OCSP_RESPONSE = "MAMKAQY="; + + private final static String EE_CERT = + "MIICADCCAWmgAwIBAgIEOvxUmjANBgkqhkiG9w0BAQQFADAqMQswCQYDVQQGEwJ1czE" + + "MMAoGA1UEChMDc3VuMQ0wCwYDVQQLEwRsYWJzMB4XDTAxMDUxNDIwNDQyMVoXDTI4MD" + + "kyOTIwNDQyMVowOTELMAkGA1UEBhMCdXMxDDAKBgNVBAoTA3N1bjENMAsGA1UECxMEb" + + "GFiczENMAsGA1UECxMEaXNyZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4MmP" + + "GDriFJ+OhDlTuLpHzPy0nawDKyIYUJPZmU9M/pCAUbZewAOyAXGPYVU1og2ZiO9tWBi" + + "ZBeJGoFHEkkhfeqSVb2PsRckiXvPZ3AiSVmdX0uD/a963abmhRMYB1gDO2+jBe3F/DU" + + "pHwpyThchy8tYUMh7Gr7+m/8FwZbdbSpMCAwEAAaMkMCIwDwYDVR0PAQH/BAUDAwekA" + + "DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBAUAA4GBAME3fmXvES0FVDXSD1iC" + + "TJLf86kUy3H+uMG7h5pOQmcfF1o9PVWlNByVf4r2b4GRgftPQ3Ao0SAvq1aSkW7YpkN" + + "pcartYqNk2E5brPajOC0v+Pkxf/g/pkRTT6Zp+9erGQF4Ta62q0iwOyc3FovSbh0Ph2" + + "WidZRP4qUG5I6JmGkI"; + + private final static String TRUST_ANCHOR = + "MIICIzCCAYygAwIBAgIEOvxT7DANBgkqhkiG9w0BAQQFADAbMQswCQYDVQQGEwJ1czE" + + "MMAoGA1UEChMDc3VuMB4XDTAxMDUxNDIxMDQyOVoXDTI4MDkyOTIxMDQyOVowKjELMA" + + "kGA1UEBhMCdXMxDDAKBgNVBAoTA3N1bjENMAsGA1UECxMEbGFiczCBnzANBgkqhkiG9" + + "w0BAQEFAAOBjQAwgYkCgYEA0/16V87rhznCM0y7IqyGcfQBentG+PglA+1hiqCuQY/A" + + "jFiDKr5N+LpcfU28P41E4M+DSDrMIEe4JchRcXeJY6aIVhpOveVV9mgtBaEKlsScrIJ" + + "zmVqM07PG9JENg2FibECnB5TNUSfVbFKfvtAqaZ7Pc971oZVoIePBWnfKV9kCAwEAAa" + + "NlMGMwPwYDVR0eAQH/BDUwM6AxMC+kKjELMAkGA1UEBhMCdXMxDDAKBgNVBAoTA3N1b" + + "jENMAsGA1UECxMEbGFic4ABAzAPBgNVHQ8BAf8EBQMDB6QAMA8GA1UdEwEB/wQFMAMB" + + "Af8wDQYJKoZIhvcNAQEEBQADgYEAfJ5HWd7K5PmX0+Vbsux4SYhoaejDwwgS43BRNa+" + + "AmFq9LIZ+ZcjBMVte8Y3sJF+nz9+1qBaUhNhbaECCqsgmWSwvI+0kUzJXL89k9AdQ8m" + + "AYf6CB6+kaZQBgrdSdqSGz3tCVa2MIK8wmb0ROM40oJ7vt3qSwgFi3UTltxkFfwQ0="; + + private static CertificateFactory cf; + private static Base64.Decoder base64Decoder = Base64.getDecoder(); + + public static void main(String[] args) throws Exception { + cf = CertificateFactory.getInstance("X.509"); + X509Certificate taCert = getX509Cert(TRUST_ANCHOR); + X509Certificate eeCert = getX509Cert(EE_CERT); + CertPath cp = cf.generateCertPath(Collections.singletonList(eeCert)); + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); + PKIXRevocationChecker prc = + (PKIXRevocationChecker)cpv.getRevocationChecker(); + prc.setOptions(EnumSet.of(Option.SOFT_FAIL, Option.NO_FALLBACK)); + byte[] response = base64Decoder.decode(OCSP_RESPONSE); + + prc.setOcspResponses(Collections.singletonMap(eeCert, response)); + + TrustAnchor ta = new TrustAnchor(taCert, null); + PKIXParameters params = new PKIXParameters(Collections.singleton(ta)); + + params.addCertPathChecker(prc); + + try { + cpv.validate(cp, params); + throw new Exception("FAILED: expected CertPathValidatorException"); + } catch (CertPathValidatorException cpve) { + cpve.printStackTrace(); + } + } + + private static X509Certificate getX509Cert(String enc) throws Exception { + byte[] bytes = base64Decoder.decode(enc); + ByteArrayInputStream is = new ByteArrayInputStream(bytes); + return (X509Certificate)cf.generateCertificate(is); + } +} From ab579cbd5f466139fa402f5dae5512a72f5ce2a9 Mon Sep 17 00:00:00 2001 From: Mark Sheppard Date: Mon, 9 Sep 2013 13:44:30 +0100 Subject: [PATCH 36/75] 8021372: NetworkInterface.getNetworkInterfaces() returns duplicate hardware address Amended src/windows/native/java/net/NetworkInterface_winXP.c to "properly" handle Ipv6IfIndex Reviewed-by: chegar, dsamersoff --- .../native/java/net/NetworkInterface_winXP.c | 19 ++- .../UniqueMacAddressesTest.java | 129 ++++++++++++++++++ 2 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java diff --git a/jdk/src/windows/native/java/net/NetworkInterface_winXP.c b/jdk/src/windows/native/java/net/NetworkInterface_winXP.c index 1078d826afa..414f5c701d3 100644 --- a/jdk/src/windows/native/java/net/NetworkInterface_winXP.c +++ b/jdk/src/windows/native/java/net/NetworkInterface_winXP.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,7 +82,6 @@ static int getAdapters (JNIEnv *env, IP_ADAPTER_ADDRESSES **adapters) { IP_ADAPTER_ADDRESSES *adapterInfo; ULONG len; adapterInfo = (IP_ADAPTER_ADDRESSES *)malloc (bufsize); - if (adapterInfo == NULL) { JNU_ThrowByName(env, "java/lang/OutOfMemoryError", "Native heap allocation failure"); return -1; @@ -160,8 +159,12 @@ IP_ADAPTER_ADDRESSES *getAdapter (JNIEnv *env, jint index) { ptr = adapterInfo; ret = NULL; while (ptr != NULL) { - // IPv4 interface - if (ptr->Ipv6IfIndex == index) { + // in theory the IPv4 index and the IPv6 index can be the same + // where an interface is enabled for v4 and v6 + // IfIndex == 0 IPv4 not available on this interface + // Ipv6IfIndex == 0 IPv6 not available on this interface + if (((ptr->IfIndex != 0)&&(ptr->IfIndex == index)) || + ((ptr->Ipv6IfIndex !=0) && (ptr->Ipv6IfIndex == index))) { ret = (IP_ADAPTER_ADDRESSES *) malloc(sizeof(IP_ADAPTER_ADDRESSES)); if (ret == NULL) { free(adapterInfo); @@ -172,6 +175,7 @@ IP_ADAPTER_ADDRESSES *getAdapter (JNIEnv *env, jint index) { //copy the memory and break out of the while loop. memcpy(ret, ptr, sizeof(IP_ADAPTER_ADDRESSES)); break; + } ptr=ptr->Next; } @@ -192,7 +196,6 @@ int getAllInterfacesAndAddresses (JNIEnv *env, netif **netifPP) int tun=0, net=0; *netifPP = NULL; - /* * Get the IPv4 interfaces. This information is the same * as what previous JDK versions would return. @@ -264,7 +267,7 @@ int getAllInterfacesAndAddresses (JNIEnv *env, netif **netifPP) * set the index to the IPv6 index and add the * IPv6 addresses */ - nif->index = ptr->Ipv6IfIndex; + nif->ipv6Index = ptr->Ipv6IfIndex; c = getAddrsFromAdapter(ptr, &nif->addrs); nif->naddrs += c; break; @@ -309,6 +312,9 @@ int getAllInterfacesAndAddresses (JNIEnv *env, netif **netifPP) strcpy (nif->name, newname); wcscpy ((PWCHAR)nif->displayName, ptr->FriendlyName); nif->dNameIsUnicode = TRUE; + + // the java.net.NetworkInterface abstraction only has index + // so the Ipv6IfIndex needs to map onto index nif->index = ptr->Ipv6IfIndex; nif->ipv6Index = ptr->Ipv6IfIndex; nif->hasIpv6Address = TRUE; @@ -487,7 +493,6 @@ static jobject createNetworkInterfaceXP(JNIEnv *env, netif *ifs) (*env)->SetObjectField(env, netifObj, ni_nameID, name); (*env)->SetObjectField(env, netifObj, ni_displayNameID, displayName); (*env)->SetIntField(env, netifObj, ni_indexID, ifs->index); - /* * Get the IP addresses for this interface if necessary * Note that 0 is a valid number of addresses. diff --git a/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java b/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java new file mode 100644 index 00000000000..c2f5c495c73 --- /dev/null +++ b/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; + + +/* + * @test + * @bug 8021372 + * @summary Tests that the MAC addresses returned by NetworkInterface.getNetworkInterfaces are unique for each adapter. + * + */ +public class UniqueMacAddressesTest { + + public static void main(String[] args) throws Exception { + new UniqueMacAddressesTest().execute(); + System.out.println("UniqueMacAddressesTest: OK"); + } + + public UniqueMacAddressesTest() { + System.out.println("UniqueMacAddressesTest: start "); + } + + public void execute() throws Exception { + Enumeration networkInterfaces; + boolean areMacAddressesUnique = false; + List networkInterfaceList = new ArrayList(); + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + + // build a list of NetworkInterface objects to test MAC address + // uniqueness + createNetworkInterfaceList(networkInterfaces, networkInterfaceList); + areMacAddressesUnique = checkMacAddressesAreUnique(networkInterfaceList); + if (!areMacAddressesUnique) { + throw new RuntimeException("mac address uniqueness test failed"); + } + } + + private boolean checkMacAddressesAreUnique ( + List networkInterfaces) throws Exception { + boolean uniqueMacAddresses = true; + for (NetworkInterface networkInterface : networkInterfaces) { + for (NetworkInterface comparisonNetIf : networkInterfaces) { + System.out.println("Comparing netif " + + networkInterface.getName() + " and netif " + + comparisonNetIf.getName()); + if (testMacAddressesEqual(networkInterface, comparisonNetIf)) { + uniqueMacAddresses = false; + break; + } + } + if (uniqueMacAddresses != true) + break; + } + return uniqueMacAddresses; + } + + private boolean testMacAddressesEqual(NetworkInterface netIf1, + NetworkInterface netIf2) throws Exception { + + byte[] rawMacAddress1 = null; + byte[] rawMacAddress2 = null; + boolean macAddressesEqual = false; + if (!netIf1.getName().equals(netIf2.getName())) { + System.out.println("compare hardware addresses " + + createMacAddressString(netIf1) + " and " + createMacAddressString(netIf2)); + rawMacAddress1 = netIf1.getHardwareAddress(); + rawMacAddress2 = netIf2.getHardwareAddress(); + macAddressesEqual = Arrays.equals(rawMacAddress1, rawMacAddress2); + } else { + // same interface + macAddressesEqual = false; + } + return macAddressesEqual; + } + + private String createMacAddressString (NetworkInterface netIf) throws Exception { + byte[] macAddr = netIf.getHardwareAddress(); + StringBuilder sb = new StringBuilder(); + if (macAddr != null) { + for (int i = 0; i < macAddr.length; i++) { + sb.append(String.format("%02X%s", macAddr[i], + (i < macAddr.length - 1) ? "-" : "")); + } + } + return sb.toString(); + } + + private void createNetworkInterfaceList(Enumeration nis, + List networkInterfaceList) throws Exception { + byte[] macAddr = null; + NetworkInterface netIf = null; + while (nis.hasMoreElements()) { + netIf = (NetworkInterface) nis.nextElement(); + macAddr = netIf.getHardwareAddress(); + if (macAddr != null) { + System.out + .println("Adding NetworkInterface " + netIf.getName()); + networkInterfaceList.add(netIf); + } + } + } +} From aefe8c12c047da8a4753780397be14ff5d390975 Mon Sep 17 00:00:00 2001 From: Jason Uh Date: Mon, 9 Sep 2013 10:52:56 -0700 Subject: [PATCH 37/75] 8024432: Fix doclint issues in java.security Reviewed-by: darcy, mullan --- .../java/security/AccessController.java | 24 +++++++++++++++++++ .../java/security/AlgorithmParameters.java | 1 + .../java/security/AlgorithmParametersSpi.java | 2 ++ .../classes/java/security/KeyFactory.java | 2 ++ .../classes/java/security/KeyFactorySpi.java | 2 ++ .../share/classes/java/security/KeyStore.java | 1 + .../classes/java/security/Principal.java | 3 ++- .../security/cert/CertPathBuilderSpi.java | 2 ++ .../security/cert/CertPathValidatorSpi.java | 2 ++ .../security/cert/PKIXRevocationChecker.java | 3 +++ .../RSAMultiPrimePrivateCrtKey.java | 7 +++++- .../security/interfaces/RSAPrivateCrtKey.java | 7 +++++- .../security/interfaces/RSAPrivateKey.java | 7 +++++- .../security/interfaces/RSAPublicKey.java | 7 +++++- 14 files changed, 65 insertions(+), 5 deletions(-) diff --git a/jdk/src/share/classes/java/security/AccessController.java b/jdk/src/share/classes/java/security/AccessController.java index ed103a9186d..a7d089958fe 100644 --- a/jdk/src/share/classes/java/security/AccessController.java +++ b/jdk/src/share/classes/java/security/AccessController.java @@ -279,6 +279,9 @@ public final class AccessController { *

    Note that any DomainCombiner associated with the current * AccessControlContext will be ignored while the action is performed. * + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. + * * @param action the action to be performed. * * @return the value returned by the action's {@code run} method. @@ -305,6 +308,9 @@ public final class AccessController { *

    This method preserves the current AccessControlContext's * DomainCombiner (which may be null) while the action is performed. * + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. + * * @param action the action to be performed. * * @return the value returned by the action's {@code run} method. @@ -344,6 +350,8 @@ public final class AccessController { * {@link java.security.SecurityPermission}, then the action is performed * with no permissions. * + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. * @param action the action to be performed. * @param context an access control context * representing the restriction to be applied to the @@ -377,6 +385,8 @@ public final class AccessController { * If the action's {@code run} method throws an (unchecked) exception, * it will propagate through this method. * + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. * @param action the action to be performed. * @param context an access control context * representing the restriction to be applied to the @@ -429,6 +439,8 @@ public final class AccessController { *

    This method preserves the current AccessControlContext's * DomainCombiner (which may be null) while the action is performed. * + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. * @param action the action to be performed. * @param context an access control context * representing the restriction to be applied to the @@ -479,6 +491,9 @@ public final class AccessController { *

    Note that any DomainCombiner associated with the current * AccessControlContext will be ignored while the action is performed. * + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. + * * @param action the action to be performed * * @return the value returned by the action's {@code run} method @@ -509,6 +524,9 @@ public final class AccessController { *

    This method preserves the current AccessControlContext's * DomainCombiner (which may be null) while the action is performed. * + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. + * * @param action the action to be performed. * * @return the value returned by the action's {@code run} method @@ -585,6 +603,8 @@ public final class AccessController { * {@link java.security.SecurityPermission}, then the action is performed * with no permissions. * + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. * @param action the action to be performed * @param context an access control context * representing the restriction to be applied to the @@ -622,6 +642,8 @@ public final class AccessController { * If the action's {@code run} method throws an (unchecked) exception, * it will propagate through this method. * + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. * @param action the action to be performed. * @param context an access control context * representing the restriction to be applied to the @@ -676,6 +698,8 @@ public final class AccessController { *

    This method preserves the current AccessControlContext's * DomainCombiner (which may be null) while the action is performed. * + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. * @param action the action to be performed. * @param context an access control context * representing the restriction to be applied to the diff --git a/jdk/src/share/classes/java/security/AlgorithmParameters.java b/jdk/src/share/classes/java/security/AlgorithmParameters.java index c603a196c9f..b548fcb64c8 100644 --- a/jdk/src/share/classes/java/security/AlgorithmParameters.java +++ b/jdk/src/share/classes/java/security/AlgorithmParameters.java @@ -324,6 +324,7 @@ public class AlgorithmParameters { * parameters should be returned in an instance of the * {@code DSAParameterSpec} class. * + * @param the type of the parameter specification to be returrned * @param paramSpec the specification class in which * the parameters should be returned. * diff --git a/jdk/src/share/classes/java/security/AlgorithmParametersSpi.java b/jdk/src/share/classes/java/security/AlgorithmParametersSpi.java index be231a4cafe..282493b97b9 100644 --- a/jdk/src/share/classes/java/security/AlgorithmParametersSpi.java +++ b/jdk/src/share/classes/java/security/AlgorithmParametersSpi.java @@ -102,6 +102,8 @@ public abstract class AlgorithmParametersSpi { * parameters should be returned in an instance of the * {@code DSAParameterSpec} class. * + * @param the type of the parameter specification to be returned + * * @param paramSpec the specification class in which * the parameters should be returned. * diff --git a/jdk/src/share/classes/java/security/KeyFactory.java b/jdk/src/share/classes/java/security/KeyFactory.java index 0eb6b754107..8e761ff41f7 100644 --- a/jdk/src/share/classes/java/security/KeyFactory.java +++ b/jdk/src/share/classes/java/security/KeyFactory.java @@ -395,6 +395,8 @@ public class KeyFactory { * key material should be returned in an instance of the * {@code DSAPublicKeySpec} class. * + * @param the type of the key specification to be returned + * * @param key the key. * * @param keySpec the specification class in which diff --git a/jdk/src/share/classes/java/security/KeyFactorySpi.java b/jdk/src/share/classes/java/security/KeyFactorySpi.java index 877c3a11be1..5ee7f458931 100644 --- a/jdk/src/share/classes/java/security/KeyFactorySpi.java +++ b/jdk/src/share/classes/java/security/KeyFactorySpi.java @@ -106,6 +106,8 @@ public abstract class KeyFactorySpi { * key material should be returned in an instance of the * {@code DSAPublicKeySpec} class. * + * @param the type of the key specification to be returned + * * @param key the key. * * @param keySpec the specification class in which diff --git a/jdk/src/share/classes/java/security/KeyStore.java b/jdk/src/share/classes/java/security/KeyStore.java index c363d0719f7..187683baa50 100644 --- a/jdk/src/share/classes/java/security/KeyStore.java +++ b/jdk/src/share/classes/java/security/KeyStore.java @@ -1753,6 +1753,7 @@ public class KeyStore { /** * Returns the KeyStore described by this object. * + * @return the {@code KeyStore} described by this object * @exception KeyStoreException if an error occured during the * operation, for example if the KeyStore could not be * instantiated or loaded diff --git a/jdk/src/share/classes/java/security/Principal.java b/jdk/src/share/classes/java/security/Principal.java index 48938cfdd07..a538e707ee7 100644 --- a/jdk/src/share/classes/java/security/Principal.java +++ b/jdk/src/share/classes/java/security/Principal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -81,6 +81,7 @@ public interface Principal { *

    Subclasses may override this with a different implementation, if * necessary. * + * @param subject the {@code Subject} * @return true if {@code subject} is non-null and is * implied by this principal, or false otherwise. * @since 1.8 diff --git a/jdk/src/share/classes/java/security/cert/CertPathBuilderSpi.java b/jdk/src/share/classes/java/security/cert/CertPathBuilderSpi.java index 87908c03bd9..e7755411797 100644 --- a/jdk/src/share/classes/java/security/cert/CertPathBuilderSpi.java +++ b/jdk/src/share/classes/java/security/cert/CertPathBuilderSpi.java @@ -87,6 +87,8 @@ public abstract class CertPathBuilderSpi { * service providers, this method cannot be abstract and by default throws * an {@code UnsupportedOperationException}. * + * @return a {@code CertPathChecker} that this implementation uses to + * check the revocation status of certificates * @throws UnsupportedOperationException if this method is not supported * @since 1.8 */ diff --git a/jdk/src/share/classes/java/security/cert/CertPathValidatorSpi.java b/jdk/src/share/classes/java/security/cert/CertPathValidatorSpi.java index 50ad9c85c9b..02d503c9e62 100644 --- a/jdk/src/share/classes/java/security/cert/CertPathValidatorSpi.java +++ b/jdk/src/share/classes/java/security/cert/CertPathValidatorSpi.java @@ -97,6 +97,8 @@ public abstract class CertPathValidatorSpi { * service providers, this method cannot be abstract and by default throws * an {@code UnsupportedOperationException}. * + * @return a {@code CertPathChecker} that this implementation uses to + * check the revocation status of certificates * @throws UnsupportedOperationException if this method is not supported * @since 1.8 */ diff --git a/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java b/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java index b667397c8e5..3046a03ed23 100644 --- a/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java +++ b/jdk/src/share/classes/java/security/cert/PKIXRevocationChecker.java @@ -103,6 +103,9 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker { private Map ocspResponses = Collections.emptyMap(); private Set

    {@code
    + *     int sum = widgets.stream()
    + *                      .filter(w -> w.getColor() == RED)
    + *                      .mapToInt(w -> w.getWeight())
    + *                      .sum();
    + * }
    + * + * In this example, {@code widgets} is a {@code Collection}. We create + * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()}, + * filter it to produce a stream containing only the red widgets, and then + * transform it into a stream of {@code int} values representing the weight of + * each red widget. Then this stream is summed to produce a total weight. + * + *

    To perform a computation, stream + * operations are composed into a + * stream pipeline. A stream pipeline consists of a source (which + * might be an array, a collection, a generator function, an IO channel, + * etc), zero or more intermediate operations (which transform a + * stream into another stream, such as {@link Stream#filter(Predicate)}), and a + * terminal operation (which produces a result or side-effect, such + * as {@link IntStream#sum()} or {@link IntStream#forEach(IntConsumer)}). + * Streams are lazy; computation on the source data is only performed when the + * terminal operation is initiated, and source elements are consumed only + * as needed. + * + *

    Collections and streams, while bearing some superficial similarities, + * have different goals. Collections are primarily concerned with the efficient + * management of, and access to, their elements. By contrast, streams do not + * provide a means to directly access or manipulate their elements, and are + * instead concerned with declaratively describing their source and the + * computational operations which will be performed in aggregate on that source. + * However, if the provided stream operations do not offer the desired + * functionality, the {@link #iterator()} and {@link #spliterator()} operations + * can be used to perform a controlled traversal. + * + *

    A stream pipeline, like the "widgets" example above, can be viewed as + * a query on the stream source. Unless the source was explicitly + * designed for concurrent modification (such as a {@link ConcurrentHashMap}), + * unpredictable or erroneous behavior may result from modifying the stream + * source while it is being queried. + * + *

    Most stream operations accept parameters that describe user-specified + * behavior, such as the lambda expression {@code w -> w.getWeight()} passed to + * {@code mapToInt} in the example above. Such parameters are always instances + * of a functional interface such + * as {@link java.util.function.Function}, and are often lambda expressions or + * method references. These parameters can never be null, should not modify the + * stream source, and should be + * effectively stateless + * (their result should not depend on any state that might change during + * execution of the stream pipeline.) + * + *

    A stream should be operated on (invoking an intermediate or terminal stream + * operation) only once. This rules out, for example, "forked" streams, where + * the same source feeds two or more pipelines, or multiple traversals of the + * same stream. A stream implementation may throw {@link IllegalStateException} + * if it detects that the stream is being reused. However, since some stream + * operations may return their receiver rather than a new stream object, it may + * not be possible to detect reuse in all cases. + * + *

    Streams have a {@link #close()} method and implement {@link AutoCloseable}, + * but nearly all stream instances do not actually need to be closed after use. + * Generally, only streams whose source is an IO channel (such as those returned + * by {@link Files#lines(Path, Charset)}) will require closing. Most streams + * are backed by collections, arrays, or generating functions, which require no + * special resource management. (If a stream does require closing, it can be + * declared as a resource in a {@code try}-with-resources statement.) + * + *

    Stream pipelines may execute either sequentially or in + * parallel. This + * execution mode is a property of the stream. Streams are created + * with an initial choice of sequential or parallel execution. (For example, + * {@link Collection#stream() Collection.stream()} creates a sequential stream, + * and {@link Collection#parallelStream() Collection.parallelStream()} creates + * a parallel one.) This choice of execution mode may be modified by the + * {@link #sequential()} or {@link #parallel()} methods, and may be queried with + * the {@link #isParallel()} method. + * + * @param the type of the stream elements + * @param the type of of the stream implementing {@code BaseStream} * @since 1.8 + * @see java.util.stream */ public interface BaseStream> extends AutoCloseable { @@ -58,14 +145,11 @@ public interface BaseStream> Spliterator spliterator(); /** - * Returns whether this stream, when executed, would execute in parallel - * (assuming no further modification of the stream, such as appending - * further intermediate operations or changing its parallelism). Calling - * this method after invoking an intermediate or terminal stream operation - * method may yield unpredictable results. + * Returns whether this stream, if a terminal operation were to be executed, + * would execute in parallel. Calling this method after invoking an + * terminal stream operation method may yield unpredictable results. * * @return {@code true} if this stream would execute in parallel if executed - * without further modification otherwise {@code false} */ boolean isParallel(); @@ -96,7 +180,8 @@ public interface BaseStream> /** * Returns an equivalent stream that is * unordered. May return - * itself if the stream was already unordered. + * itself, either because the stream was already unordered, or because + * the underlying stream state was modified to be unordered. * *

    This is an intermediate * operation. diff --git a/jdk/src/share/classes/java/util/stream/Collector.java b/jdk/src/share/classes/java/util/stream/Collector.java index 49629176032..e409d569808 100644 --- a/jdk/src/share/classes/java/util/stream/Collector.java +++ b/jdk/src/share/classes/java/util/stream/Collector.java @@ -26,6 +26,7 @@ package java.util.stream; import java.util.Collections; import java.util.EnumSet; +import java.util.Objects; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; @@ -33,71 +34,74 @@ import java.util.function.Function; import java.util.function.Supplier; /** - * A reduction operation that - * folds input elements into a mutable result container, optionally transforming + * A mutable reduction operation that + * accumulates input elements into a mutable result container, optionally transforming * the accumulated result into a final representation after all input elements - * have been processed. + * have been processed. Reduction operations can be performed either sequentially + * or in parallel. * *

    Examples of mutable reduction operations include: * accumulating elements into a {@code Collection}; concatenating * strings using a {@code StringBuilder}; computing summary information about * elements such as sum, min, max, or average; computing "pivot table" summaries - * such as "maximum valued transaction by seller", etc. Reduction operations - * can be performed either sequentially or in parallel. - * - *

    The following are examples of using the predefined {@code Collector} - * implementations in {@link Collectors} with the {@code Stream} API to perform - * mutable reduction tasks: - *

    {@code
    - *     // Accumulate names into a List
    - *     List list = people.stream().map(Person::getName).collect(Collectors.toList());
    - *
    - *     // Accumulate names into a TreeSet
    - *     Set list = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
    - *
    - *     // Convert elements to strings and concatenate them, separated by commas
    - *     String joined = things.stream()
    - *                           .map(Object::toString)
    - *                           .collect(Collectors.joining(", "));
    - *
    - *     // Find highest-paid employee
    - *     Employee highestPaid = employees.stream()
    - *                                     .collect(Collectors.maxBy(Comparators.comparing(Employee::getSalary)))
    - *                                     .get();
    - *
    - *     // Group employees by department
    - *     Map> byDept
    - *         = employees.stream()
    - *                    .collect(Collectors.groupingBy(Employee::getDepartment));
    - *
    - *     // Find highest-paid employee by department
    - *     Map> highestPaidByDept
    - *         = employees.stream()
    - *                    .collect(Collectors.groupingBy(Employee::getDepartment,
    - *                                                   Collectors.maxBy(Comparators.comparing(Employee::getSalary))));
    - *
    - *     // Partition students into passing and failing
    - *     Map> passingFailing =
    - *         students.stream()
    - *                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
    - *
    - * }
    + * such as "maximum valued transaction by seller", etc. The class {@link Collectors} + * provides implementations of many common mutable reductions. * *

    A {@code Collector} is specified by four functions that work together to * accumulate entries into a mutable result container, and optionally perform - * a final transform on the result. They are: creation of a new result container, - * incorporating a new data element into a result container, combining two - * result containers into one, and performing a final transform on the container. - * The combiner function is used during parallel operations, where - * subsets of the input are accumulated into separate result - * containers, and then the subresults merged into a combined result. The - * combiner function may merge one set of subresults into the other and return - * that, or it may return a new object to describe the combined results. + * a final transform on the result. They are:

      + *
    • creation of a new result container ({@link #supplier()})
    • + *
    • incorporating a new data element into a result container ({@link #accumulator()})
    • + *
    • combining two result containers into one ({@link #combiner()})
    • + *
    • performing an optional final transform on the container ({@link #finisher()})
    • + *
    * *

    Collectors also have a set of characteristics, such as - * {@link Characteristics#CONCURRENT}. These characteristics provide - * hints that can be used by a reduction implementation to provide better - * performance. + * {@link Characteristics#CONCURRENT}, that provide hints that can be used by a + * reduction implementation to provide better performance. + * + *

    A sequential implementation of a reduction using a collector would + * create a single result container using the supplier function, and invoke the + * accumulator function once for each input element. A parallel implementation + * would partition the input, create a result container for each partition, + * accumulate the contents of each partition into a subresult for that partition, + * and then use the combiner function to merge the subresults into a combined + * result. + * + *

    To ensure that sequential and parallel executions produce equivalent + * results, the collector functions must satisfy an identity and an + * associativity constraints. + * + *

    The identity constraint says that for any partially accumulated result, + * combining it with an empty result container must produce an equivalent + * result. That is, for a partially accumulated result {@code a} that is the + * result of any series of accumulator and combiner invocations, {@code a} must + * be equivalent to {@code combiner.apply(a, supplier.get())}. + * + *

    The associativity constraint says that splitting the computation must + * produce an equivalent result. That is, for any input elements {@code t1} + * and {@code t2}, the results {@code r1} and {@code r2} in the computation + * below must be equivalent: + *

    {@code
    + *     A a1 = supplier.get();
    + *     accumulator.accept(a1, t1);
    + *     accumulator.accept(a1, t2);
    + *     R r1 = finisher.apply(a1);  // result without splitting
    + *
    + *     A a2 = supplier.get();
    + *     accumulator.accept(a2, t1);
    + *     A a3 = supplier.get();
    + *     accumulator.accept(a3, t2);
    + *     R r2 = finisher.apply(combiner.apply(a2, a3));  // result with splitting
    + * } 
    + * + *

    For collectors that do not have the {@code UNORDERED} characteristic, + * two accumulated results {@code a1} and {@code a2} are equivalent if + * {@code finisher.apply(a1).equals(finisher.apply(a2))}. For unordered + * collectors, equivalence is relaxed to allow for non-equality related to + * differences in order. (For example, an unordered collector that accumulated + * elements to a {@code List} would consider two lists equivalent if they + * contained the same elements, ignoring order.) * *

    Libraries that implement reduction based on {@code Collector}, such as * {@link Stream#collect(Collector)}, must adhere to the following constraints: @@ -132,6 +136,20 @@ import java.util.function.Supplier; * originating data is unordered.

  • * * + *

    In addition to the predefined implementations in {@link Collectors}, the + * static factory methods {@link #of(Supplier, BiConsumer, BinaryOperator, Characteristics...)} + * can be used to construct collectors. For example, you could create a collector + * that accumulates widgets into a {@code TreeSet} with: + * + *

    {@code
    + *     Collector> intoSet =
    + *         Collector.of(TreeSet::new, TreeSet::add,
    + *                      (left, right) -> { left.addAll(right); return left; });
    + * }
    + * + * (This behavior is also implemented by the predefined collector + * {@link Collectors#toCollection(Supplier)}). + * * @apiNote * Performing a reduction operation with a {@code Collector} should produce a * result equivalent to: @@ -144,27 +162,35 @@ import java.util.function.Supplier; * *

    However, the library is free to partition the input, perform the reduction * on the partitions, and then use the combiner function to combine the partial - * results to achieve a parallel reduction. Depending on the specific reduction + * results to achieve a parallel reduction. (Depending on the specific reduction * operation, this may perform better or worse, depending on the relative cost - * of the accumulator and combiner functions. + * of the accumulator and combiner functions.) * - *

    An example of an operation that can be easily modeled by {@code Collector} - * is accumulating elements into a {@code TreeSet}. In this case, the {@code - * resultSupplier()} function is {@code () -> new Treeset()}, the - * {@code accumulator} function is - * {@code (set, element) -> set.add(element) }, and the combiner - * function is {@code (left, right) -> { left.addAll(right); return left; }}. - * (This behavior is implemented by - * {@code Collectors.toCollection(TreeSet::new)}). + *

    Collectors are designed to be composed; many of the methods + * in {@link Collectors} are functions that take a collector and produce + * a new collector. For example, given the following collector that computes + * the sum of the salaries of a stream of employees: * - * TODO Associativity and commutativity + *

    {@code
    + *     Collector summingSalaries
    + *         = Collectors.summingInt(Employee::getSalary))
    + * }
    + * + * If we wanted to create a collector to tabulate the sum of salaries by + * department, we could reuse the "sum of salaries" logic using + * {@link Collectors#groupingBy(Function, Collector)}: + * + *
    {@code
    + *     Collector> summingSalariesByDept
    + *         = Collectors.groupingBy(Employee::getDepartment, summingSalaries);
    + * }
    * * @see Stream#collect(Collector) * @see Collectors * * @param the type of input elements to the reduction operation * @param the mutable accumulation type of the reduction operation (often - * hidden as an implementation detail) + * hidden as an implementation detail) * @param the result type of the reduction operation * @since 1.8 */ @@ -177,25 +203,25 @@ public interface Collector { Supplier supplier(); /** - * A function that folds a new value into a mutable result container. + * A function that folds a value into a mutable result container. * - * @return a function which folds a new value into a mutable result container + * @return a function which folds a value into a mutable result container */ BiConsumer accumulator(); /** * A function that accepts two partial results and merges them. The * combiner function may fold state from one argument into the other and - * return that, or may return a new result object. + * return that, or may return a new result container. * - * @return a function which combines two partial results into a cumulative + * @return a function which combines two partial results into a combined * result */ BinaryOperator combiner(); /** * Perform the final transformation from the intermediate accumulation type - * {@code A} to the final result representation {@code R}. + * {@code A} to the final result type {@code R}. * *

    If the characteristic {@code IDENTITY_TRANSFORM} is * set, this function may be presumed to be an identity transform with an @@ -228,12 +254,17 @@ public interface Collector { * @param The type of input elements for the new collector * @param The type of intermediate accumulation result, and final result, * for the new collector + * @throws NullPointerException if any argument is null * @return the new {@code Collector} */ public static Collector of(Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Characteristics... characteristics) { + Objects.requireNonNull(supplier); + Objects.requireNonNull(accumulator); + Objects.requireNonNull(combiner); + Objects.requireNonNull(characteristics); Set cs = (characteristics.length == 0) ? Collectors.CH_ID : Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH, @@ -254,6 +285,7 @@ public interface Collector { * @param The type of input elements for the new collector * @param The intermediate accumulation type of the new collector * @param The final result type of the new collector + * @throws NullPointerException if any argument is null * @return the new {@code Collector} */ public static Collector of(Supplier supplier, @@ -261,6 +293,11 @@ public interface Collector { BinaryOperator combiner, Function finisher, Characteristics... characteristics) { + Objects.requireNonNull(supplier); + Objects.requireNonNull(accumulator); + Objects.requireNonNull(combiner); + Objects.requireNonNull(finisher); + Objects.requireNonNull(characteristics); Set cs = Collectors.CH_NOID; if (characteristics.length > 0) { cs = EnumSet.noneOf(Characteristics.class); @@ -288,8 +325,9 @@ public interface Collector { CONCURRENT, /** - * Indicates that the result container has no intrinsic order, such as - * a {@link Set}. + * Indicates that the collection operation does not commit to preserving + * the encounter order of input elements. (This might be true if the + * result container has no intrinsic order, such as a {@link Set}.) */ UNORDERED, diff --git a/jdk/src/share/classes/java/util/stream/Collectors.java b/jdk/src/share/classes/java/util/stream/Collectors.java index bdbd8ad0774..00f7c866311 100644 --- a/jdk/src/share/classes/java/util/stream/Collectors.java +++ b/jdk/src/share/classes/java/util/stream/Collectors.java @@ -62,37 +62,35 @@ import java.util.function.ToLongFunction; * operations, such as accumulating elements into collections, summarizing * elements according to various criteria, etc. * - *

    The following are examples of using the predefined {@code Collector} - * implementations in {@link Collectors} with the {@code Stream} API to perform - * mutable reduction tasks: + *

    The following are examples of using the predefined collectors to perform + * common mutable reduction tasks: * *

    {@code
      *     // Accumulate names into a List
      *     List list = people.stream().map(Person::getName).collect(Collectors.toList());
      *
      *     // Accumulate names into a TreeSet
    - *     Set list = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
    + *     Set set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
      *
      *     // Convert elements to strings and concatenate them, separated by commas
      *     String joined = things.stream()
      *                           .map(Object::toString)
      *                           .collect(Collectors.joining(", "));
      *
    - *     // Find highest-paid employee
    - *     Employee highestPaid = employees.stream()
    - *                                     .collect(Collectors.maxBy(Comparator.comparing(Employee::getSalary)))
    - *                                     .get();
    + *     // Compute sum of salaries of employee
    + *     int total = employees.stream()
    + *                          .collect(Collectors.summingInt(Employee::getSalary)));
      *
      *     // Group employees by department
      *     Map> byDept
      *         = employees.stream()
      *                    .collect(Collectors.groupingBy(Employee::getDepartment));
      *
    - *     // Find highest-paid employee by department
    - *     Map> highestPaidByDept
    + *     // Compute sum of salaries by department
    + *     Map totalByDept
      *         = employees.stream()
      *                    .collect(Collectors.groupingBy(Employee::getDepartment,
    - *                                                   Collectors.maxBy(Comparator.comparing(Employee::getSalary))));
    + *                                                   Collectors.summingInt(Employee::getSalary)));
      *
      *     // Partition students into passing and failing
      *     Map> passingFailing =
    @@ -101,8 +99,6 @@ import java.util.function.ToLongFunction;
      *
      * }
    * - * TODO explanation of parallel collection - * * @since 1.8 */ public final class Collectors { @@ -222,7 +218,8 @@ public final class Collectors { /** * Returns a {@code Collector} that accumulates the input elements into a * new {@code List}. There are no guarantees on the type, mutability, - * serializability, or thread-safety of the {@code List} returned. + * serializability, or thread-safety of the {@code List} returned; if more + * control over the returned {@code List} is required, use {@link #toCollection(Supplier)}. * * @param the type of the input elements * @return a {@code Collector} which collects all the input elements into a @@ -238,7 +235,9 @@ public final class Collectors { /** * Returns a {@code Collector} that accumulates the input elements into a * new {@code Set}. There are no guarantees on the type, mutability, - * serializability, or thread-safety of the {@code Set} returned. + * serializability, or thread-safety of the {@code Set} returned; if more + * control over the returned {@code Set} is required, use + * {@link #toCollection(Supplier)}. * *

    This is an {@link Collector.Characteristics#UNORDERED unordered} * Collector. @@ -903,7 +902,7 @@ public final class Collectors { * where the city names are sorted: *

    {@code
          *     ConcurrentMap> namesByCity
    -     *         = people.stream().collect(groupingByConcurrent(Person::getCity, ConcurrentSkipListMap::new,
    +     *         = people.stream().collect(groupingByConcurrent(Person::getCity,
          *                                                        mapping(Person::getLastName, toSet())));
          * }
    * diff --git a/jdk/src/share/classes/java/util/stream/DoublePipeline.java b/jdk/src/share/classes/java/util/stream/DoublePipeline.java index 75981d5801f..43b3d04bed9 100644 --- a/jdk/src/share/classes/java/util/stream/DoublePipeline.java +++ b/jdk/src/share/classes/java/util/stream/DoublePipeline.java @@ -313,8 +313,8 @@ abstract class DoublePipeline } @Override - public final DoubleStream peek(DoubleConsumer consumer) { - Objects.requireNonNull(consumer); + public final DoubleStream peek(DoubleConsumer action) { + Objects.requireNonNull(action); return new StatelessOp(this, StreamShape.DOUBLE_VALUE, 0) { @Override @@ -322,7 +322,7 @@ abstract class DoublePipeline return new Sink.ChainedDouble(sink) { @Override public void accept(double t) { - consumer.accept(t); + action.accept(t); downstream.accept(t); } }; @@ -436,14 +436,14 @@ abstract class DoublePipeline } @Override - public final R collect(Supplier resultFactory, + public final R collect(Supplier supplier, ObjDoubleConsumer accumulator, BiConsumer combiner) { BinaryOperator operator = (left, right) -> { combiner.accept(left, right); return left; }; - return evaluate(ReduceOps.makeDouble(resultFactory, accumulator, operator)); + return evaluate(ReduceOps.makeDouble(supplier, accumulator, operator)); } @Override diff --git a/jdk/src/share/classes/java/util/stream/DoubleStream.java b/jdk/src/share/classes/java/util/stream/DoubleStream.java index bf356926154..f10092ac7a5 100644 --- a/jdk/src/share/classes/java/util/stream/DoubleStream.java +++ b/jdk/src/share/classes/java/util/stream/DoubleStream.java @@ -24,13 +24,18 @@ */ package java.util.stream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.DoubleSummaryStatistics; import java.util.Objects; import java.util.OptionalDouble; import java.util.PrimitiveIterator; import java.util.Spliterator; import java.util.Spliterators; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.DoubleBinaryOperator; import java.util.function.DoubleConsumer; @@ -45,40 +50,87 @@ import java.util.function.ObjDoubleConsumer; import java.util.function.Supplier; /** - * A sequence of primitive double elements supporting sequential and parallel - * bulk operations. Streams support lazy intermediate operations (transforming - * a stream to another stream) such as {@code filter} and {@code map}, and terminal - * operations (consuming the contents of a stream to produce a result or - * side-effect), such as {@code forEach}, {@code findFirst}, and {@code - * iterator}. Once an operation has been performed on a stream, it - * is considered consumed and no longer usable for other operations. + * A sequence of elements supporting sequential and parallel aggregate + * operations. The following example illustrates an aggregate operation using + * {@link Stream} and {@link DoubleStream}: * - *

    For sequential stream pipelines, all operations are performed in the - * encounter order of the pipeline - * source, if the pipeline source has a defined encounter order. + *

    {@code
    + *     double sum = widgets.stream()
    + *                         .filter(w -> w.getColor() == RED)
    + *                         .mapToDouble(w -> w.getWeight())
    + *                         .sum();
    + * }
    * - *

    For parallel stream pipelines, unless otherwise specified, intermediate - * stream operations preserve the - * encounter order of their source, and terminal operations - * respect the encounter order of their source, if the source - * has an encounter order. Provided that and parameters to stream operations - * satisfy the non-interference - * requirements, and excepting differences arising from the absence of - * a defined encounter order, the result of a stream pipeline should be the - * stable across multiple executions of the same operations on the same source. - * However, the timing and thread in which side-effects occur (for those - * operations which are allowed to produce side-effects, such as - * {@link #forEach(DoubleConsumer)}), are explicitly nondeterministic for parallel - * execution of stream pipelines. + * In this example, {@code widgets} is a {@code Collection}. We create + * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()}, + * filter it to produce a stream containing only the red widgets, and then + * transform it into a stream of {@code double} values representing the weight of + * each red widget. Then this stream is summed to produce a total weight. * - *

    Unless otherwise noted, passing a {@code null} argument to any stream - * method may result in a {@link NullPointerException}. + *

    To perform a computation, stream + * operations are composed into a + * stream pipeline. A stream pipeline consists of a source (which + * might be an array, a collection, a generator function, an IO channel, + * etc), zero or more intermediate operations (which transform a + * stream into another stream, such as {@link DoubleStream#filter(DoublePredicate)}), and a + * terminal operation (which produces a result or side-effect, such + * as {@link DoubleStream#sum()} or {@link DoubleStream#forEach(DoubleConsumer)}. + * Streams are lazy; computation on the source data is only performed when the + * terminal operation is initiated, and source elements are consumed only + * as needed. * - * @apiNote - * Streams are not data structures; they do not manage the storage for their - * elements, nor do they support access to individual elements. However, - * you can use the {@link #iterator()} or {@link #spliterator()} operations to - * perform a controlled traversal. + *

    Collections and streams, while bearing some superficial similarities, + * have different goals. Collections are primarily concerned with the efficient + * management of, and access to, their elements. By contrast, streams do not + * provide a means to directly access or manipulate their elements, and are + * instead concerned with declaratively describing their source and the + * computational operations which will be performed in aggregate on that source. + * However, if the provided stream operations do not offer the desired + * functionality, the {@link #iterator()} and {@link #spliterator()} operations + * can be used to perform a controlled traversal. + * + *

    A stream pipeline, like the "widgets" example above, can be viewed as + * a query on the stream source. Unless the source was explicitly + * designed for concurrent modification (such as a {@link ConcurrentHashMap}), + * unpredictable or erroneous behavior may result from modifying the stream + * source while it is being queried. + * + *

    Most stream operations accept parameters that describe user-specified + * behavior, such as the lambda expression {@code w -> w.getWeight()} passed to + * {@code mapToDouble} in the example above. Such parameters are always instances + * of a functional interface such + * as {@link java.util.function.Function}, and are often lambda expressions or + * method references. These parameters can never be null, should not modify the + * stream source, and should be + * effectively stateless + * (their result should not depend on any state that might change during + * execution of the stream pipeline.) + * + *

    A stream should be operated on (invoking an intermediate or terminal stream + * operation) only once. This rules out, for example, "forked" streams, where + * the same source feeds two or more pipelines, or multiple traversals of the + * same stream. A stream implementation may throw {@link IllegalStateException} + * if it detects that the stream is being reused. However, since some stream + * operations may return their receiver rather than a new stream object, it may + * not be possible to detect reuse in all cases. + * + *

    Streams have a {@link #close()} method and implement {@link AutoCloseable}, + * but nearly all stream instances do not actually need to be closed after use. + * Generally, only streams whose source is an IO channel (such as those returned + * by {@link Files#lines(Path, Charset)}) will require closing. Most streams + * are backed by collections, arrays, or generating functions, which require no + * special resource management. (If a stream does require closing, it can be + * declared as a resource in a {@code try}-with-resources statement.) + * + *

    Stream pipelines may execute either sequentially or in + * parallel. This + * execution mode is a property of the stream. Streams are created + * with an initial choice of sequential or parallel execution. (For example, + * {@link Collection#stream() Collection.stream()} creates a sequential stream, + * and {@link Collection#parallelStream() Collection.parallelStream()} creates + * a parallel one.) This choice of execution mode may be modified by the + * {@link #sequential()} or {@link #parallel()} methods, and may be queried with + * the {@link #isParallel()} method. * * @since 1.8 * @see java.util.stream @@ -159,22 +211,13 @@ public interface DoubleStream extends BaseStream { /** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of the stream produced by applying the - * provided mapping function to each element. + * provided mapping function to each element. (If the result of the mapping + * function is {@code null}, this is treated as if the result was an empty + * stream.) * *

    This is an intermediate * operation. * - * @apiNote - * The {@code flatMap()} operation has the effect of applying a one-to-many - * tranformation to the elements of the stream, and then flattening the - * resulting elements into a new stream. For example, if {@code orders} - * is a stream of purchase orders, and each purchase order contains a - * collection of line items, then the following produces a stream of line - * items: - *

    {@code
    -     *     orderStream.flatMap(order -> order.getLineItems().stream())...
    -     * }
    - * * @param mapper a * non-interfering, stateless function to apply to * each element which produces an {@code DoubleStream} of new @@ -226,18 +269,18 @@ public interface DoubleStream extends BaseStream { *
    {@code
          *     list.stream()
          *         .filter(filteringFunction)
    -     *         .peek(e -> {System.out.println("Filtered value: " + e); });
    +     *         .peek(e -> System.out.println("Filtered value: " + e));
          *         .map(mappingFunction)
    -     *         .peek(e -> {System.out.println("Mapped value: " + e); });
    +     *         .peek(e -> System.out.println("Mapped value: " + e));
          *         .collect(Collectors.toDoubleSummaryStastistics());
          * }
    * - * @param consumer a + * @param action a * non-interfering action to perform on the elements as * they are consumed from the stream * @return the new stream */ - DoubleStream peek(DoubleConsumer consumer); + DoubleStream peek(DoubleConsumer action); /** * Returns a stream consisting of the elements of this stream, truncated @@ -254,8 +297,8 @@ public interface DoubleStream extends BaseStream { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream. If the - * {@code startInclusive} index lies past the end of this stream then an + * after discarding the first {@code startInclusive} elements of the stream. + * If this stream contains fewer than {@code startInclusive} elements then an * empty stream will be returned. * *

    This is a stateful @@ -269,10 +312,10 @@ public interface DoubleStream extends BaseStream { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream and - * truncated to contain no more than {@code endExclusive - startInclusive} - * elements. If the {@code startInclusive} index lies past the end - * of this stream then an empty stream will be returned. + * after discarding the first {@code startInclusive} elements and truncating + * the result to be no longer than {@code endExclusive - startInclusive} + * elements in length. If this stream contains fewer than + * {@code startInclusive} elements then an empty stream will be returned. * *

    This is a short-circuiting * stateful intermediate operation. @@ -421,12 +464,12 @@ public interface DoubleStream extends BaseStream { /** * Performs a mutable * reduction operation on the elements of this stream. A mutable - * reduction is one in which the reduced value is a mutable value holder, + * reduction is one in which the reduced value is a mutable result container, * such as an {@code ArrayList}, and elements are incorporated by updating - * the state of the result, rather than by replacing the result. This + * the state of the result rather than by replacing the result. This * produces a result equivalent to: *

    {@code
    -     *     R result = resultFactory.get();
    +     *     R result = supplier.get();
          *     for (double element : this stream)
          *         accumulator.accept(result, element);
          *     return result;
    @@ -440,10 +483,9 @@ public interface DoubleStream extends BaseStream {
          * operation.
          *
          * @param  type of the result
    -     * @param resultFactory a function that creates a new result container.
    -     *                      For a parallel execution, this function may be
    -     *                      called multiple times and must return a fresh value
    -     *                      each time.
    +     * @param supplier a function that creates a new result container. For a
    +     *                 parallel execution, this function may be called
    +     *                 multiple times and must return a fresh value each time.
          * @param accumulator an associative
          *                    non-interfering,
          *                    stateless function for incorporating an additional
    @@ -455,7 +497,7 @@ public interface DoubleStream extends BaseStream {
          * @return the result of the reduction
          * @see Stream#collect(Supplier, BiConsumer, BiConsumer)
          */
    -     R collect(Supplier resultFactory,
    +     R collect(Supplier supplier,
                       ObjDoubleConsumer accumulator,
                       BiConsumer combiner);
     
    @@ -467,12 +509,15 @@ public interface DoubleStream extends BaseStream {
          * yield more accurate results.  If any stream element is a {@code NaN} or
          * the sum is at any point a {@code NaN} then the sum will be {@code NaN}.
          * This is a special case of a
    -     * reduction and is
    +     * reduction and is
          * equivalent to:
          * 
    {@code
          *     return reduce(0, Double::sum);
          * }
    * + *

    This is a terminal + * operation. + * * @return the sum of elements in this stream */ double sum(); @@ -483,12 +528,15 @@ public interface DoubleStream extends BaseStream { * element will be {@code Double.NaN} if any stream element was NaN. Unlike * the numerical comparison operators, this method considers negative zero * to be strictly smaller than positive zero. This is a special case of a - * reduction and is + * reduction and is * equivalent to: *

    {@code
          *     return reduce(Double::min);
          * }
    * + *

    This is a terminal + * operation. + * * @return an {@code OptionalDouble} containing the minimum element of this * stream, or an empty optional if the stream is empty */ @@ -501,12 +549,15 @@ public interface DoubleStream extends BaseStream { * the numerical comparison operators, this method considers negative zero * to be strictly smaller than positive zero. This is a * special case of a - * reduction and is + * reduction and is * equivalent to: *

    {@code
          *     return reduce(Double::max);
          * }
    * + *

    This is a terminal + * operation. + * * @return an {@code OptionalDouble} containing the maximum element of this * stream, or an empty optional if the stream is empty */ @@ -514,7 +565,7 @@ public interface DoubleStream extends BaseStream { /** * Returns the count of elements in this stream. This is a special case of - * a reduction and is + * a reduction and is * equivalent to: *

    {@code
          *     return mapToLong(e -> 1L).sum();
    @@ -535,7 +586,10 @@ public interface DoubleStream extends BaseStream {
          * magnitude tend to yield more accurate results. If any recorded value is
          * a {@code NaN} or the sum is at any point a {@code NaN} then the average
          * will be {@code NaN}. This is a special case of a
    -     * reduction.
    +     * reduction.
    +     *
    +     * 

    This is a terminal + * operation. * * @return an {@code OptionalDouble} containing the average element of this * stream, or an empty optional if the stream is empty @@ -545,7 +599,10 @@ public interface DoubleStream extends BaseStream { /** * Returns a {@code DoubleSummaryStatistics} describing various summary data * about the elements of this stream. This is a special - * case of a reduction. + * case of a reduction. + * + *

    This is a terminal + * operation. * * @return a {@code DoubleSummaryStatistics} describing various summary data * about the elements of this stream @@ -602,9 +659,8 @@ public interface DoubleStream extends BaseStream { /** * Returns an {@link OptionalDouble} describing the first element of this - * stream (in the encounter order), or an empty {@code OptionalDouble} if - * the stream is empty. If the stream has no encounter order, then any - * element may be returned. + * stream, or an empty {@code OptionalDouble} if the stream is empty. If + * the stream has no encounter order, then any element may be returned. * *

    This is a short-circuiting * terminal operation. @@ -624,8 +680,8 @@ public interface DoubleStream extends BaseStream { *

    The behavior of this operation is explicitly nondeterministic; it is * free to select any element in the stream. This is to allow for maximal * performance in parallel operations; the cost is that multiple invocations - * on the same source may not return the same result. (If the first element - * in the encounter order is desired, use {@link #findFirst()} instead.) + * on the same source may not return the same result. (If a stable result + * is desired, use {@link #findFirst()} instead.) * * @return an {@code OptionalDouble} describing some element of this stream, * or an empty {@code OptionalDouble} if the stream is empty @@ -637,6 +693,9 @@ public interface DoubleStream extends BaseStream { * Returns a {@code Stream} consisting of the elements of this stream, * boxed to {@code Double}. * + *

    This is an intermediate + * operation. + * * @return a {@code Stream} consistent of the elements of this stream, * each boxed to a {@code Double} */ @@ -686,7 +745,7 @@ public interface DoubleStream extends BaseStream { } /** - * Returns a sequential stream whose elements are the specified values. + * Returns a sequential ordered stream whose elements are the specified values. * * @param values the elements of the new stream * @return the new stream @@ -696,7 +755,7 @@ public interface DoubleStream extends BaseStream { } /** - * Returns an infinite sequential {@code DoubleStream} produced by iterative + * Returns an infinite sequential ordered {@code DoubleStream} produced by iterative * application of a function {@code f} to an initial element {@code seed}, * producing a {@code Stream} consisting of {@code seed}, {@code f(seed)}, * {@code f(f(seed))}, etc. @@ -734,8 +793,8 @@ public interface DoubleStream extends BaseStream { } /** - * Returns a sequential {@code DoubleStream} where each element is - * generated by an {@code DoubleSupplier}. This is suitable for generating + * Returns a sequential stream where each element is generated by + * the provided {@code DoubleSupplier}. This is suitable for generating * constant streams, streams of random elements, etc. * * @param s the {@code DoubleSupplier} for generated elements @@ -748,16 +807,16 @@ public interface DoubleStream extends BaseStream { } /** - * Creates a lazy concatenated {@code DoubleStream} whose elements are all the - * elements of a first {@code DoubleStream} succeeded by all the elements of the - * second {@code DoubleStream}. The resulting stream is ordered if both + * Creates a lazily concatenated stream whose elements are all the + * elements of the first stream followed by all the elements of the + * second stream. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input * streams is parallel. When the resulting stream is closed, the close * handlers for both input streams are invoked. * * @param a the first stream - * @param b the second stream to concatenate on to end of the first stream - * @return the concatenation of the two streams + * @param b the second stream + * @return the concatenation of the two input streams */ public static DoubleStream concat(DoubleStream a, DoubleStream b) { Objects.requireNonNull(a); @@ -772,9 +831,9 @@ public interface DoubleStream extends BaseStream { /** * A mutable builder for a {@code DoubleStream}. * - *

    A stream builder has a lifecycle, where it starts in a building - * phase, during which elements can be added, and then transitions to a - * built phase, after which elements may not be added. The built phase + *

    A stream builder has a lifecycle, which starts in a building + * phase, during which elements can be added, and then transitions to a built + * phase, after which elements may not be added. The built phase * begins when the {@link #build()} method is called, which creates an * ordered stream whose elements are the elements that were added to the * stream builder, in the order they were added. diff --git a/jdk/src/share/classes/java/util/stream/IntPipeline.java b/jdk/src/share/classes/java/util/stream/IntPipeline.java index f35bc1d7a8e..13f7e0a9731 100644 --- a/jdk/src/share/classes/java/util/stream/IntPipeline.java +++ b/jdk/src/share/classes/java/util/stream/IntPipeline.java @@ -349,8 +349,8 @@ abstract class IntPipeline } @Override - public final IntStream peek(IntConsumer consumer) { - Objects.requireNonNull(consumer); + public final IntStream peek(IntConsumer action) { + Objects.requireNonNull(action); return new StatelessOp(this, StreamShape.INT_VALUE, 0) { @Override @@ -358,7 +358,7 @@ abstract class IntPipeline return new Sink.ChainedInt(sink) { @Override public void accept(int t) { - consumer.accept(t); + action.accept(t); downstream.accept(t); } }; @@ -473,14 +473,14 @@ abstract class IntPipeline } @Override - public final R collect(Supplier resultFactory, + public final R collect(Supplier supplier, ObjIntConsumer accumulator, BiConsumer combiner) { BinaryOperator operator = (left, right) -> { combiner.accept(left, right); return left; }; - return evaluate(ReduceOps.makeInt(resultFactory, accumulator, operator)); + return evaluate(ReduceOps.makeInt(supplier, accumulator, operator)); } @Override diff --git a/jdk/src/share/classes/java/util/stream/IntStream.java b/jdk/src/share/classes/java/util/stream/IntStream.java index c107ca46de9..07f9ab5dc78 100644 --- a/jdk/src/share/classes/java/util/stream/IntStream.java +++ b/jdk/src/share/classes/java/util/stream/IntStream.java @@ -24,7 +24,11 @@ */ package java.util.stream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.IntSummaryStatistics; import java.util.Objects; import java.util.OptionalDouble; @@ -32,6 +36,7 @@ import java.util.OptionalInt; import java.util.PrimitiveIterator; import java.util.Spliterator; import java.util.Spliterators; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.IntBinaryOperator; @@ -46,40 +51,87 @@ import java.util.function.ObjIntConsumer; import java.util.function.Supplier; /** - * A sequence of primitive integer elements supporting sequential and parallel - * bulk operations. Streams support lazy intermediate operations (transforming - * a stream to another stream) such as {@code filter} and {@code map}, and terminal - * operations (consuming the contents of a stream to produce a result or - * side-effect), such as {@code forEach}, {@code findFirst}, and {@code - * iterator}. Once an operation has been performed on a stream, it - * is considered consumed and no longer usable for other operations. + * A sequence of elements supporting sequential and parallel aggregate + * operations. The following example illustrates an aggregate operation using + * {@link Stream} and {@link IntStream}: * - *

    For sequential stream pipelines, all operations are performed in the - * encounter order of the pipeline - * source, if the pipeline source has a defined encounter order. + *

    {@code
    + *     int sum = widgets.stream()
    + *                      .filter(w -> w.getColor() == RED)
    + *                      .mapToInt(w -> w.getWeight())
    + *                      .sum();
    + * }
    * - *

    For parallel stream pipelines, unless otherwise specified, intermediate - * stream operations preserve the - * encounter order of their source, and terminal operations - * respect the encounter order of their source, if the source - * has an encounter order. Provided that and parameters to stream operations - * satisfy the non-interference - * requirements, and excepting differences arising from the absence of - * a defined encounter order, the result of a stream pipeline should be the - * stable across multiple executions of the same operations on the same source. - * However, the timing and thread in which side-effects occur (for those - * operations which are allowed to produce side-effects, such as - * {@link #forEach(IntConsumer)}), are explicitly nondeterministic for parallel - * execution of stream pipelines. + * In this example, {@code widgets} is a {@code Collection}. We create + * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()}, + * filter it to produce a stream containing only the red widgets, and then + * transform it into a stream of {@code int} values representing the weight of + * each red widget. Then this stream is summed to produce a total weight. * - *

    Unless otherwise noted, passing a {@code null} argument to any stream - * method may result in a {@link NullPointerException}. + *

    To perform a computation, stream + * operations are composed into a + * stream pipeline. A stream pipeline consists of a source (which + * might be an array, a collection, a generator function, an IO channel, + * etc), zero or more intermediate operations (which transform a + * stream into another stream, such as {@link IntStream#filter(IntPredicate)}), and a + * terminal operation (which produces a result or side-effect, such + * as {@link IntStream#sum()} or {@link IntStream#forEach(IntConsumer)}). + * Streams are lazy; computation on the source data is only performed when the + * terminal operation is initiated, and source elements are consumed only + * as needed. * - * @apiNote - * Streams are not data structures; they do not manage the storage for their - * elements, nor do they support access to individual elements. However, - * you can use the {@link #iterator()} or {@link #spliterator()} operations to - * perform a controlled traversal. + *

    Collections and streams, while bearing some superficial similarities, + * have different goals. Collections are primarily concerned with the efficient + * management of, and access to, their elements. By contrast, streams do not + * provide a means to directly access or manipulate their elements, and are + * instead concerned with declaratively describing their source and the + * computational operations which will be performed in aggregate on that source. + * However, if the provided stream operations do not offer the desired + * functionality, the {@link #iterator()} and {@link #spliterator()} operations + * can be used to perform a controlled traversal. + * + *

    A stream pipeline, like the "widgets" example above, can be viewed as + * a query on the stream source. Unless the source was explicitly + * designed for concurrent modification (such as a {@link ConcurrentHashMap}), + * unpredictable or erroneous behavior may result from modifying the stream + * source while it is being queried. + * + *

    Most stream operations accept parameters that describe user-specified + * behavior, such as the lambda expression {@code w -> w.getWeight()} passed to + * {@code mapToInt} in the example above. Such parameters are always instances + * of a functional interface such + * as {@link java.util.function.Function}, and are often lambda expressions or + * method references. These parameters can never be null, should not modify the + * stream source, and should be + * effectively stateless + * (their result should not depend on any state that might change during + * execution of the stream pipeline.) + * + *

    A stream should be operated on (invoking an intermediate or terminal stream + * operation) only once. This rules out, for example, "forked" streams, where + * the same source feeds two or more pipelines, or multiple traversals of the + * same stream. A stream implementation may throw {@link IllegalStateException} + * if it detects that the stream is being reused. However, since some stream + * operations may return their receiver rather than a new stream object, it may + * not be possible to detect reuse in all cases. + * + *

    Streams have a {@link #close()} method and implement {@link AutoCloseable}, + * but nearly all stream instances do not actually need to be closed after use. + * Generally, only streams whose source is an IO channel (such as those returned + * by {@link Files#lines(Path, Charset)}) will require closing. Most streams + * are backed by collections, arrays, or generating functions, which require no + * special resource management. (If a stream does require closing, it can be + * declared as a resource in a {@code try}-with-resources statement.) + * + *

    Stream pipelines may execute either sequentially or in + * parallel. This + * execution mode is a property of the stream. Streams are created + * with an initial choice of sequential or parallel execution. (For example, + * {@link Collection#stream() Collection.stream()} creates a sequential stream, + * and {@link Collection#parallelStream() Collection.parallelStream()} creates + * a parallel one.) This choice of execution mode may be modified by the + * {@link #sequential()} or {@link #parallel()} methods, and may be queried with + * the {@link #isParallel()} method. * * @since 1.8 * @see java.util.stream @@ -160,22 +212,13 @@ public interface IntStream extends BaseStream { /** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of the stream produced by applying the - * provided mapping function to each element. + * provided mapping function to each element. (If the result of the mapping + * function is {@code null}, this is treated as if the result was an empty + * stream.) * *

    This is an intermediate * operation. * - * @apiNote - * The {@code flatMap()} operation has the effect of applying a one-to-many - * tranformation to the elements of the stream, and then flattening the - * resulting elements into a new stream. For example, if {@code orders} - * is a stream of purchase orders, and each purchase order contains a - * collection of line items, then the following produces a stream of line - * items: - *

    {@code
    -     *     orderStream.flatMap(order -> order.getLineItems().stream())...
    -     * }
    - * * @param mapper a * non-interfering, stateless function to apply to * each element which produces an {@code IntStream} of new @@ -224,18 +267,18 @@ public interface IntStream extends BaseStream { *
    {@code
          *     list.stream()
          *         .filter(filteringFunction)
    -     *         .peek(e -> {System.out.println("Filtered value: " + e); });
    +     *         .peek(e -> System.out.println("Filtered value: " + e));
          *         .map(mappingFunction)
    -     *         .peek(e -> {System.out.println("Mapped value: " + e); });
    +     *         .peek(e -> System.out.println("Mapped value: " + e));
          *         .collect(Collectors.toIntSummaryStastistics());
          * }
    * - * @param consumer a - * non-interfering action to perform on the elements as - * they are consumed from the stream + * @param action a + * non-interfering action to perform on the elements as + * they are consumed from the stream * @return the new stream */ - IntStream peek(IntConsumer consumer); + IntStream peek(IntConsumer action); /** * Returns a stream consisting of the elements of this stream, truncated @@ -252,8 +295,8 @@ public interface IntStream extends BaseStream { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream. If the - * {@code startInclusive} index lies past the end of this stream then an + * after discarding the first {@code startInclusive} elements of the stream. + * If this stream contains fewer than {@code startInclusive} elements then an * empty stream will be returned. * *

    This is a stateful @@ -267,10 +310,10 @@ public interface IntStream extends BaseStream { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream and - * truncated to contain no more than {@code endExclusive - startInclusive} - * elements. If the {@code startInclusive} index lies past the end - * of this stream then an empty stream will be returned. + * after discarding the first {@code startInclusive} elements and truncating + * the result to be no longer than {@code endExclusive - startInclusive} + * elements in length. If this stream contains fewer than + * {@code startInclusive} elements then an empty stream will be returned. * *

    This is a short-circuiting * stateful intermediate operation. @@ -419,12 +462,12 @@ public interface IntStream extends BaseStream { /** * Performs a mutable * reduction operation on the elements of this stream. A mutable - * reduction is one in which the reduced value is a mutable value holder, + * reduction is one in which the reduced value is a mutable result container, * such as an {@code ArrayList}, and elements are incorporated by updating - * the state of the result, rather than by replacing the result. This + * the state of the result rather than by replacing the result. This * produces a result equivalent to: *

    {@code
    -     *     R result = resultFactory.get();
    +     *     R result = supplier.get();
          *     for (int element : this stream)
          *         accumulator.accept(result, element);
          *     return result;
    @@ -437,10 +480,9 @@ public interface IntStream extends BaseStream {
          * operation.
          *
          * @param  type of the result
    -     * @param resultFactory a function that creates a new result container.
    -     *                      For a parallel execution, this function may be
    -     *                      called multiple times and must return a fresh value
    -     *                      each time.
    +     * @param supplier a function that creates a new result container. For a
    +     *                 parallel execution, this function may be called
    +     *                 multiple times and must return a fresh value each time.
          * @param accumulator an associative
          *                    non-interfering,
          *                    stateless function for incorporating an additional
    @@ -452,18 +494,21 @@ public interface IntStream extends BaseStream {
          * @return the result of the reduction
          * @see Stream#collect(Supplier, BiConsumer, BiConsumer)
          */
    -     R collect(Supplier resultFactory,
    +     R collect(Supplier supplier,
                       ObjIntConsumer accumulator,
                       BiConsumer combiner);
     
         /**
          * Returns the sum of elements in this stream.  This is a special case
    -     * of a reduction
    +     * of a reduction
          * and is equivalent to:
          * 
    {@code
          *     return reduce(0, Integer::sum);
          * }
    * + *

    This is a terminal + * operation. + * * @return the sum of elements in this stream */ int sum(); @@ -471,7 +516,7 @@ public interface IntStream extends BaseStream { /** * Returns an {@code OptionalInt} describing the minimum element of this * stream, or an empty optional if this stream is empty. This is a special - * case of a reduction + * case of a reduction * and is equivalent to: *

    {@code
          *     return reduce(Integer::min);
    @@ -479,7 +524,6 @@ public interface IntStream extends BaseStream {
          *
          * 

    This is a terminal operation. * - * @return an {@code OptionalInt} containing the minimum element of this * stream, or an empty {@code OptionalInt} if the stream is empty */ @@ -488,7 +532,7 @@ public interface IntStream extends BaseStream { /** * Returns an {@code OptionalInt} describing the maximum element of this * stream, or an empty optional if this stream is empty. This is a special - * case of a reduction + * case of a reduction * and is equivalent to: *

    {@code
          *     return reduce(Integer::max);
    @@ -504,7 +548,7 @@ public interface IntStream extends BaseStream {
     
         /**
          * Returns the count of elements in this stream.  This is a special case of
    -     * a reduction and is
    +     * a reduction and is
          * equivalent to:
          * 
    {@code
          *     return mapToLong(e -> 1L).sum();
    @@ -520,7 +564,10 @@ public interface IntStream extends BaseStream {
          * Returns an {@code OptionalDouble} describing the arithmetic mean of elements of
          * this stream, or an empty optional if this stream is empty.  This is a
          * special case of a
    -     * reduction.
    +     * reduction.
    +     *
    +     * 

    This is a terminal + * operation. * * @return an {@code OptionalDouble} containing the average element of this * stream, or an empty optional if the stream is empty @@ -530,7 +577,10 @@ public interface IntStream extends BaseStream { /** * Returns an {@code IntSummaryStatistics} describing various * summary data about the elements of this stream. This is a special - * case of a reduction. + * case of a reduction. + * + *

    This is a terminal + * operation. * * @return an {@code IntSummaryStatistics} describing various summary data * about the elements of this stream @@ -587,9 +637,8 @@ public interface IntStream extends BaseStream { /** * Returns an {@link OptionalInt} describing the first element of this - * stream (in the encounter order), or an empty {@code OptionalInt} if the - * stream is empty. If the stream has no encounter order, then any element - * may be returned. + * stream, or an empty {@code OptionalInt} if the stream is empty. If the + * stream has no encounter order, then any element may be returned. * *

    This is a short-circuiting * terminal operation. @@ -609,8 +658,8 @@ public interface IntStream extends BaseStream { *

    The behavior of this operation is explicitly nondeterministic; it is * free to select any element in the stream. This is to allow for maximal * performance in parallel operations; the cost is that multiple invocations - * on the same source may not return the same result. (If the first element - * in the encounter order is desired, use {@link #findFirst()} instead.) + * on the same source may not return the same result. (If a stable result + * is desired, use {@link #findFirst()} instead.) * * @return an {@code OptionalInt} describing some element of this stream, or * an empty {@code OptionalInt} if the stream is empty @@ -622,6 +671,9 @@ public interface IntStream extends BaseStream { * Returns a {@code LongStream} consisting of the elements of this stream, * converted to {@code long}. * + *

    This is an intermediate + * operation. + * * @return a {@code LongStream} consisting of the elements of this stream, * converted to {@code long} */ @@ -631,6 +683,9 @@ public interface IntStream extends BaseStream { * Returns a {@code DoubleStream} consisting of the elements of this stream, * converted to {@code double}. * + *

    This is an intermediate + * operation. + * * @return a {@code DoubleStream} consisting of the elements of this stream, * converted to {@code double} */ @@ -640,6 +695,9 @@ public interface IntStream extends BaseStream { * Returns a {@code Stream} consisting of the elements of this stream, * each boxed to an {@code Integer}. * + *

    This is an intermediate + * operation. + * * @return a {@code Stream} consistent of the elements of this stream, * each boxed to an {@code Integer} */ @@ -688,7 +746,7 @@ public interface IntStream extends BaseStream { } /** - * Returns a sequential stream whose elements are the specified values. + * Returns a sequential ordered stream whose elements are the specified values. * * @param values the elements of the new stream * @return the new stream @@ -698,7 +756,7 @@ public interface IntStream extends BaseStream { } /** - * Returns an infinite sequential {@code IntStream} produced by iterative + * Returns an infinite sequential ordered {@code IntStream} produced by iterative * application of a function {@code f} to an initial element {@code seed}, * producing a {@code Stream} consisting of {@code seed}, {@code f(seed)}, * {@code f(f(seed))}, etc. @@ -736,8 +794,8 @@ public interface IntStream extends BaseStream { } /** - * Returns a sequential {@code IntStream} where each element is - * generated by an {@code IntSupplier}. This is suitable for generating + * Returns a sequential stream where each element is generated by + * the provided {@code IntSupplier}. This is suitable for generating * constant streams, streams of random elements, etc. * * @param s the {@code IntSupplier} for generated elements @@ -750,7 +808,7 @@ public interface IntStream extends BaseStream { } /** - * Returns a sequential {@code IntStream} from {@code startInclusive} + * Returns a sequential ordered {@code IntStream} from {@code startInclusive} * (inclusive) to {@code endExclusive} (exclusive) by an incremental step of * {@code 1}. * @@ -776,7 +834,7 @@ public interface IntStream extends BaseStream { } /** - * Returns a sequential {@code IntStream} from {@code startInclusive} + * Returns a sequential ordered {@code IntStream} from {@code startInclusive} * (inclusive) to {@code endInclusive} (inclusive) by an incremental step of * {@code 1}. * @@ -802,16 +860,16 @@ public interface IntStream extends BaseStream { } /** - * Creates a lazy concatenated {@code IntStream} whose elements are all the - * elements of a first {@code IntStream} succeeded by all the elements of the - * second {@code IntStream}. The resulting stream is ordered if both + * Creates a lazily concatenated stream whose elements are all the + * elements of the first stream followed by all the elements of the + * second stream. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input * streams is parallel. When the resulting stream is closed, the close * handlers for both input streams are invoked. * * @param a the first stream - * @param b the second stream to concatenate on to end of the first stream - * @return the concatenation of the two streams + * @param b the second stream + * @return the concatenation of the two input streams */ public static IntStream concat(IntStream a, IntStream b) { Objects.requireNonNull(a); @@ -826,9 +884,9 @@ public interface IntStream extends BaseStream { /** * A mutable builder for an {@code IntStream}. * - *

    A stream builder has a lifecycle, where it starts in a building - * phase, during which elements can be added, and then transitions to a - * built phase, after which elements may not be added. The built phase + *

    A stream builder has a lifecycle, which starts in a building + * phase, during which elements can be added, and then transitions to a built + * phase, after which elements may not be added. The built phase * begins when the {@link #build()} method is called, which creates an * ordered stream whose elements are the elements that were added to the * stream builder, in the order they were added. diff --git a/jdk/src/share/classes/java/util/stream/LongPipeline.java b/jdk/src/share/classes/java/util/stream/LongPipeline.java index a59ec3f5f00..5ed030e02a1 100644 --- a/jdk/src/share/classes/java/util/stream/LongPipeline.java +++ b/jdk/src/share/classes/java/util/stream/LongPipeline.java @@ -330,8 +330,8 @@ abstract class LongPipeline } @Override - public final LongStream peek(LongConsumer consumer) { - Objects.requireNonNull(consumer); + public final LongStream peek(LongConsumer action) { + Objects.requireNonNull(action); return new StatelessOp(this, StreamShape.LONG_VALUE, 0) { @Override @@ -339,7 +339,7 @@ abstract class LongPipeline return new Sink.ChainedLong(sink) { @Override public void accept(long t) { - consumer.accept(t); + action.accept(t); downstream.accept(t); } }; @@ -455,14 +455,14 @@ abstract class LongPipeline } @Override - public final R collect(Supplier resultFactory, + public final R collect(Supplier supplier, ObjLongConsumer accumulator, BiConsumer combiner) { BinaryOperator operator = (left, right) -> { combiner.accept(left, right); return left; }; - return evaluate(ReduceOps.makeLong(resultFactory, accumulator, operator)); + return evaluate(ReduceOps.makeLong(supplier, accumulator, operator)); } @Override diff --git a/jdk/src/share/classes/java/util/stream/LongStream.java b/jdk/src/share/classes/java/util/stream/LongStream.java index e64c67204dc..ca61d2f200e 100644 --- a/jdk/src/share/classes/java/util/stream/LongStream.java +++ b/jdk/src/share/classes/java/util/stream/LongStream.java @@ -24,7 +24,11 @@ */ package java.util.stream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.LongSummaryStatistics; import java.util.Objects; import java.util.OptionalDouble; @@ -32,6 +36,7 @@ import java.util.OptionalLong; import java.util.PrimitiveIterator; import java.util.Spliterator; import java.util.Spliterators; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.LongBinaryOperator; @@ -46,40 +51,87 @@ import java.util.function.ObjLongConsumer; import java.util.function.Supplier; /** - * A sequence of primitive long elements supporting sequential and parallel - * bulk operations. Streams support lazy intermediate operations (transforming - * a stream to another stream) such as {@code filter} and {@code map}, and terminal - * operations (consuming the contents of a stream to produce a result or - * side-effect), such as {@code forEach}, {@code findFirst}, and {@code - * iterator}. Once an operation has been performed on a stream, it - * is considered consumed and no longer usable for other operations. + * A sequence of elements supporting sequential and parallel aggregate + * operations. The following example illustrates an aggregate operation using + * {@link Stream} and {@link LongStream}: * - *

    For sequential stream pipelines, all operations are performed in the - * encounter order of the pipeline - * source, if the pipeline source has a defined encounter order. + *

    {@code
    + *     long sum = widgets.stream()
    + *                       .filter(w -> w.getColor() == RED)
    + *                       .mapToLong(w -> w.getWeight())
    + *                       .sum();
    + * }
    * - *

    For parallel stream pipelines, unless otherwise specified, intermediate - * stream operations preserve the - * encounter order of their source, and terminal operations - * respect the encounter order of their source, if the source - * has an encounter order. Provided that and parameters to stream operations - * satisfy the non-interference - * requirements, and excepting differences arising from the absence of - * a defined encounter order, the result of a stream pipeline should be the - * stable across multiple executions of the same operations on the same source. - * However, the timing and thread in which side-effects occur (for those - * operations which are allowed to produce side-effects, such as - * {@link #forEach(LongConsumer)}), are explicitly nondeterministic for parallel - * execution of stream pipelines. + * In this example, {@code widgets} is a {@code Collection}. We create + * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()}, + * filter it to produce a stream containing only the red widgets, and then + * transform it into a stream of {@code long} values representing the weight of + * each red widget. Then this stream is summed to produce a total weight. * - *

    Unless otherwise noted, passing a {@code null} argument to any stream - * method may result in a {@link NullPointerException}. + *

    To perform a computation, stream + * operations are composed into a + * stream pipeline. A stream pipeline consists of a source (which + * might be an array, a collection, a generator function, an IO channel, + * etc), zero or more intermediate operations (which transform a + * stream into another stream, such as {@link LongStream#filter(LongPredicate)}), and a + * terminal operation (which produces a result or side-effect, such + * as {@link LongStream#sum()} or {@link LongStream#forEach(LongConsumer)}). + * Streams are lazy; computation on the source data is only performed when the + * terminal operation is initiated, and source elements are consumed only + * as needed. * - * @apiNote - * Streams are not data structures; they do not manage the storage for their - * elements, nor do they support access to individual elements. However, - * you can use the {@link #iterator()} or {@link #spliterator()} operations to - * perform a controlled traversal. + *

    Collections and streams, while bearing some superficial similarities, + * have different goals. Collections are primarily concerned with the efficient + * management of, and access to, their elements. By contrast, streams do not + * provide a means to directly access or manipulate their elements, and are + * instead concerned with declaratively describing their source and the + * computational operations which will be performed in aggregate on that source. + * However, if the provided stream operations do not offer the desired + * functionality, the {@link #iterator()} and {@link #spliterator()} operations + * can be used to perform a controlled traversal. + * + *

    A stream pipeline, like the "widgets" example above, can be viewed as + * a query on the stream source. Unless the source was explicitly + * designed for concurrent modification (such as a {@link ConcurrentHashMap}), + * unpredictable or erroneous behavior may result from modifying the stream + * source while it is being queried. + * + *

    Most stream operations accept parameters that describe user-specified + * behavior, such as the lambda expression {@code w -> w.getWeight()} passed to + * {@code mapToLong} in the example above. Such parameters are always instances + * of a functional interface such + * as {@link java.util.function.Function}, and are often lambda expressions or + * method references. These parameters can never be null, should not modify the + * stream source, and should be + * effectively stateless + * (their result should not depend on any state that might change during + * execution of the stream pipeline.) + * + *

    A stream should be operated on (invoking an intermediate or terminal stream + * operation) only once. This rules out, for example, "forked" streams, where + * the same source feeds two or more pipelines, or multiple traversals of the + * same stream. A stream implementation may throw {@link IllegalStateException} + * if it detects that the stream is being reused. However, since some stream + * operations may return their receiver rather than a new stream object, it may + * not be possible to detect reuse in all cases. + * + *

    Streams have a {@link #close()} method and implement {@link AutoCloseable}, + * but nearly all stream instances do not actually need to be closed after use. + * Generally, only streams whose source is an IO channel (such as those returned + * by {@link Files#lines(Path, Charset)}) will require closing. Most streams + * are backed by collections, arrays, or generating functions, which require no + * special resource management. (If a stream does require closing, it can be + * declared as a resource in a {@code try}-with-resources statement.) + * + *

    Stream pipelines may execute either sequentially or in + * parallel. This + * execution mode is a property of the stream. Streams are created + * with an initial choice of sequential or parallel execution. (For example, + * {@link Collection#stream() Collection.stream()} creates a sequential stream, + * and {@link Collection#parallelStream() Collection.parallelStream()} creates + * a parallel one.) This choice of execution mode may be modified by the + * {@link #sequential()} or {@link #parallel()} methods, and may be queried with + * the {@link #isParallel()} method. * * @since 1.8 * @see java.util.stream @@ -160,22 +212,13 @@ public interface LongStream extends BaseStream { /** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of the stream produced by applying the - * provided mapping function to each element. + * provided mapping function to each element. (If the result of the mapping + * function is {@code null}, this is treated as if the result was an empty + * stream.) * *

    This is an intermediate * operation. * - * @apiNote - * The {@code flatMap()} operation has the effect of applying a one-to-many - * tranformation to the elements of the stream, and then flattening the - * resulting elements into a new stream. For example, if {@code orders} - * is a stream of purchase orders, and each purchase order contains a - * collection of line items, then the following produces a stream of line - * items: - *

    {@code
    -     *     orderStream.flatMap(order -> order.getLineItems().stream())...
    -     * }
    - * * @param mapper a * non-interfering, stateless function to apply to * each element which produces an {@code LongStream} of new @@ -224,18 +267,18 @@ public interface LongStream extends BaseStream { *
    {@code
          *     list.stream()
          *         .filter(filteringFunction)
    -     *         .peek(e -> {System.out.println("Filtered value: " + e); });
    +     *         .peek(e -> System.out.println("Filtered value: " + e));
          *         .map(mappingFunction)
    -     *         .peek(e -> {System.out.println("Mapped value: " + e); });
    +     *         .peek(e -> System.out.println("Mapped value: " + e));
          *         .collect(Collectors.toLongSummaryStastistics());
          * }
    * - * @param consumer a - * non-interfering action to perform on the elements as - * they are consumed from the stream + * @param action a + * non-interfering action to perform on the elements as + * they are consumed from the stream * @return the new stream */ - LongStream peek(LongConsumer consumer); + LongStream peek(LongConsumer action); /** * Returns a stream consisting of the elements of this stream, truncated @@ -252,8 +295,8 @@ public interface LongStream extends BaseStream { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream. If the - * {@code startInclusive} index lies past the end of this stream then an + * after discarding the first {@code startInclusive} elements of the stream. + * If this stream contains fewer than {@code startInclusive} elements then an * empty stream will be returned. * *

    This is a stateful @@ -267,10 +310,10 @@ public interface LongStream extends BaseStream { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream and - * truncated to contain no more than {@code endExclusive - startInclusive} - * elements. If the {@code startInclusive} index lies past the end - * of this stream then an empty stream will be returned. + * after discarding the first {@code startInclusive} elements and truncating + * the result to be no longer than {@code endExclusive - startInclusive} + * elements in length. If this stream contains fewer than + * {@code startInclusive} elements then an empty stream will be returned. * *

    This is a short-circuiting * stateful intermediate operation. @@ -419,12 +462,12 @@ public interface LongStream extends BaseStream { /** * Performs a mutable * reduction operation on the elements of this stream. A mutable - * reduction is one in which the reduced value is a mutable value holder, + * reduction is one in which the reduced value is a mutable result container, * such as an {@code ArrayList}, and elements are incorporated by updating - * the state of the result, rather than by replacing the result. This + * the state of the result rather than by replacing the result. This * produces a result equivalent to: *

    {@code
    -     *     R result = resultFactory.get();
    +     *     R result = supplier.get();
          *     for (long element : this stream)
          *         accumulator.accept(result, element);
          *     return result;
    @@ -437,10 +480,9 @@ public interface LongStream extends BaseStream {
          * operation.
          *
          * @param  type of the result
    -     * @param resultFactory a function that creates a new result container.
    -     *                      For a parallel execution, this function may be
    -     *                      called multiple times and must return a fresh value
    -     *                      each time.
    +     * @param supplier a function that creates a new result container. For a
    +     *                 parallel execution, this function may be called
    +     *                 multiple times and must return a fresh value each time.
          * @param accumulator an associative
          *                    non-interfering,
          *                    stateless function for incorporating an additional
    @@ -452,18 +494,21 @@ public interface LongStream extends BaseStream {
          * @return the result of the reduction
          * @see Stream#collect(Supplier, BiConsumer, BiConsumer)
          */
    -     R collect(Supplier resultFactory,
    +     R collect(Supplier supplier,
                       ObjLongConsumer accumulator,
                       BiConsumer combiner);
     
         /**
          * Returns the sum of elements in this stream.  This is a special case
    -     * of a reduction
    +     * of a reduction
          * and is equivalent to:
          * 
    {@code
          *     return reduce(0, Long::sum);
          * }
    * + *

    This is a terminal + * operation. + * * @return the sum of elements in this stream */ long sum(); @@ -471,7 +516,7 @@ public interface LongStream extends BaseStream { /** * Returns an {@code OptionalLong} describing the minimum element of this * stream, or an empty optional if this stream is empty. This is a special - * case of a reduction + * case of a reduction * and is equivalent to: *

    {@code
          *     return reduce(Long::min);
    @@ -479,7 +524,6 @@ public interface LongStream extends BaseStream {
          *
          * 

    This is a terminal operation. * - * @return an {@code OptionalLong} containing the minimum element of this * stream, or an empty {@code OptionalLong} if the stream is empty */ @@ -488,7 +532,7 @@ public interface LongStream extends BaseStream { /** * Returns an {@code OptionalLong} describing the maximum element of this * stream, or an empty optional if this stream is empty. This is a special - * case of a reduction + * case of a reduction * and is equivalent to: *

    {@code
          *     return reduce(Long::max);
    @@ -504,7 +548,7 @@ public interface LongStream extends BaseStream {
     
         /**
          * Returns the count of elements in this stream.  This is a special case of
    -     * a reduction and is
    +     * a reduction and is
          * equivalent to:
          * 
    {@code
          *     return map(e -> 1L).sum();
    @@ -520,7 +564,10 @@ public interface LongStream extends BaseStream {
          * Returns an {@code OptionalDouble} describing the arithmetic mean of elements of
          * this stream, or an empty optional if this stream is empty.  This is a
          * special case of a
    -     * reduction.
    +     * reduction.
    +     *
    +     * 

    This is a terminal + * operation. * * @return an {@code OptionalDouble} containing the average element of this * stream, or an empty optional if the stream is empty @@ -530,7 +577,10 @@ public interface LongStream extends BaseStream { /** * Returns a {@code LongSummaryStatistics} describing various summary data * about the elements of this stream. This is a special case of a - * reduction. + * reduction. + * + *

    This is a terminal + * operation. * * @return a {@code LongSummaryStatistics} describing various summary data * about the elements of this stream @@ -587,9 +637,8 @@ public interface LongStream extends BaseStream { /** * Returns an {@link OptionalLong} describing the first element of this - * stream (in the encounter order), or an empty {@code OptionalLong} if the - * stream is empty. If the stream has no encounter order, then any element - * may be returned. + * stream, or an empty {@code OptionalLong} if the stream is empty. If the + * stream has no encounter order, then any element may be returned. * *

    This is a short-circuiting * terminal operation. @@ -609,8 +658,8 @@ public interface LongStream extends BaseStream { *

    The behavior of this operation is explicitly nondeterministic; it is * free to select any element in the stream. This is to allow for maximal * performance in parallel operations; the cost is that multiple invocations - * on the same source may not return the same result. (If the first element - * in the encounter order is desired, use {@link #findFirst()} instead.) + * on the same source may not return the same result. (If a stable result + * is desired, use {@link #findFirst()} instead.) * * @return an {@code OptionalLong} describing some element of this stream, * or an empty {@code OptionalLong} if the stream is empty @@ -622,6 +671,9 @@ public interface LongStream extends BaseStream { * Returns a {@code DoubleStream} consisting of the elements of this stream, * converted to {@code double}. * + *

    This is an intermediate + * operation. + * * @return a {@code DoubleStream} consisting of the elements of this stream, * converted to {@code double} */ @@ -631,6 +683,9 @@ public interface LongStream extends BaseStream { * Returns a {@code Stream} consisting of the elements of this stream, * each boxed to a {@code Long}. * + *

    This is an intermediate + * operation. + * * @return a {@code Stream} consistent of the elements of this stream, * each boxed to {@code Long} */ @@ -679,7 +734,7 @@ public interface LongStream extends BaseStream { } /** - * Returns a sequential stream whose elements are the specified values. + * Returns a sequential ordered stream whose elements are the specified values. * * @param values the elements of the new stream * @return the new stream @@ -689,7 +744,7 @@ public interface LongStream extends BaseStream { } /** - * Returns an infinite sequential {@code LongStream} produced by iterative + * Returns an infinite sequential ordered {@code LongStream} produced by iterative * application of a function {@code f} to an initial element {@code seed}, * producing a {@code Stream} consisting of {@code seed}, {@code f(seed)}, * {@code f(f(seed))}, etc. @@ -727,9 +782,9 @@ public interface LongStream extends BaseStream { } /** - * Returns a sequential {@code LongStream} where each element is generated - * by a {@code LongSupplier}. This is suitable for generating constant - * streams, streams of random elements, etc. + * Returns a sequential stream where each element is generated by + * the provided {@code LongSupplier}. This is suitable for generating + * constant streams, streams of random elements, etc. * * @param s the {@code LongSupplier} for generated elements * @return a new sequential {@code LongStream} @@ -741,7 +796,7 @@ public interface LongStream extends BaseStream { } /** - * Returns a sequential {@code LongStream} from {@code startInclusive} + * Returns a sequential ordered {@code LongStream} from {@code startInclusive} * (inclusive) to {@code endExclusive} (exclusive) by an incremental step of * {@code 1}. * @@ -774,7 +829,7 @@ public interface LongStream extends BaseStream { } /** - * Returns a sequential {@code LongStream} from {@code startInclusive} + * Returns a sequential ordered {@code LongStream} from {@code startInclusive} * (inclusive) to {@code endInclusive} (inclusive) by an incremental step of * {@code 1}. * @@ -808,16 +863,16 @@ public interface LongStream extends BaseStream { } /** - * Creates a lazy concatenated {@code LongStream} whose elements are all the - * elements of a first {@code LongStream} succeeded by all the elements of the - * second {@code LongStream}. The resulting stream is ordered if both + * Creates a lazily concatenated stream whose elements are all the + * elements of the first stream followed by all the elements of the + * second stream. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input * streams is parallel. When the resulting stream is closed, the close * handlers for both input streams are invoked. * * @param a the first stream - * @param b the second stream to concatenate on to end of the first stream - * @return the concatenation of the two streams + * @param b the second stream + * @return the concatenation of the two input streams */ public static LongStream concat(LongStream a, LongStream b) { Objects.requireNonNull(a); @@ -832,9 +887,9 @@ public interface LongStream extends BaseStream { /** * A mutable builder for a {@code LongStream}. * - *

    A stream builder has a lifecycle, where it starts in a building - * phase, during which elements can be added, and then transitions to a - * built phase, after which elements may not be added. The built phase + *

    A stream builder has a lifecycle, which starts in a building + * phase, during which elements can be added, and then transitions to a built + * phase, after which elements may not be added. The built phase begins * begins when the {@link #build()} method is called, which creates an * ordered stream whose elements are the elements that were added to the * stream builder, in the order they were added. diff --git a/jdk/src/share/classes/java/util/stream/PipelineHelper.java b/jdk/src/share/classes/java/util/stream/PipelineHelper.java index 6824e3b3179..f510131d6ec 100644 --- a/jdk/src/share/classes/java/util/stream/PipelineHelper.java +++ b/jdk/src/share/classes/java/util/stream/PipelineHelper.java @@ -28,7 +28,7 @@ import java.util.Spliterator; import java.util.function.IntFunction; /** - * Helper class for executing + * Helper class for executing * stream pipelines, capturing all of the information about a stream * pipeline (output shape, intermediate operations, stream flags, parallelism, * etc) in one place. diff --git a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java index 42d711f4b2b..9d6aa59c8a7 100644 --- a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java +++ b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java @@ -170,6 +170,7 @@ abstract class ReferencePipeline } @Override + @SuppressWarnings("unchecked") public void accept(P_OUT u) { if (predicate.test(u)) downstream.accept(u); @@ -263,6 +264,7 @@ abstract class ReferencePipeline } @Override + @SuppressWarnings("unchecked") public void accept(P_OUT u) { try (Stream result = mapper.apply(u)) { // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it @@ -360,16 +362,17 @@ abstract class ReferencePipeline } @Override - public final Stream peek(Consumer tee) { - Objects.requireNonNull(tee); + public final Stream peek(Consumer action) { + Objects.requireNonNull(action); return new StatelessOp(this, StreamShape.REFERENCE, 0) { @Override Sink opWrapSink(int flags, Sink sink) { return new Sink.ChainedReference(sink) { @Override + @SuppressWarnings("unchecked") public void accept(P_OUT u) { - tee.accept(u); + action.accept(u); downstream.accept(u); } }; @@ -515,10 +518,10 @@ abstract class ReferencePipeline } @Override - public final R collect(Supplier resultFactory, + public final R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { - return evaluate(ReduceOps.makeRef(resultFactory, accumulator, combiner)); + return evaluate(ReduceOps.makeRef(supplier, accumulator, combiner)); } @Override diff --git a/jdk/src/share/classes/java/util/stream/Stream.java b/jdk/src/share/classes/java/util/stream/Stream.java index 715729f24a7..4a9f05f5103 100644 --- a/jdk/src/share/classes/java/util/stream/Stream.java +++ b/jdk/src/share/classes/java/util/stream/Stream.java @@ -24,13 +24,18 @@ */ package java.util.stream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Spliterator; import java.util.Spliterators; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BinaryOperator; @@ -44,51 +49,90 @@ import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; import java.util.function.UnaryOperator; -// @@@ Specification to-do list @@@ -// - Describe the difference between sequential and parallel streams -// - More general information about reduce, better definitions for associativity, more description of -// how reduce employs parallelism, more examples -// - Role of stream flags in various operations, specifically ordering -// - Whether each op preserves encounter order -// @@@ Specification to-do list @@@ - /** - * A sequence of elements supporting sequential and parallel bulk operations. - * Streams support lazy intermediate operations (transforming a stream to - * another stream) such as {@code filter} and {@code map}, and terminal - * operations (consuming the contents of a stream to produce a result or - * side-effect), such as {@code forEach}, {@code findFirst}, and {@code - * iterator}. Once an operation has been performed on a stream, it - * is considered consumed and no longer usable for other operations. + * A sequence of elements supporting sequential and parallel aggregate + * operations. The following example illustrates an aggregate operation using + * {@link Stream} and {@link IntStream}: * - *

    For sequential stream pipelines, all operations are performed in the - * encounter order of the pipeline - * source, if the pipeline source has a defined encounter order. + *

    {@code
    + *     int sum = widgets.stream()
    + *                      .filter(w -> w.getColor() == RED)
    + *                      .mapToInt(w -> w.getWeight())
    + *                      .sum();
    + * }
    * - *

    For parallel stream pipelines, unless otherwise specified, intermediate - * stream operations preserve the - * encounter order of their source, and terminal operations - * respect the encounter order of their source, if the source - * has an encounter order. Provided that and parameters to stream operations - * satisfy the non-interference - * requirements, and excepting differences arising from the absence of - * a defined encounter order, the result of a stream pipeline should be the - * stable across multiple executions of the same operations on the same source. - * However, the timing and thread in which side-effects occur (for those - * operations which are allowed to produce side-effects, such as - * {@link #forEach(Consumer)}), are explicitly nondeterministic for parallel - * execution of stream pipelines. + * In this example, {@code widgets} is a {@code Collection}. We create + * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()}, + * filter it to produce a stream containing only the red widgets, and then + * transform it into a stream of {@code int} values representing the weight of + * each red widget. Then this stream is summed to produce a total weight. * - *

    Unless otherwise noted, passing a {@code null} argument to any stream - * method may result in a {@link NullPointerException}. + *

    To perform a computation, stream + * operations are composed into a + * stream pipeline. A stream pipeline consists of a source (which + * might be an array, a collection, a generator function, an I/O channel, + * etc), zero or more intermediate operations (which transform a + * stream into another stream, such as {@link Stream#filter(Predicate)}), and a + * terminal operation (which produces a result or side-effect, such + * as {@link Stream#count()} or {@link Stream#forEach(Consumer)}). + * Streams are lazy; computation on the source data is only performed when the + * terminal operation is initiated, and source elements are consumed only + * as needed. * - * @apiNote - * Streams are not data structures; they do not manage the storage for their - * elements, nor do they support access to individual elements. However, - * you can use the {@link #iterator()} or {@link #spliterator()} operations to - * perform a controlled traversal. + *

    Collections and streams, while bearing some superficial similarities, + * have different goals. Collections are primarily concerned with the efficient + * management of, and access to, their elements. By contrast, streams do not + * provide a means to directly access or manipulate their elements, and are + * instead concerned with declaratively describing their source and the + * computational operations which will be performed in aggregate on that source. + * However, if the provided stream operations do not offer the desired + * functionality, the {@link #iterator()} and {@link #spliterator()} operations + * can be used to perform a controlled traversal. * - * @param type of elements + *

    A stream pipeline, like the "widgets" example above, can be viewed as + * a query on the stream source. Unless the source was explicitly + * designed for concurrent modification (such as a {@link ConcurrentHashMap}), + * unpredictable or erroneous behavior may result from modifying the stream + * source while it is being queried. + * + *

    Most stream operations accept parameters that describe user-specified + * behavior, such as the lambda expression {@code w -> w.getWeight()} passed to + * {@code mapToInt} in the example above. Such parameters are always instances + * of a functional interface such + * as {@link java.util.function.Function}, and are often lambda expressions or + * method references. These parameters can never be null, should not modify the + * stream source, and should be + * effectively stateless + * (their result should not depend on any state that might change during + * execution of the stream pipeline.) + * + *

    A stream should be operated on (invoking an intermediate or terminal stream + * operation) only once. This rules out, for example, "forked" streams, where + * the same source feeds two or more pipelines, or multiple traversals of the + * same stream. A stream implementation may throw {@link IllegalStateException} + * if it detects that the stream is being reused. However, since some stream + * operations may return their receiver rather than a new stream object, it may + * not be possible to detect reuse in all cases. + * + *

    Streams have a {@link #close()} method and implement {@link AutoCloseable}, + * but nearly all stream instances do not actually need to be closed after use. + * Generally, only streams whose source is an IO channel (such as those returned + * by {@link Files#lines(Path, Charset)}) will require closing. Most streams + * are backed by collections, arrays, or generating functions, which require no + * special resource management. (If a stream does require closing, it can be + * declared as a resource in a {@code try}-with-resources statement.) + * + *

    Stream pipelines may execute either sequentially or in + * parallel. This + * execution mode is a property of the stream. Streams are created + * with an initial choice of sequential or parallel execution. (For example, + * {@link Collection#stream() Collection.stream()} creates a sequential stream, + * and {@link Collection#parallelStream() Collection.parallelStream()} creates + * a parallel one.) This choice of execution mode may be modified by the + * {@link #sequential()} or {@link #parallel()} methods, and may be queried with + * the {@link #isParallel()} method. + * + * @param the type of the stream elements * @since 1.8 * @see java.util.stream */ @@ -168,9 +212,9 @@ public interface Stream extends BaseStream> { /** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of the stream produced by applying the - * provided mapping function to each element. If the result of the mapping - * function is {@code null}, this is treated as if the result is an empty - * stream. + * provided mapping function to each element. (If the result of the mapping + * function is {@code null}, this is treated as if the result was an empty + * stream.) * *

    This is an intermediate * operation. @@ -197,9 +241,9 @@ public interface Stream extends BaseStream> { /** * Returns an {@code IntStream} consisting of the results of replacing each * element of this stream with the contents of the stream produced by - * applying the provided mapping function to each element. If the result of - * the mapping function is {@code null}, this is treated as if the result is - * an empty stream. + * applying the provided mapping function to each element. (If the result + * of the mapping function is {@code null}, this is treated as if the result + * was an empty stream.) * *

    This is an intermediate * operation. @@ -214,9 +258,9 @@ public interface Stream extends BaseStream> { /** * Returns a {@code LongStream} consisting of the results of replacing each * element of this stream with the contents of the stream produced - * by applying the provided mapping function to each element. If the result - * of the mapping function is {@code null}, this is treated as if the - * result is an empty stream. + * by applying the provided mapping function to each element. (If the result + * of the mapping function is {@code null}, this is treated as if the result + * was an empty stream.) * *

    This is an intermediate * operation. @@ -231,9 +275,9 @@ public interface Stream extends BaseStream> { /** * Returns a {@code DoubleStream} consisting of the results of replacing each * element of this stream with the contents of the stream produced - * by applying the provided mapping function to each element. If the result + * by applying the provided mapping function to each element. (If the result * of the mapping function is {@code null}, this is treated as if the result - * is an empty stream. + * was an empty stream.) * *

    This is an intermediate * operation. @@ -260,7 +304,7 @@ public interface Stream extends BaseStream> { * Returns a stream consisting of the elements of this stream, sorted * according to natural order. If the elements of this stream are not * {@code Comparable}, a {@code java.lang.ClassCastException} may be thrown - * when the stream pipeline is executed. + * when the terminal operation is executed. * *

    This is a stateful * intermediate operation. @@ -301,18 +345,18 @@ public interface Stream extends BaseStream> { *

    {@code
          *     list.stream()
          *         .filter(filteringFunction)
    -     *         .peek(e -> {System.out.println("Filtered value: " + e); });
    +     *         .peek(e -> System.out.println("Filtered value: " + e));
          *         .map(mappingFunction)
    -     *         .peek(e -> {System.out.println("Mapped value: " + e); });
    +     *         .peek(e -> System.out.println("Mapped value: " + e));
          *         .collect(Collectors.intoList());
          * }
    * - * @param consumer a + * @param action a * non-interfering action to perform on the elements as * they are consumed from the stream * @return the new stream */ - Stream peek(Consumer consumer); + Stream peek(Consumer action); /** * Returns a stream consisting of the elements of this stream, truncated @@ -329,8 +373,8 @@ public interface Stream extends BaseStream> { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream. If the - * {@code startInclusive} index lies past the end of this stream then an + * after discarding the first {@code startInclusive} elements of the stream. + * If this stream contains fewer than {@code startInclusive} elements then an * empty stream will be returned. * *

    This is a stateful @@ -344,10 +388,10 @@ public interface Stream extends BaseStream> { /** * Returns a stream consisting of the remaining elements of this stream - * after indexing {@code startInclusive} elements into the stream and - * truncated to contain no more than {@code endExclusive - startInclusive} - * elements. If the {@code startInclusive} index lies past the end - * of this stream then an empty stream will be returned. + * after discarding the first {@code startInclusive} elements and truncating + * the result to be no longer than {@code endExclusive - startInclusive} + * elements in length. If this stream contains fewer than + * {@code startInclusive} elements then an empty stream will be returned. * *

    This is a short-circuiting * stateful intermediate operation. @@ -405,11 +449,23 @@ public interface Stream extends BaseStream> { /** * Returns an array containing the elements of this stream, using the - * provided {@code generator} function to allocate the returned array. + * provided {@code generator} function to allocate the returned array, as + * well as any additional arrays that might be required for a partitioned + * execution or for resizing. * *

    This is a terminal * operation. * + * @apiNote + * The generator function takes an integer, which is the size of the + * desired array, and produces an array of the desired size. This can be + * concisely expressed with an array constructor reference: + *

    {@code
    +     *     Person[] men = people.stream()
    +     *                          .filter(p -> p.getGender() == MALE)
    +     *                          .toArray(Person[]::new);
    +     * }
    + * * @param the element type of the resulting array * @param generator a function which produces a new array of the desired * type and the provided length @@ -451,7 +507,7 @@ public interface Stream extends BaseStream> { * Integer sum = integers.reduce(0, (a, b) -> a+b); * }
    * - * or more compactly: + * or: * *
    {@code
          *     Integer sum = integers.reduce(0, Integer::sum);
    @@ -501,7 +557,8 @@ public interface Stream extends BaseStream> {
          * @param accumulator an associative
          *                    non-interfering,
          *                    stateless function for combining two values
    -     * @return the result of the reduction
    +     * @return an {@link Optional} describing the result of the reduction
    +     * @throws NullPointerException if the result of the reduction is null
          * @see #reduce(Object, BinaryOperator)
          * @see #min(java.util.Comparator)
          * @see #max(java.util.Comparator)
    @@ -511,7 +568,7 @@ public interface Stream extends BaseStream> {
         /**
          * Performs a reduction on the
          * elements of this stream, using the provided identity, accumulation
    -     * function, and a combining functions.  This is equivalent to:
    +     * function, and combining functions.  This is equivalent to:
          * 
    {@code
          *     U result = identity;
          *     for (T element : this stream)
    @@ -537,8 +594,8 @@ public interface Stream extends BaseStream> {
          * by an explicit combination of {@code map} and {@code reduce} operations.
          * The {@code accumulator} function acts as a fused mapper and accumulator,
          * which can sometimes be more efficient than separate mapping and reduction,
    -     * such as in the case where knowing the previously reduced value allows you
    -     * to avoid some computation.
    +     * such as when knowing the previously reduced value allows you to avoid
    +     * some computation.
          *
          * @param  The type of the result
          * @param identity the identity value for the combiner function
    @@ -561,12 +618,12 @@ public interface Stream extends BaseStream> {
         /**
          * Performs a mutable
          * reduction operation on the elements of this stream.  A mutable
    -     * reduction is one in which the reduced value is a mutable value holder,
    +     * reduction is one in which the reduced value is a mutable result container,
          * such as an {@code ArrayList}, and elements are incorporated by updating
    -     * the state of the result, rather than by replacing the result.  This
    +     * the state of the result rather than by replacing the result.  This
          * produces a result equivalent to:
          * 
    {@code
    -     *     R result = resultFactory.get();
    +     *     R result = supplier.get();
          *     for (T element : this stream)
          *         accumulator.accept(result, element);
          *     return result;
    @@ -579,10 +636,11 @@ public interface Stream extends BaseStream> {
          * operation.
          *
          * @apiNote There are many existing classes in the JDK whose signatures are
    -     * a good match for use as arguments to {@code collect()}.  For example,
    -     * the following will accumulate strings into an ArrayList:
    +     * well-suited for use with method references as arguments to {@code collect()}.
    +     * For example, the following will accumulate strings into an {@code ArrayList}:
          * 
    {@code
    -     *     List asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    +     *     List asList = stringStream.collect(ArrayList::new, ArrayList::add,
    +     *                                                ArrayList::addAll);
          * }
    * *

    The following will take a stream of strings and concatenates them into a @@ -594,10 +652,9 @@ public interface Stream extends BaseStream> { * }

    * * @param type of the result - * @param resultFactory a function that creates a new result container. - * For a parallel execution, this function may be - * called multiple times and must return a fresh value - * each time. + * @param supplier a function that creates a new result container. For a + * parallel execution, this function may be called + * multiple times and must return a fresh value each time. * @param accumulator an associative * non-interfering, * stateless function for incorporating an additional @@ -608,24 +665,24 @@ public interface Stream extends BaseStream> { * must be compatible with the accumulator function * @return the result of the reduction */ - R collect(Supplier resultFactory, + R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner); /** * Performs a mutable * reduction operation on the elements of this stream using a - * {@code Collector} object to describe the reduction. A {@code Collector} + * {@code Collector}. A {@code Collector} * encapsulates the functions used as arguments to * {@link #collect(Supplier, BiConsumer, BiConsumer)}, allowing for reuse of - * collection strategies, and composition of collect operations such as + * collection strategies and composition of collect operations such as * multiple-level grouping or partitioning. * *

    This is a terminal * operation. * *

    When executed in parallel, multiple intermediate results may be - * instantiated, populated, and merged, so as to maintain isolation of + * instantiated, populated, and merged so as to maintain isolation of * mutable data structures. Therefore, even when executed in parallel * with non-thread-safe data structures (such as {@code ArrayList}), no * additional synchronization is needed for a parallel reduction. @@ -638,16 +695,16 @@ public interface Stream extends BaseStream> { * *

    The following will classify {@code Person} objects by city: *

    {@code
    -     *     Map> peopleByCity
    -     *         = personStream.collect(Collectors.groupBy(Person::getCity));
    +     *     Map> peopleByCity
    +     *         = personStream.collect(Collectors.groupingBy(Person::getCity));
          * }
    * *

    The following will classify {@code Person} objects by state and city, * cascading two {@code Collector}s together: *

    {@code
    -     *     Map>> peopleByStateAndCity
    -     *         = personStream.collect(Collectors.groupBy(Person::getState,
    -     *                                                   Collectors.groupBy(Person::getCity)));
    +     *     Map>> peopleByStateAndCity
    +     *         = personStream.collect(Collectors.groupingBy(Person::getState,
    +     *                                                      Collectors.groupingBy(Person::getCity)));
          * }
    * * @param the type of the result @@ -662,7 +719,7 @@ public interface Stream extends BaseStream> { /** * Returns the minimum element of this stream according to the provided * {@code Comparator}. This is a special case of a - * reduction. + * reduction. * *

    This is a terminal operation. * @@ -671,13 +728,14 @@ public interface Stream extends BaseStream> { * elements of this stream * @return an {@code Optional} describing the minimum element of this stream, * or an empty {@code Optional} if the stream is empty + * @throws NullPointerException if the minimum element is null */ Optional min(Comparator comparator); /** * Returns the maximum element of this stream according to the provided * {@code Comparator}. This is a special case of a - * reduction. + * reduction. * *

    This is a terminal * operation. @@ -687,12 +745,13 @@ public interface Stream extends BaseStream> { * elements of this stream * @return an {@code Optional} describing the maximum element of this stream, * or an empty {@code Optional} if the stream is empty + * @throws NullPointerException if the maximum element is null */ Optional max(Comparator comparator); /** * Returns the count of elements in this stream. This is a special case of - * a reduction and is + * a reduction and is * equivalent to: *

    {@code
          *     return mapToLong(e -> 1L).sum();
    @@ -753,10 +812,9 @@ public interface Stream extends BaseStream> {
         boolean noneMatch(Predicate predicate);
     
         /**
    -     * Returns an {@link Optional} describing the first element of this stream
    -     * (in the encounter order), or an empty {@code Optional} if the stream is
    -     * empty.  If the stream has no encounter order, then any element may be
    -     * returned.
    +     * Returns an {@link Optional} describing the first element of this stream,
    +     * or an empty {@code Optional} if the stream is empty.  If the stream has
    +     * no encounter order, then any element may be returned.
          *
          * 

    This is a short-circuiting * terminal operation. @@ -777,8 +835,8 @@ public interface Stream extends BaseStream> { *

    The behavior of this operation is explicitly nondeterministic; it is * free to select any element in the stream. This is to allow for maximal * performance in parallel operations; the cost is that multiple invocations - * on the same source may not return the same result. (If the first element - * in the encounter order is desired, use {@link #findFirst()} instead.) + * on the same source may not return the same result. (If a stable result + * is desired, use {@link #findFirst()} instead.) * * @return an {@code Optional} describing some element of this stream, or an * empty {@code Optional} if the stream is empty @@ -821,20 +879,20 @@ public interface Stream extends BaseStream> { } /** - * Returns a sequential stream whose elements are the specified values. + * Returns a sequential ordered stream whose elements are the specified values. * * @param the type of stream elements * @param values the elements of the new stream * @return the new stream */ @SafeVarargs - @SuppressWarnings("varargs") // Creating a stream from an array is safe + @SuppressWarnings("varargs") public static Stream of(T... values) { return Arrays.stream(values); } /** - * Returns an infinite sequential {@code Stream} produced by iterative + * Returns an infinite sequential ordered {@code Stream} produced by iterative * application of a function {@code f} to an initial element {@code seed}, * producing a {@code Stream} consisting of {@code seed}, {@code f(seed)}, * {@code f(f(seed))}, etc. @@ -872,8 +930,8 @@ public interface Stream extends BaseStream> { } /** - * Returns a sequential {@code Stream} where each element is - * generated by a {@code Supplier}. This is suitable for generating + * Returns a sequential stream where each element is generated by + * the provided {@code Supplier}. This is suitable for generating * constant streams, streams of random elements, etc. * * @param the type of stream elements @@ -887,17 +945,16 @@ public interface Stream extends BaseStream> { } /** - * Creates a lazy concatenated {@code Stream} whose elements are all the - * elements of a first {@code Stream} succeeded by all the elements of the - * second {@code Stream}. The resulting stream is ordered if both + * Creates a lazily concatenated stream whose elements are all the + * elements of the first stream followed by all the elements of the + * second stream. The resulting stream is ordered if both * of the input streams are ordered, and parallel if either of the input * streams is parallel. When the resulting stream is closed, the close * handlers for both input streams are invoked. * * @param The type of stream elements * @param a the first stream - * @param b the second stream to concatenate on to end of the first - * stream + * @param b the second stream * @return the concatenation of the two input streams */ public static Stream concat(Stream a, Stream b) { @@ -917,7 +974,7 @@ public interface Stream extends BaseStream> { * {@code Builder} (without the copying overhead that comes from using * an {@code ArrayList} as a temporary buffer.) * - *

    A {@code Stream.Builder} has a lifecycle, where it starts in a building + *

    A stream builder has a lifecycle, which starts in a building * phase, during which elements can be added, and then transitions to a built * phase, after which elements may not be added. The built phase begins * when the {@link #build()} method is called, which creates an ordered diff --git a/jdk/src/share/classes/java/util/stream/StreamSpliterators.java b/jdk/src/share/classes/java/util/stream/StreamSpliterators.java index 4a62803b56b..f7b78422ebe 100644 --- a/jdk/src/share/classes/java/util/stream/StreamSpliterators.java +++ b/jdk/src/share/classes/java/util/stream/StreamSpliterators.java @@ -1456,4 +1456,5 @@ class StreamSpliterators { } } } -} \ No newline at end of file +} + diff --git a/jdk/src/share/classes/java/util/stream/StreamSupport.java b/jdk/src/share/classes/java/util/stream/StreamSupport.java index f6e04b0a51a..6feadd1db21 100644 --- a/jdk/src/share/classes/java/util/stream/StreamSupport.java +++ b/jdk/src/share/classes/java/util/stream/StreamSupport.java @@ -32,12 +32,8 @@ import java.util.function.Supplier; * Low-level utility methods for creating and manipulating streams. * *

    This class is mostly for library writers presenting stream views - * of their data structures; most static stream methods for end users are in - * {@link Streams}. - * - *

    Unless otherwise stated, streams are created as sequential - * streams. A sequential stream can be transformed into a parallel stream by - * calling the {@code parallel()} method on the created stream. + * of data structures; most static stream methods intended for end users are in + * the various {@code Stream} classes. * * @since 1.8 */ @@ -80,7 +76,7 @@ public final class StreamSupport { * {@code Supplier} of {@code Spliterator}. * *

    The {@link Supplier#get()} method will be invoked on the supplier no - * more than once, and after the terminal operation of the stream pipeline + * more than once, and only after the terminal operation of the stream pipeline * commences. * *

    For spliterators that report a characteristic of {@code IMMUTABLE} @@ -88,7 +84,7 @@ public final class StreamSupport { * late-binding, it is likely * more efficient to use {@link #stream(java.util.Spliterator, boolean)} * instead. - * The use of a {@code Supplier} in this form provides a level of + *

    The use of a {@code Supplier} in this form provides a level of * indirection that reduces the scope of potential interference with the * source. Since the supplier is only invoked after the terminal operation * commences, any modifications to the source up to the start of the @@ -148,7 +144,7 @@ public final class StreamSupport { * {@code Supplier} of {@code Spliterator.OfInt}. * *

    The {@link Supplier#get()} method will be invoked on the supplier no - * more than once, and after the terminal operation of the stream pipeline + * more than once, and only after the terminal operation of the stream pipeline * commences. * *

    For spliterators that report a characteristic of {@code IMMUTABLE} @@ -156,7 +152,7 @@ public final class StreamSupport { * late-binding, it is likely * more efficient to use {@link #intStream(java.util.Spliterator.OfInt, boolean)} * instead. - * The use of a {@code Supplier} in this form provides a level of + *

    The use of a {@code Supplier} in this form provides a level of * indirection that reduces the scope of potential interference with the * source. Since the supplier is only invoked after the terminal operation * commences, any modifications to the source up to the start of the @@ -215,7 +211,7 @@ public final class StreamSupport { * {@code Supplier} of {@code Spliterator.OfLong}. * *

    The {@link Supplier#get()} method will be invoked on the supplier no - * more than once, and after the terminal operation of the stream pipeline + * more than once, and only after the terminal operation of the stream pipeline * commences. * *

    For spliterators that report a characteristic of {@code IMMUTABLE} @@ -223,7 +219,7 @@ public final class StreamSupport { * late-binding, it is likely * more efficient to use {@link #longStream(java.util.Spliterator.OfLong, boolean)} * instead. - * The use of a {@code Supplier} in this form provides a level of + *

    The use of a {@code Supplier} in this form provides a level of * indirection that reduces the scope of potential interference with the * source. Since the supplier is only invoked after the terminal operation * commences, any modifications to the source up to the start of the @@ -282,7 +278,7 @@ public final class StreamSupport { * {@code Supplier} of {@code Spliterator.OfDouble}. * *

    The {@link Supplier#get()} method will be invoked on the supplier no - * more than once, and after the terminal operation of the stream pipeline + * more than once, and only after the terminal operation of the stream pipeline * commences. * *

    For spliterators that report a characteristic of {@code IMMUTABLE} @@ -290,7 +286,7 @@ public final class StreamSupport { * late-binding, it is likely * more efficient to use {@link #doubleStream(java.util.Spliterator.OfDouble, boolean)} * instead. - * The use of a {@code Supplier} in this form provides a level of + *

    The use of a {@code Supplier} in this form provides a level of * indirection that reduces the scope of potential interference with the * source. Since the supplier is only invoked after the terminal operation * commences, any modifications to the source up to the start of the diff --git a/jdk/src/share/classes/java/util/stream/package-info.java b/jdk/src/share/classes/java/util/stream/package-info.java index 46d033e38cd..2c01847ace3 100644 --- a/jdk/src/share/classes/java/util/stream/package-info.java +++ b/jdk/src/share/classes/java/util/stream/package-info.java @@ -24,347 +24,484 @@ */ /** - *

    java.util.stream

    - * - * Classes to support functional-style operations on streams of values, as in the following: + * Classes to support functional-style operations on streams of elements, such + * as map-reduce transformations on collections. For example: * *
    {@code
    - *     int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED)
    - *                                       .mapToInt(b -> b.getWeight())
    - *                                       .sum();
    + *     int sum = widgets.stream()
    + *                      .filter(b -> b.getColor() == RED)
    + *                      .mapToInt(b -> b.getWeight())
    + *                      .sum();
      * }
    * - *

    Here we use {@code blocks}, which might be a {@code Collection}, as a source for a stream, - * and then perform a filter-map-reduce ({@code sum()} is an example of a reduction - * operation) on the stream to obtain the sum of the weights of the red blocks. + *

    Here we use {@code widgets}, a {@code Collection}, + * as a source for a stream, and then perform a filter-map-reduce on the stream + * to obtain the sum of the weights of the red widgets. (Summation is an + * example of a reduction + * operation.) * - *

    The key abstraction used in this approach is {@link java.util.stream.Stream}, as well as its primitive - * specializations {@link java.util.stream.IntStream}, {@link java.util.stream.LongStream}, - * and {@link java.util.stream.DoubleStream}. Streams differ from Collections in several ways: + *

    The key abstraction introduced in this package is stream. The + * classes {@link java.util.stream.Stream}, {@link java.util.stream.IntStream}, + * {@link java.util.stream.LongStream}, and {@link java.util.stream.DoubleStream} + * are streams over objects and the primitive {@code int}, {@code long} and + * {@code double} types. Streams differ from collections in several ways: * *

      - *
    • No storage. A stream is not a data structure that stores elements; instead, they - * carry values from a source (which could be a data structure, a generator, an IO channel, etc) - * through a pipeline of computational operations.
    • - *
    • Functional in nature. An operation on a stream produces a result, but does not modify - * its underlying data source. For example, filtering a {@code Stream} produces a new {@code Stream}, - * rather than removing elements from the underlying source.
    • - *
    • Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, - * can be implemented lazily, exposing opportunities for optimization. (For example, "find the first - * {@code String} matching a pattern" need not examine all the input strings.) Stream operations - * are divided into intermediate ({@code Stream}-producing) operations and terminal (value-producing) - * operations; all intermediate operations are lazy.
    • - *
    • Possibly unbounded. While collections have a finite size, streams need not. Operations - * such as {@code limit(n)} or {@code findFirst()} can allow computations on infinite streams - * to complete in finite time.
    • + *
    • No storage. A stream is not a data structure that stores elements; + * instead, it conveys elements from a source such as a data structure, + * an array, a generator function, or an I/O channel, through a pipeline of + * computational operations.
    • + *
    • Functional in nature. An operation on a stream produces a result, + * but does not modify its source. For example, filtering a {@code Stream} + * obtained from a collection produces a new {@code Stream} without the + * filtered elements, rather than removing elements from the source + * collection.
    • + *
    • Laziness-seeking. Many stream operations, such as filtering, mapping, + * or duplicate removal, can be implemented lazily, exposing opportunities + * for optimization. For example, "find the first {@code String} with + * three consecutive vowels" need not examine all the input strings. + * Stream operations are divided into intermediate ({@code Stream}-producing) + * operations and terminal (value- or side-effect-producing) operations. + * Intermediate operations are always lazy.
    • + *
    • Possibly unbounded. While collections have a finite size, streams + * need not. Short-circuting operations such as {@code limit(n)} or + * {@code findFirst()} can allow computations on infinite streams to + * complete in finite time.
    • + *
    • Consumable. The elements of a stream are only visited once during + * the life of a stream. Like an {@link java.util.Iterator}, a new stream + * must be generated to revisit the same elements of the source. + *
    • *
    * - *

    Stream pipelines

    + * Streams can be obtained in a number of ways. Some examples include: + *
      + *
    • From a {@link java.util.Collection} via the {@code stream()} and + * {@code parallelStream()} methods;
    • + *
    • From an array via {@link java.util.Arrays#stream(Object[])};
    • + *
    • From static factory methods on the stream classes, such as + * {@link java.util.stream.Stream#of(Object[])}, + * {@link java.util.stream.IntStream#range(int, int)} + * or {@link java.util.stream.Stream#iterate(Object, UnaryOperator)};
    • + *
    • The lines of a file can be obtained from {@link java.io.BufferedReader#lines()};
    • + *
    • Streams of file paths can be obtained from methods in {@link java.nio.file.Files};
    • + *
    • Streams of random numbers can be obtained from {@link java.util.Random#ints()};
    • + *
    • Numerous other stream-bearing methods in the JDK, including + * {@link java.util.BitSet#stream()}, + * {@link java.util.regex.Pattern#splitAsStream(java.lang.CharSequence)}, + * and {@link java.util.jar.JarFile#stream()}.
    • + *
    * - *

    Streams are used to create pipelines of operations. A - * complete stream pipeline has several components: a source (which may be a {@code Collection}, - * an array, a generator function, or an IO channel); zero or more intermediate operations - * such as {@code Stream.filter} or {@code Stream.map}; and a terminal operation such - * as {@code Stream.forEach} or {@code java.util.stream.Stream.reduce}. Stream operations may take as parameters - * function values (which are often lambda expressions, but could be method references - * or objects) which parameterize the behavior of the operation, such as a {@code Predicate} - * passed to the {@code Stream#filter} method. + *

    Additional stream sources can be provided by third-party libraries using + * these techniques. * - *

    Intermediate operations return a new {@code Stream}. They are lazy; executing an - * intermediate operation such as {@link java.util.stream.Stream#filter Stream.filter} does - * not actually perform any filtering, instead creating a new {@code Stream} that, when - * traversed, contains the elements of the initial {@code Stream} that match the - * given {@code Predicate}. Consuming elements from the stream source does not - * begin until the terminal operation is executed. + *

    Stream operations and pipelines

    * - *

    Terminal operations consume the {@code Stream} and produce a result or a side-effect. - * After a terminal operation is performed, the stream can no longer be used and you must - * return to the data source, or select a new data source, to get a new stream. For example, - * obtaining the sum of weights of all red blocks, and then of all blue blocks, requires a - * filter-map-reduce on two different streams: - *

    {@code
    - *     int sumOfRedWeights  = blocks.stream().filter(b -> b.getColor() == RED)
    - *                                           .mapToInt(b -> b.getWeight())
    - *                                           .sum();
    - *     int sumOfBlueWeights = blocks.stream().filter(b -> b.getColor() == BLUE)
    - *                                           .mapToInt(b -> b.getWeight())
    - *                                           .sum();
    - * }
    + *

    Stream operations are + * divided into intermediate and terminal operations, and are + * combined to form stream pipelines. A stream pipeline consists of a + * source (such as a {@code Collection}, an array, a generator function, or an + * I/O channel); followed by zero or more intermediate operations such + * as {@code Stream.filter} or {@code Stream.map}; and a terminal + * operation such as {@code Stream.forEach} or {@code Stream.reduce}. * - *

    However, there are other techniques that allow you to obtain both results in a single - * pass if multiple traversal is impractical or inefficient. TODO provide link + *

    Intermediate operations return a new stream. They are always + * lazy; executing an intermediate operation such as + * {@code filter()} does not actually perform any filtering, but instead + * creates a new stream that, when traversed, contains the elements of + * the initial stream that match the given predicate. Traversal + * of the pipeline source does not begin until the terminal operation of the + * pipeline is executed. * - *

    Stream operations

    + *

    Terminal operations, such as {@code Stream.forEach} or + * {@code IntStream.sum}, may traverse the stream to produce a result or a + * side-effect. After the terminal operation is performed, the stream pipeline + * is considered consumed, and can no longer be used; if you need to traverse + * the same data source again, you must return to the data source to get a new + * stream. In almost all cases, terminal operations are eager, + * completing their traversal of the data source and processing of the pipeline + * before returning. Only the terminal operations {@code iterator()} and + * {@code spliterator()} are not; these are provided as an "escape hatch" to enable + * arbitrary client-controlled pipeline traversals in the event that the + * existing operations are not sufficient to the task. * - *

    Intermediate stream operation (such as {@code filter} or {@code sorted}) always produce a - * new {@code Stream}, and are alwayslazy. Executing a lazy operations does not - * trigger processing of the stream contents; all processing is deferred until the terminal - * operation commences. Processing streams lazily allows for significant efficiencies; in a - * pipeline such as the filter-map-sum example above, filtering, mapping, and addition can be - * fused into a single pass, with minimal intermediate state. Laziness also enables us to avoid - * examining all the data when it is not necessary; for operations such as "find the first - * string longer than 1000 characters", one need not examine all the input strings, just enough - * to find one that has the desired characteristics. (This behavior becomes even more important - * when the input stream is infinite and not merely large.) + *

    Processing streams lazily allows for significant efficiencies; in a + * pipeline such as the filter-map-sum example above, filtering, mapping, and + * summing can be fused into a single pass on the data, with minimal + * intermediate state. Laziness also allows avoiding examining all the data + * when it is not necessary; for operations such as "find the first string + * longer than 1000 characters", it is only necessary to examine just enough + * strings to find one that has the desired characteristics without examining + * all of the strings available from the source. (This behavior becomes even + * more important when the input stream is infinite and not merely large.) * - *

    Intermediate operations are further divided into stateless and stateful - * operations. Stateless operations retain no state from previously seen values when processing - * a new value; examples of stateless intermediate operations include {@code filter} and - * {@code map}. Stateful operations may incorporate state from previously seen elements in - * processing new values; examples of stateful intermediate operations include {@code distinct} - * and {@code sorted}. Stateful operations may need to process the entire input before - * producing a result; for example, one cannot produce any results from sorting a stream until - * one has seen all elements of the stream. As a result, under parallel computation, some - * pipelines containing stateful intermediate operations have to be executed in multiple passes. - * Pipelines containing exclusively stateless intermediate operations can be processed in a - * single pass, whether sequential or parallel. + *

    Intermediate operations are further divided into stateless + * and stateful operations. Stateless operations, such as {@code filter} + * and {@code map}, retain no state from previously seen element when processing + * a new element -- each element can be processed + * independently of operations on other elements. Stateful operations, such as + * {@code distinct} and {@code sorted}, may incorporate state from previously + * seen elements when processing new elements. * - *

    Further, some operations are deemed short-circuiting operations. An intermediate - * operation is short-circuiting if, when presented with infinite input, it may produce a - * finite stream as a result. A terminal operation is short-circuiting if, when presented with - * infinite input, it may terminate in finite time. (Having a short-circuiting operation is a - * necessary, but not sufficient, condition for the processing of an infinite stream to - * terminate normally in finite time.) + *

    Stateful operations may need to process the entire input + * before producing a result. For example, one cannot produce any results from + * sorting a stream until one has seen all elements of the stream. As a result, + * under parallel computation, some pipelines containing stateful intermediate + * operations may require multiple passes on the data or may need to buffer + * significant data. Pipelines containing exclusively stateless intermediate + * operations can be processed in a single pass, whether sequential or parallel, + * with minimal data buffering. * - * Terminal operations (such as {@code forEach} or {@code findFirst}) are always eager - * (they execute completely before returning), and produce a non-{@code Stream} result, such - * as a primitive value or a {@code Collection}, or have side-effects. + *

    Further, some operations are deemed short-circuiting operations. + * An intermediate operation is short-circuiting if, when presented with + * infinite input, it may produce a finite stream as a result. A terminal + * operation is short-circuiting if, when presented with infinite input, it may + * terminate in finite time. Having a short-circuiting operation in the pipeline + * is a necessary, but not sufficient, condition for the processing of an infinite + * stream to terminate normally in finite time. * *

    Parallelism

    * - *

    By recasting aggregate operations as a pipeline of operations on a stream of values, many - * aggregate operations can be more easily parallelized. A {@code Stream} can execute either - * in serial or in parallel. When streams are created, they are either created as sequential - * or parallel streams; the parallel-ness of streams can also be switched by the - * {@link java.util.stream Stream#sequential()} and {@link java.util.stream.Stream#parallel()} - * operations. The {@code Stream} implementations in the JDK create serial streams unless - * parallelism is explicitly requested. For example, {@code Collection} has methods + *

    Processing elements with an explicit {@code for-}loop is inherently serial. + * Streams facilitate parallel execution by reframing the computation as a pipeline of + * aggregate operations, rather than as imperative operations on each individual + * element. All streams operations can execute either in serial or in parallel. + * The stream implementations in the JDK create serial streams unless parallelism is + * explicitly requested. For example, {@code Collection} has methods * {@link java.util.Collection#stream} and {@link java.util.Collection#parallelStream}, - * which produce sequential and parallel streams respectively; other stream-bearing methods - * such as {@link java.util.stream.IntStream#range(int, int)} produce sequential - * streams but these can be efficiently parallelized by calling {@code parallel()} on the - * result. The set of operations on serial and parallel streams is identical. To execute the - * "sum of weights of blocks" query in parallel, we would do: + * which produce sequential and parallel streams respectively; other + * stream-bearing methods such as {@link java.util.stream.IntStream#range(int, int)} + * produce sequential streams but these streams can be efficiently parallelized by + * invoking their {@link java.util.stream.BaseStream#parallel()} method. + * To execute the prior "sum of weights of widgets" query in parallel, we would + * do: * *

    {@code
    - *     int sumOfWeights = blocks.parallelStream().filter(b -> b.getColor() == RED)
    - *                                               .mapToInt(b -> b.getWeight())
    - *                                               .sum();
    + *     int sumOfWeights = widgets.}{@code parallelStream()}{@code .filter(b -> b.getColor() == RED)
    + *                                                .mapToInt(b -> b.getWeight())
    + *                                                .sum();
      * }
    * - *

    The only difference between the serial and parallel versions of this example code is - * the creation of the initial {@code Stream}. Whether a {@code Stream} will execute in serial - * or parallel can be determined by the {@code Stream#isParallel} method. When the terminal - * operation is initiated, the entire stream pipeline is either executed sequentially or in - * parallel, determined by the last operation that affected the stream's serial-parallel - * orientation (which could be the stream source, or the {@code sequential()} or - * {@code parallel()} methods.) + *

    The only difference between the serial and parallel versions of this + * example is the creation of the initial stream, using "{@code parallelStream()}" + * instead of "{@code stream()}". When the terminal operation is initiated, + * the stream pipeline is executed sequentially or in parallel depending on the + * orientation of the stream on which it is invoked. Whether a stream will execute in serial or + * parallel can be determined with the {@code isParallel()} method, and the + * orientation of a stream can be modified with the + * {@link java.util.stream.BaseStream#sequential()} and + * {@link java.util.stream.BaseStream#parallel()} operations. When the terminal + * operation is initiated, the stream pipeline is executed sequentially or in + * parallel depending on the mode of the stream on which it is invoked. * - *

    In order for the results of parallel operations to be deterministic and consistent with - * their serial equivalent, the function values passed into the various stream operations should - * be stateless. + *

    Except for operations identified as explicitly nondeterministic, such + * as {@code findAny()}, whether a stream executes sequentially or in parallel + * should not change the result of the computation. * - *

    Ordering

    - * - *

    Streams may or may not have an encounter order. An encounter - * order specifies the order in which elements are provided by the stream to the - * operations pipeline. Whether or not there is an encounter order depends on - * the source, the intermediate operations, and the terminal operation. - * Certain stream sources (such as {@code List} or arrays) are intrinsically - * ordered, whereas others (such as {@code HashSet}) are not. Some intermediate - * operations may impose an encounter order on an otherwise unordered stream, - * such as {@link java.util.stream.Stream#sorted()}, and others may render an - * ordered stream unordered (such as {@link java.util.stream.Stream#unordered()}). - * Some terminal operations may ignore encounter order, such as - * {@link java.util.stream.Stream#forEach}. - * - *

    If a Stream is ordered, most operations are constrained to operate on the - * elements in their encounter order; if the source of a stream is a {@code List} - * containing {@code [1, 2, 3]}, then the result of executing {@code map(x -> x*2)} - * must be {@code [2, 4, 6]}. However, if the source has no defined encounter - * order, than any of the six permutations of the values {@code [2, 4, 6]} would - * be a valid result. Many operations can still be efficiently parallelized even - * under ordering constraints. - * - *

    For sequential streams, ordering is only relevant to the determinism - * of operations performed repeatedly on the same source. (An {@code ArrayList} - * is constrained to iterate elements in order; a {@code HashSet} is not, and - * repeated iteration might produce a different order.) - * - *

    For parallel streams, relaxing the ordering constraint can enable - * optimized implementation for some operations. For example, duplicate - * filtration on an ordered stream must completely process the first partition - * before it can return any elements from a subsequent partition, even if those - * elements are available earlier. On the other hand, without the constraint of - * ordering, duplicate filtration can be done more efficiently by using - * a shared {@code ConcurrentHashSet}. There will be cases where the stream - * is structurally ordered (the source is ordered and the intermediate - * operations are order-preserving), but the user does not particularly care - * about the encounter order. In some cases, explicitly de-ordering the stream - * with the {@link java.util.stream.Stream#unordered()} method may result in - * improved parallel performance for some stateful or terminal operations. + *

    Most stream operations accept parameters that describe user-specified + * behavior, which are often lambda expressions. To preserve correct behavior, + * these behavioral parameters must be non-interfering, and in + * most cases must be stateless. Such parameters are always instances + * of a functional interface such + * as {@link java.util.function.Function}, and are often lambda expressions or + * method references. * *

    Non-interference

    * - * The {@code java.util.stream} package enables you to execute possibly-parallel - * bulk-data operations over a variety of data sources, including even non-thread-safe - * collections such as {@code ArrayList}. This is possible only if we can - * prevent interference with the data source during the execution of a - * stream pipeline. (Execution begins when the terminal operation is invoked, and ends - * when the terminal operation completes.) For most data sources, preventing interference - * means ensuring that the data source is not modified at all during the execution - * of the stream pipeline. (Some data sources, such as concurrent collections, are - * specifically designed to handle concurrent modification.) + * Streams enable you to execute possibly-parallel aggregate operations over a + * variety of data sources, including even non-thread-safe collections such as + * {@code ArrayList}. This is possible only if we can prevent + * interference with the data source during the execution of a stream + * pipeline. Except for the escape-hatch operations {@code iterator()} and + * {@code spliterator()}, execution begins when the terminal operation is + * invoked, and ends when the terminal operation completes. For most data + * sources, preventing interference means ensuring that the data source is + * not modified at all during the execution of the stream pipeline. + * The notable exception to this are streams whose sources are concurrent + * collections, which are specifically designed to handle concurrent modification. * - *

    Accordingly, lambda expressions (or other objects implementing the appropriate functional - * interface) passed to stream methods should never modify the stream's data source. An - * implementation is said to interfere with the data source if it modifies, or causes - * to be modified, the stream's data source. The need for non-interference applies to all - * pipelines, not just parallel ones. Unless the stream source is concurrent, modifying a - * stream's data source during execution of a stream pipeline can cause exceptions, incorrect - * answers, or nonconformant results. + *

    Accordingly, behavioral parameters passed to stream methods should never + * modify the stream's data source. An implementation is said to + * interfere with the data source if it modifies, or causes to be + * modified, the stream's data source. The need for non-interference applies + * to all pipelines, not just parallel ones. Unless the stream source is + * concurrent, modifying a stream's data source during execution of a stream + * pipeline can cause exceptions, incorrect answers, or nonconformant behavior. + * + *

    Results may be nondeterministic or incorrect if the behavioral + * parameters of stream operations are stateful. A stateful lambda + * (or other object implementing the appropriate functional interface) is one + * whose result depends on any state which might change during the execution + * of the stream pipeline. An example of a stateful lambda is: * - *

    Further, results may be nondeterministic or incorrect if the lambda expressions passed to - * stream operations are stateful. A stateful lambda (or other object implementing the - * appropriate functional interface) is one whose result depends on any state which might change - * during the execution of the stream pipeline. An example of a stateful lambda is: *

    {@code
      *     Set seen = Collections.synchronizedSet(new HashSet<>());
      *     stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...
      * }
    - * Here, if the mapping operation is performed in parallel, the results for the same input - * could vary from run to run, due to thread scheduling differences, whereas, with a stateless - * lambda expression the results would always be the same. + * + * Here, if the mapping operation is performed in parallel, the results for the + * same input could vary from run to run, due to thread scheduling differences, + * whereas, with a stateless lambda expression the results would always be the + * same. + * + * For well-behaved stream sources, the source can be modified before the + * terminal operation commences and those modifications will be reflected in + * the covered elements. For example, consider the following code: + * + *
    {@code
    + *     List l = new ArrayList(Arrays.asList("one", "two"));
    + *     Stream sl = l.stream();
    + *     l.add("three");
    + *     String s = sl.collect(joining(" "));
    + * }
    + * + * First a list is created consisting of two strings: "one"; and "two". Then a + * stream is created from that list. Next the list is modified by adding a third + * string: "three". Finally the elements of the stream are collected and joined + * together. Since the list was modified before the terminal {@code collect} + * operation commenced the result will be a string of "one two three". All the + * streams returned from JDK collections, and most other JDK classes, + * are well-behaved in this manner; for streams generated by other libraries, see + * Low-level stream + * construction for requirements for building well-behaved streams. + * + *

    Some streams, particularly those whose stream sources are concurrent, can + * tolerate concurrent modification during execution of a stream pipeline. + * However, in no case -- even if the stream source is concurrent -- should + * behavioral parameters to stream operations modify the stream source. Modifying + * the stream source from within the stream source may cause pipeline execution + * to fail to terminate, produce inaccurate results, or throw exceptions. + * The following example shows inappropriate interference with the source: + *

    {@code
    + *     // Don't do this!
    + *     List l = new ArrayList(Arrays.asList("one", "two"));
    + *     Stream sl = l.stream();
    + *     String s = sl.peek(s -> l.add("BAD LAMBDA")).collect(joining(" "));
    + * }
    * *

    Side-effects

    * + * Side-effects in behavioral parameters to stream operations are, in general, + * discouraged, as they can often lead to unwitting violations of the + * statelessness requirement, as well as other thread-safety hazards. Many + * computations where one might be tempted to use side effects can be more + * safely and efficiently expressed without side-effects, such as using + * reduction instead of mutable + * accumulators. However, side-effects such as using {@code println()} for debugging + * purposes are usually harmless. A small number of stream operations, such as + * {@code forEach()} and {@code peek()}, can operate only via side-effects; + * these should be used with care. + * + *

    As an example of how to transform a stream pipeline that inappropriately + * uses side-effects to one that does not, the following code searches a stream + * of strings for those matching a given regular expression, and puts the + * matches in a list. + * + *

    {@code
    + *     ArrayList results = new ArrayList<>();
    + *     stream.filter(s -> pattern.matcher(s).matches())
    + *           .forEach(s -> results.add(s));  // Unnecessary use of side-effects!
    + * }
    + * + * This code unnecessarily uses side-effects. If executed in parallel, the + * non-thread-safety of {@code ArrayList} would cause incorrect results, and + * adding needed synchronization would cause contention, undermining the + * benefit of parallelism. Furthermore, using side-effects here is completely + * unnecessary; the {@code forEach()} can simply be replaced with a reduction + * operation that is safer, more efficient, and more amenable to + * parallelization: + * + *
    {@code
    + *     Listresults =
    + *         stream.filter(s -> pattern.matcher(s).matches())
    + *               .collect(Collectors.toList());  // No side-effects!
    + * }
    + * + *

    Ordering

    + * + *

    Streams may or may not have a defined encounter order. Whether + * or not a stream has an encounter order depends on the source and the + * intermediate operations. Certain stream sources (such as {@code List} or + * arrays) are intrinsically ordered, whereas others (such as {@code HashSet}) + * are not. Some intermediate operations, such as {@code sorted()}, may impose + * an encounter order on an otherwise unordered stream, and others may render an + * ordered stream unordered, such as {@link java.util.stream.BaseStream#unordered()}. + * Further, some terminal operations may ignore encounter order, such as + * {@code forEach()}. + * + *

    If a stream is ordered, most operations are constrained to operate on the + * elements in their encounter order; if the source of a stream is a {@code List} + * containing {@code [1, 2, 3]}, then the result of executing {@code map(x -> x*2)} + * must be {@code [2, 4, 6]}. However, if the source has no defined encounter + * order, then any permutation of the values {@code [2, 4, 6]} would be a valid + * result. + * + *

    For sequential streams, the presence or absence of an encounter order does + * not affect performance, only determinism. If a stream is ordered, repeated + * execution of identical stream pipelines on an identical source will produce + * an identical result; if it is not ordered, repeated execution might produce + * different results. + * + *

    For parallel streams, relaxing the ordering constraint can sometimes enable + * more efficient execution. Certain aggregate operations, + * such as filtering duplicates ({@code distinct()}) or grouped reductions + * ({@code Collectors.groupingBy()}) can be implemented more efficiently if ordering of elements + * is not relevant. Similarly, operations that are intrinsically tied to encounter order, + * such as {@code limit()}, may require + * buffering to ensure proper ordering, undermining the benefit of parallelism. + * In cases where the stream has an encounter order, but the user does not + * particularly care about that encounter order, explicitly de-ordering + * the stream with {@link java.util.stream.BaseStream#unordered() unordered()} may + * improve parallel performance for some stateful or terminal operations. + * However, most stream pipelines, such as the "sum of weight of blocks" example + * above, still parallelize efficiently even under ordering constraints. + * *

    Reduction operations

    * - * A reduction operation takes a stream of elements and processes them in a way - * that reduces to a single value or summary description, such as finding the sum or maximum - * of a set of numbers. (In more complex scenarios, the reduction operation might need to - * extract data from the elements before reducing that data to a single value, such as - * finding the sum of weights of a set of blocks. This would require extracting the weight - * from each block before summing up the weights.) + * A reduction operation (also called a fold) takes a sequence + * of input elements and combines them into a single summary result by repeated + * application of a combining operation, such as finding the sum or maximum of + * a set of numbers, or accumulating elements into a list. The streams classes have + * multiple forms of general reduction operations, called + * {@link java.util.stream.Stream#reduce(java.util.function.BinaryOperator) reduce()} + * and {@link java.util.stream.Stream#collect(java.util.stream.Collector) collect()}, + * as well as multiple specialized reduction forms such as + * {@link java.util.stream.IntStream#sum() sum()}, {@link java.util.stream.IntStream#max() max()}, + * or {@link java.util.stream.IntStream#count() count()}. * - *

    Of course, such operations can be readily implemented as simple sequential loops, as in: + *

    Of course, such operations can be readily implemented as simple sequential + * loops, as in: *

    {@code
      *    int sum = 0;
      *    for (int x : numbers) {
      *       sum += x;
      *    }
      * }
    - * However, there may be a significant advantage to preferring a {@link java.util.stream.Stream#reduce reduce operation} - * over a mutative accumulation such as the above -- a properly constructed reduce operation is - * inherently parallelizable so long as the - * {@link java.util.function.BinaryOperator reduction operaterator} - * has the right characteristics. Specifically the operator must be - * associative. For example, given a - * stream of numbers for which we want to find the sum, we can write: + * However, there are good reasons to prefer a reduce operation + * over a mutative accumulation such as the above. Not only is a reduction + * "more abstract" -- it operates on the stream as a whole rather than individual + * elements -- but a properly constructed reduce operation is inherently + * parallelizable, so long as the function(s) used to process the elements + * are associative and + * stateless. + * For example, given a stream of numbers for which we want to find the sum, we + * can write: *
    {@code
    - *    int sum = numbers.reduce(0, (x,y) -> x+y);
    + *    int sum = numbers.stream().reduce(0, (x,y) -> x+y);
      * }
    - * or more succinctly: + * or: *
    {@code
    - *    int sum = numbers.reduce(0, Integer::sum);
    + *    int sum = numbers.stream().reduce(0, Integer::sum);
      * }
    * - *

    (The primitive specializations of {@link java.util.stream.Stream}, such as - * {@link java.util.stream.IntStream}, even have convenience methods for common reductions, - * such as {@link java.util.stream.IntStream#sum() sum} and {@link java.util.stream.IntStream#max() max}, - * which are implemented as simple wrappers around reduce.) - * - *

    Reduction parallellizes well since the implementation of {@code reduce} can operate on - * subsets of the stream in parallel, and then combine the intermediate results to get the final - * correct answer. Even if you were to use a parallelizable form of the - * {@link java.util.stream.Stream#forEach(Consumer) forEach()} method - * in place of the original for-each loop above, you would still have to provide thread-safe - * updates to the shared accumulating variable {@code sum}, and the required synchronization - * would likely eliminate any performance gain from parallelism. Using a {@code reduce} method - * instead removes all of the burden of parallelizing the reduction operation, and the library - * can provide an efficient parallel implementation with no additional synchronization needed. - * - *

    The "blocks" examples shown earlier shows how reduction combines with other operations - * to replace for loops with bulk operations. If {@code blocks} is a collection of {@code Block} - * objects, which have a {@code getWeight} method, we can find the heaviest block with: + *

    These reduction operations can run safely in parallel with almost no + * modification: *

    {@code
    - *     OptionalInt heaviest = blocks.stream()
    - *                                  .mapToInt(Block::getWeight)
    - *                                  .reduce(Integer::max);
    + *    int sum = numbers.parallelStream().reduce(0, Integer::sum);
      * }
    * - *

    In its more general form, a {@code reduce} operation on elements of type {@code } - * yielding a result of type {@code } requires three parameters: + *

    Reduction parallellizes well because the implementation + * can operate on subsets of the data in parallel, and then combine the + * intermediate results to get the final correct answer. (Even if the language + * had a "parallel for-each" construct, the mutative accumulation approach would + * still required the developer to provide + * thread-safe updates to the shared accumulating variable {@code sum}, and + * the required synchronization would then likely eliminate any performance gain from + * parallelism.) Using {@code reduce()} instead removes all of the + * burden of parallelizing the reduction operation, and the library can provide + * an efficient parallel implementation with no additional synchronization + * required. + * + *

    The "widgets" examples shown earlier shows how reduction combines with + * other operations to replace for loops with bulk operations. If {@code widgets} + * is a collection of {@code Widget} objects, which have a {@code getWeight} method, + * we can find the heaviest widget with: + *

    {@code
    + *     OptionalInt heaviest = widgets.parallelStream()
    + *                                   .mapToInt(Widget::getWeight)
    + *                                   .max();
    + * }
    + * + *

    In its more general form, a {@code reduce} operation on elements of type + * {@code } yielding a result of type {@code } requires three parameters: *

    {@code
      *  U reduce(U identity,
    - *              BiFunction accumlator,
    + *              BiFunction accumulator,
      *              BinaryOperator combiner);
      * }
    - * Here, the identity element is both an initial seed for the reduction, and a default - * result if there are no elements. The accumulator function takes a partial result and - * the next element, and produce a new partial result. The combiner function combines - * the partial results of two accumulators to produce a new partial result, and eventually the - * final result. + * Here, the identity element is both an initial seed value for the reduction + * and a default result if there are no input elements. The accumulator + * function takes a partial result and the next element, and produces a new + * partial result. The combiner function combines two partial results + * to produce a new partial result. (The combiner is necessary in parallel + * reductions, where the input is partitioned, a partial accumulation computed + * for each partition, and then the partial results are combined to produce a + * final result.) * - *

    This form is a generalization of the two-argument form, and is also a generalization of - * the map-reduce construct illustrated above. If we wanted to re-cast the simple {@code sum} - * example using the more general form, {@code 0} would be the identity element, while - * {@code Integer::sum} would be both the accumulator and combiner. For the sum-of-weights - * example, this could be re-cast as: + *

    More formally, the {@code identity} value must be an identity for + * the combiner function. This means that for all {@code u}, + * {@code combiner.apply(identity, u)} is equal to {@code u}. Additionally, the + * {@code combiner} function must be associative and + * must be compatible with the {@code accumulator} function: for all {@code u} + * and {@code t}, {@code combiner.apply(u, accumulator.apply(identity, t))} must + * be {@code equals()} to {@code accumulator.apply(u, t)}. + * + *

    The three-argument form is a generalization of the two-argument form, + * incorporating a mapping step into the accumulation step. We could + * re-cast the simple sum-of-weights example using the more general form as + * follows: *

    {@code
    - *     int sumOfWeights = blocks.stream().reduce(0,
    - *                                               (sum, b) -> sum + b.getWeight())
    - *                                               Integer::sum);
    + *     int sumOfWeights = widgets.stream()
    + *                               .reduce(0,
    + *                                       (sum, b) -> sum + b.getWeight())
    + *                                       Integer::sum);
      * }
    - * though the map-reduce form is more readable and generally preferable. The generalized form - * is provided for cases where significant work can be optimized away by combining mapping and - * reducing into a single function. + * though the explicit map-reduce form is more readable and therefore should + * usually be preferred. The generalized form is provided for cases where + * significant work can be optimized away by combining mapping and reducing + * into a single function. * - *

    More formally, the {@code identity} value must be an identity for the combiner - * function. This means that for all {@code u}, {@code combiner.apply(identity, u)} is equal - * to {@code u}. Additionally, the {@code combiner} function must be - * associative and must be compatible with the {@code accumulator} - * function; for all {@code u} and {@code t}, the following must hold: - *

    {@code
    - *     combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
    - * }
    + *

    Mutable reduction

    * - *

    Mutable Reduction

    - * - * A mutable reduction operation is similar to an ordinary reduction, in that it reduces - * a stream of values to a single value, but instead of producing a distinct single-valued result, it - * mutates a general result container, such as a {@code Collection} or {@code StringBuilder}, + * A mutable reduction operation accumulates input elements into a + * mutable result container, such as a {@code Collection} or {@code StringBuilder}, * as it processes the elements in the stream. * - *

    For example, if we wanted to take a stream of strings and concatenate them into a single - * long string, we could achieve this with ordinary reduction: + *

    If we wanted to take a stream of strings and concatenate them into a + * single long string, we could achieve this with ordinary reduction: *

    {@code
      *     String concatenated = strings.reduce("", String::concat)
      * }
    * - * We would get the desired result, and it would even work in parallel. However, we might not - * be happy about the performance! Such an implementation would do a great deal of string - * copying, and the run time would be O(n^2) in the number of elements. A more - * performant approach would be to accumulate the results into a {@link java.lang.StringBuilder}, which - * is a mutable container for accumulating strings. We can use the same technique to + *

    We would get the desired result, and it would even work in parallel. However, + * we might not be happy about the performance! Such an implementation would do + * a great deal of string copying, and the run time would be O(n^2) in + * the number of characters. A more performant approach would be to accumulate + * the results into a {@link java.lang.StringBuilder}, which is a mutable + * container for accumulating strings. We can use the same technique to * parallelize mutable reduction as we do with ordinary reduction. * - *

    The mutable reduction operation is called {@link java.util.stream.Stream#collect(Collector) collect()}, as it - * collects together the desired results into a result container such as {@code StringBuilder}. - * A {@code collect} operation requires three things: a factory function which will construct - * new instances of the result container, an accumulating function that will update a result - * container by incorporating a new element, and a combining function that can take two - * result containers and merge their contents. The form of this is very similar to the general + *

    The mutable reduction operation is called + * {@link java.util.stream.Stream#collect(Collector) collect()}, + * as it collects together the desired results into a result container such + * as a {@code Collection}. + * A {@code collect} operation requires three functions: + * a factory function to construct new instances of the result container, an + * accumulator function to incorporate an input element into a result + * container, and a combining function to merge the contents of one result + * container into another. The form of this is very similar to the general * form of ordinary reduction: *

    {@code
      *  R collect(Supplier resultFactory,
      *               BiConsumer accumulator,
      *               BiConsumer combiner);
      * }
    - * As with {@code reduce()}, the benefit of expressing {@code collect} in this abstract way is - * that it is directly amenable to parallelization: we can accumulate partial results in parallel - * and then combine them. For example, to collect the String representations of the elements - * in a stream into an {@code ArrayList}, we could write the obvious sequential for-each form: + *

    As with {@code reduce()}, a benefit of expressing {@code collect} in this + * abstract way is that it is directly amenable to parallelization: we can + * accumulate partial results in parallel and then combine them, so long as the + * accumulation and combining functions satisfy the appropriate requirements. + * For example, to collect the String representations of the elements in a + * stream into an {@code ArrayList}, we could write the obvious sequential + * for-each form: *

    {@code
      *     ArrayList strings = new ArrayList<>();
      *     for (T element : stream) {
    @@ -377,92 +514,107 @@
      *                                                (c, e) -> c.add(e.toString()),
      *                                                (c1, c2) -> c1.addAll(c2));
      * }
    - * or, noting that we have buried a mapping operation inside the accumulator function, more - * succinctly as: + * or, pulling the mapping operation out of the accumulator function, we could + * express it more succinctly as: *
    {@code
    - *     ArrayList strings = stream.map(Object::toString)
    - *                                       .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    + *     List strings = stream.map(Object::toString)
    + *                                  .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
      * }
    - * Here, our supplier is just the {@link java.util.ArrayList#ArrayList() ArrayList constructor}, the - * accumulator adds the stringified element to an {@code ArrayList}, and the combiner simply - * uses {@link java.util.ArrayList#addAll addAll} to copy the strings from one container into the other. + * Here, our supplier is just the {@link java.util.ArrayList#ArrayList() + * ArrayList constructor}, the accumulator adds the stringified element to an + * {@code ArrayList}, and the combiner simply uses {@link java.util.ArrayList#addAll addAll} + * to copy the strings from one container into the other. * - *

    As with the regular reduction operation, the ability to parallelize only comes if an - * associativity condition is met. The {@code combiner} is associative - * if for result containers {@code r1}, {@code r2}, and {@code r3}: + *

    The three aspects of {@code collect} -- supplier, accumulator, and combiner -- + * are tightly coupled. We can use the abstraction of + * of a {@link java.util.stream.Collector} to capture all three aspects. + * The above example for collecting strings into a {@code List} can be rewritten + * using a standard {@code Collector} as: *

    {@code
    - *    combiner.accept(r1, r2);
    - *    combiner.accept(r1, r3);
    - * }
    - * is equivalent to - *
    {@code
    - *    combiner.accept(r2, r3);
    - *    combiner.accept(r1, r2);
    - * }
    - * where equivalence means that {@code r1} is left in the same state (according to the meaning - * of {@link java.lang.Object#equals equals} for the element types). Similarly, the {@code resultFactory} - * must act as an identity with respect to the {@code combiner} so that for any result - * container {@code r}: - *
    {@code
    - *     combiner.accept(r, resultFactory.get());
    - * }
    - * does not modify the state of {@code r} (again according to the meaning of - * {@link java.lang.Object#equals equals}). Finally, the {@code accumulator} and {@code combiner} must be - * compatible such that for a result container {@code r} and element {@code t}: - *
    {@code
    - *    r2 = resultFactory.get();
    - *    accumulator.accept(r2, t);
    - *    combiner.accept(r, r2);
    - * }
    - * is equivalent to: - *
    {@code
    - *    accumulator.accept(r,t);
    - * }
    - * where equivalence means that {@code r} is left in the same state (again according to the - * meaning of {@link java.lang.Object#equals equals}). - * - *

    The three aspects of {@code collect}: supplier, accumulator, and combiner, are often very - * tightly coupled, and it is convenient to introduce the notion of a {@link java.util.stream.Collector} as - * being an object that embodies all three aspects. There is a {@link java.util.stream.Stream#collect(Collector) collect} - * method that simply takes a {@code Collector} and returns the resulting container. - * The above example for collecting strings into a {@code List} can be rewritten using a - * standard {@code Collector} as: - *

    {@code
    - *     ArrayList strings = stream.map(Object::toString)
    - *                                       .collect(Collectors.toList());
    + *     List strings = stream.map(Object::toString)
    + *                                  .collect(Collectors.toList());
      * }
    * - *

    Reduction, Concurrency, and Ordering

    + *

    Packaging mutable reductions into a Collector has another advantage: + * composability. The class {@link java.util.stream.Collectors} contains a + * number of predefined factories for collectors, including combinators + * that transform one collector into another. For example, suppose we have a + * collector that computes the sum of the salaries of a stream of + * employees, as follows: * - * With some complex reduction operations, for example a collect that produces a - * {@code Map}, such as: + *

    {@code
    + *     Collector summingSalaries
    + *         = Collectors.summingInt(Employee::getSalary);
    + * } 
    + * + * (The {@code ?} for the second type parameter merely indicates that we don't + * care about the intermediate representation used by this collector.) + * If we wanted to create a collector to tabulate the sum of salaries by + * department, we could reuse {@code summingSalaries} using + * {@link java.util.stream.Collectors#groupingBy(java.util.function.Function, java.util.stream.Collector) groupingBy}: + * + *
    {@code
    + *     Map salariesByDept
    + *         = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment,
    + *                                                            summingSalaries));
    + * } 
    + * + *

    As with the regular reduction operation, {@code collect()} operations can + * only be parallelized if appropriate conditions are met. For any partially accumulated result, + * combining it with an empty result container must produce an equivalent + * result. That is, for a partially accumulated result {@code p} that is the + * result of any series of accumulator and combiner invocations, {@code p} must + * be equivalent to {@code combiner.apply(p, supplier.get())}. + * + *

    Further, however the computation is split, it must produce an equivalent + * result. For any input elements {@code t1} and {@code t2}, the results + * {@code r1} and {@code r2} in the computation below must be equivalent: + *

    {@code
    + *     A a1 = supplier.get();
    + *     accumulator.accept(a1, t1);
    + *     accumulator.accept(a1, t2);
    + *     R r1 = finisher.apply(a1);  // result without splitting
    + *
    + *     A a2 = supplier.get();
    + *     accumulator.accept(a2, t1);
    + *     A a3 = supplier.get();
    + *     accumulator.accept(a3, t2);
    + *     R r2 = finisher.apply(combiner.apply(a2, a3));  // result with splitting
    + * } 
    + * + *

    Here, equivalence generally means according to {@link java.lang.Object#equals(Object)}. + * but in some cases equivalence may be relaxed to account for differences in + * order. + * + *

    Reduction, concurrency, and ordering

    + * + * With some complex reduction operations, for example a {@code collect()} that + * produces a {@code Map}, such as: *
    {@code
      *     Map> salesByBuyer
      *         = txns.parallelStream()
      *               .collect(Collectors.groupingBy(Transaction::getBuyer));
      * }
    - * (where {@link java.util.stream.Collectors#groupingBy} is a utility function - * that returns a {@link java.util.stream.Collector} for grouping sets of elements based on some key) * it may actually be counterproductive to perform the operation in parallel. * This is because the combining step (merging one {@code Map} into another by key) * can be expensive for some {@code Map} implementations. * *

    Suppose, however, that the result container used in this reduction * was a concurrently modifiable collection -- such as a - * {@link java.util.concurrent.ConcurrentHashMap ConcurrentHashMap}. In that case, - * the parallel invocations of the accumulator could actually deposit their results - * concurrently into the same shared result container, eliminating the need for the combiner to - * merge distinct result containers. This potentially provides a boost - * to the parallel execution performance. We call this a concurrent reduction. + * {@link java.util.concurrent.ConcurrentHashMap}. In that case, the parallel + * invocations of the accumulator could actually deposit their results + * concurrently into the same shared result container, eliminating the need for + * the combiner to merge distinct result containers. This potentially provides + * a boost to the parallel execution performance. We call this a concurrent + * reduction. * - *

    A {@link java.util.stream.Collector} that supports concurrent reduction is marked with the - * {@link java.util.stream.Collector.Characteristics#CONCURRENT} characteristic. - * Having a concurrent collector is a necessary condition for performing a - * concurrent reduction, but that alone is not sufficient. If you imagine multiple - * accumulators depositing results into a shared container, the order in which - * results are deposited is non-deterministic. Consequently, a concurrent reduction - * is only possible if ordering is not important for the stream being processed. - * The {@link java.util.stream.Stream#collect(Collector)} + *

    A {@link java.util.stream.Collector} that supports concurrent reduction is + * marked with the {@link java.util.stream.Collector.Characteristics#CONCURRENT} + * characteristic. However, a concurrent collection also has a downside. If + * multiple threads are depositing results concurrently into a shared container, + * the order in which results are deposited is non-deterministic. Consequently, + * a concurrent reduction is only possible if ordering is not important for the + * stream being processed. The {@link java.util.stream.Stream#collect(Collector)} * implementation will only perform a concurrent reduction if *

      *
    • The stream is parallel;
    • @@ -472,15 +624,16 @@ *
    • Either the stream is unordered, or the collector has the * {@link java.util.stream.Collector.Characteristics#UNORDERED} characteristic. *
    - * For example: + * You can ensure the stream is unordered by using the + * {@link java.util.stream.BaseStream#unordered()} method. For example: *
    {@code
      *     Map> salesByBuyer
      *         = txns.parallelStream()
      *               .unordered()
      *               .collect(groupingByConcurrent(Transaction::getBuyer));
      * }
    - * (where {@link java.util.stream.Collectors#groupingByConcurrent} is the concurrent companion - * to {@code groupingBy}). + * (where {@link java.util.stream.Collectors#groupingByConcurrent} is the + * concurrent equivalent of {@code groupingBy}). * *

    Note that if it is important that the elements for a given key appear in the * order they appear in the source, then we cannot use a concurrent reduction, @@ -488,79 +641,77 @@ * be constrained to implement either a sequential reduction or a merge-based * parallel reduction. * - *

    Associativity

    + *

    Associativity

    * - * An operator or function {@code op} is associative if the following holds: + * An operator or function {@code op} is associative if the following + * holds: *
    {@code
      *     (a op b) op c == a op (b op c)
      * }
    - * The importance of this to parallel evaluation can be seen if we expand this to four terms: + * The importance of this to parallel evaluation can be seen if we expand this + * to four terms: *
    {@code
      *     a op b op c op d == (a op b) op (c op d)
      * }
    - * So we can evaluate {@code (a op b)} in parallel with {@code (c op d)} and then invoke {@code op} on - * the results. - * TODO what does associative mean for mutative combining functions? - * FIXME: we described mutative associativity above. + * So we can evaluate {@code (a op b)} in parallel with {@code (c op d)}, and + * then invoke {@code op} on the results. * - *

    Stream sources

    - * TODO where does this section go? + *

    Examples of associative operations include numeric addition, min, and max, + * and string concatenation. * - * XXX - change to section to stream construction gradually introducing more - * complex ways to construct - * - construction from Collection - * - construction from Iterator - * - construction from array - * - construction from generators - * - construction from spliterator + *

    Low-level stream construction

    * - * XXX - the following is quite low-level but important aspect of stream constriction + * So far, all the stream examples have used methods like + * {@link java.util.Collection#stream()} or {@link java.util.Arrays#stream(Object[])} + * to obtain a stream. How are those stream-bearing methods implemented? * - *

    A pipeline is initially constructed from a spliterator (see {@link java.util.Spliterator}) supplied by a stream source. - * The spliterator covers elements of the source and provides element traversal operations - * for a possibly-parallel computation. See methods on {@link java.util.stream.Streams} for construction - * of pipelines using spliterators. + *

    The class {@link java.util.stream.StreamSupport} has a number of low-level + * methods for creating a stream, all using some form of a {@link java.util.Spliterator}. + * A spliterator is the parallel analogue of an {@link java.util.Iterator}; it + * describes a (possibly infinite) collection of elements, with support for + * sequentially advancing, bulk traversal, and splitting off some portion of the + * input into another spliterator which can be processed in parallel. At the + * lowest level, all streams are driven by a spliterator. * - *

    A source may directly supply a spliterator. If so, the spliterator is traversed, split, or queried - * for estimated size after, and never before, the terminal operation commences. It is strongly recommended - * that the spliterator report a characteristic of {@code IMMUTABLE} or {@code CONCURRENT}, or be - * late-binding and not bind to the elements it covers until traversed, split or queried for - * estimated size. + *

    There are a number of implementation choices in implementing a spliterator, + * nearly all of which are tradeoffs between simplicity of implementation and + * runtime performance of streams using that spliterator. The simplest, but + * least performant, way to create a spliterator is to create one from an iterator + * using {@link java.util.Spliterators#spliteratorUnknownSize(java.util.Iterator, int)}. + * While such a spliterator will work, it will likely offer poor parallel + * performance, since we have lost sizing information (how big is the underlying + * data set), as well as being constrained to a simplistic splitting algorithm. * - *

    If a source cannot directly supply a recommended spliterator then it may indirectly supply a spliterator - * using a {@code Supplier}. The spliterator is obtained from the supplier after, and never before, the terminal + *

    A higher-quality spliterator will provide balanced and known-size splits, + * accurate sizing information, and a number of other + * {@link java.util.Spliterator#characteristics() characteristics} of the + * spliterator or data that can be used by implementations to optimize + * execution. + * + *

    Spliterators for mutable data sources have an additional challenge; timing + * of binding to the data, since the data could change between the time the + * spliterator is created and the time the stream pipeline is executed. Ideally, + * a spliterator for a stream would report a characteristic of {@code IMMUTABLE} + * or {@code CONCURRENT}; if not it should be late-binding. + * If a source cannot directly supply a recommended spliterator, it may + * indirectly supply a spliterator using a {@code Supplier}, and construct a + * stream via the {@code Supplier}-accepting versions of + * {@link java.util.stream.StreamSupport#stream(Supplier, int, boolean) stream()}. + * The spliterator is obtained from the supplier only after the terminal * operation of the stream pipeline commences. * - *

    Such requirements significantly reduce the scope of potential interference to the interval starting - * with the commencing of the terminal operation and ending with the producing a result or side-effect. See - * Non-Interference for - * more details. + *

    These requirements significantly reduce the scope of potential interference + * between mutations of the stream source and execution of stream pipelines. + * Streams based on spliterators with the desired characteristics, or those using + * the Supplier-based factory forms, are immune to modifications of the data + * source prior to commencement of the terminal operation (provided the behavioral + * parameters to the stream operations meet the required criteria for non-interference + * and statelessness). See Non-Interference + * for more details. * - * XXX - move the following to the non-interference section - * - *

    A source can be modified before the terminal operation commences and those modifications will be reflected in - * the covered elements. Afterwards, and depending on the properties of the source, further modifications - * might not be reflected and the throwing of a {@code ConcurrentModificationException} may occur. - * - *

    For example, consider the following code: - *

    {@code
    - *     List l = new ArrayList(Arrays.asList("one", "two"));
    - *     Stream sl = l.stream();
    - *     l.add("three");
    - *     String s = sl.collect(joining(" "));
    - * }
    - * First a list is created consisting of two strings: "one"; and "two". Then a stream is created from that list. - * Next the list is modified by adding a third string: "three". Finally the elements of the stream are collected - * and joined together. Since the list was modified before the terminal {@code collect} operation commenced - * the result will be a string of "one two three". However, if the list is modified after the terminal operation - * commences, as in: - *
    {@code
    - *     List l = new ArrayList(Arrays.asList("one", "two"));
    - *     Stream sl = l.stream();
    - *     String s = sl.peek(s -> l.add("BAD LAMBDA")).collect(joining(" "));
    - * }
    - * then a {@code ConcurrentModificationException} will be thrown since the {@code peek} operation will attempt - * to add the string "BAD LAMBDA" to the list after the terminal operation has commenced. + * @since 1.8 */ - package java.util.stream; + +import java.util.function.BinaryOperator; +import java.util.function.UnaryOperator; From 28d455529e7bc76985029e762442edd824125e10 Mon Sep 17 00:00:00 2001 From: Dmitry Nadezhin Date: Wed, 11 Sep 2013 17:07:35 -0700 Subject: [PATCH 58/75] 8010430: Math.round has surprising behavior for odd values of ulp 1 If the effective floating point exponent is zero return the significand including the implicit 1-bit. Reviewed-by: bpb, darcy, gls --- jdk/src/share/classes/java/lang/Math.java | 66 ++++++++++++++--- .../share/classes/java/lang/StrictMath.java | 4 +- jdk/test/java/lang/Math/RoundTests.java | 71 ++++++++++++++++++- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/jdk/src/share/classes/java/lang/Math.java b/jdk/src/share/classes/java/lang/Math.java index ae83e4265ad..98e901ac942 100644 --- a/jdk/src/share/classes/java/lang/Math.java +++ b/jdk/src/share/classes/java/lang/Math.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -646,7 +646,7 @@ public final class Math { /** * Returns the closest {@code int} to the argument, with ties - * rounding up. + * rounding to positive infinity. * *

    * Special cases: @@ -665,15 +665,37 @@ public final class Math { * @see java.lang.Integer#MIN_VALUE */ public static int round(float a) { - if (a != 0x1.fffffep-2f) // greatest float value less than 0.5 - return (int)floor(a + 0.5f); - else - return 0; + int intBits = Float.floatToRawIntBits(a); + int biasedExp = (intBits & FloatConsts.EXP_BIT_MASK) + >> (FloatConsts.SIGNIFICAND_WIDTH - 1); + int shift = (FloatConsts.SIGNIFICAND_WIDTH - 2 + + FloatConsts.EXP_BIAS) - biasedExp; + if ((shift & -32) == 0) { // shift >= 0 && shift < 32 + // a is a finite number such that pow(2,-32) <= ulp(a) < 1 + int r = ((intBits & FloatConsts.SIGNIF_BIT_MASK) + | (FloatConsts.SIGNIF_BIT_MASK + 1)); + if (intBits < 0) { + r = -r; + } + // In the comments below each Java expression evaluates to the value + // the corresponding mathematical expression: + // (r) evaluates to a / ulp(a) + // (r >> shift) evaluates to floor(a * 2) + // ((r >> shift) + 1) evaluates to floor((a + 1/2) * 2) + // (((r >> shift) + 1) >> 1) evaluates to floor(a + 1/2) + return ((r >> shift) + 1) >> 1; + } else { + // a is either + // - a finite number with abs(a) < exp(2,FloatConsts.SIGNIFICAND_WIDTH-32) < 1/2 + // - a finite number with ulp(a) >= 1 and hence a is a mathematical integer + // - an infinity or NaN + return (int) a; + } } /** * Returns the closest {@code long} to the argument, with ties - * rounding up. + * rounding to positive infinity. * *

    Special cases: *

    • If the argument is NaN, the result is 0. @@ -692,10 +714,32 @@ public final class Math { * @see java.lang.Long#MIN_VALUE */ public static long round(double a) { - if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5 - return (long)floor(a + 0.5d); - else - return 0; + long longBits = Double.doubleToRawLongBits(a); + long biasedExp = (longBits & DoubleConsts.EXP_BIT_MASK) + >> (DoubleConsts.SIGNIFICAND_WIDTH - 1); + long shift = (DoubleConsts.SIGNIFICAND_WIDTH - 2 + + DoubleConsts.EXP_BIAS) - biasedExp; + if ((shift & -64) == 0) { // shift >= 0 && shift < 64 + // a is a finite number such that pow(2,-64) <= ulp(a) < 1 + long r = ((longBits & DoubleConsts.SIGNIF_BIT_MASK) + | (DoubleConsts.SIGNIF_BIT_MASK + 1)); + if (longBits < 0) { + r = -r; + } + // In the comments below each Java expression evaluates to the value + // the corresponding mathematical expression: + // (r) evaluates to a / ulp(a) + // (r >> shift) evaluates to floor(a * 2) + // ((r >> shift) + 1) evaluates to floor((a + 1/2) * 2) + // (((r >> shift) + 1) >> 1) evaluates to floor(a + 1/2) + return ((r >> shift) + 1) >> 1; + } else { + // a is either + // - a finite number with abs(a) < exp(2,DoubleConsts.SIGNIFICAND_WIDTH-64) < 1/2 + // - a finite number with ulp(a) >= 1 and hence a is a mathematical integer + // - an infinity or NaN + return (long) a; + } } private static final class RandomNumberGeneratorHolder { diff --git a/jdk/src/share/classes/java/lang/StrictMath.java b/jdk/src/share/classes/java/lang/StrictMath.java index 52336484e75..ae4af2bcac8 100644 --- a/jdk/src/share/classes/java/lang/StrictMath.java +++ b/jdk/src/share/classes/java/lang/StrictMath.java @@ -633,7 +633,7 @@ public final class StrictMath { /** * Returns the closest {@code int} to the argument, with ties - * rounding up. + * rounding to positive infinity. * *

      Special cases: *

      • If the argument is NaN, the result is 0. @@ -656,7 +656,7 @@ public final class StrictMath { /** * Returns the closest {@code long} to the argument, with ties - * rounding up. + * rounding to positive infinity. * *

        Special cases: *

        • If the argument is NaN, the result is 0. diff --git a/jdk/test/java/lang/Math/RoundTests.java b/jdk/test/java/lang/Math/RoundTests.java index 9994e97df85..cae190f9770 100644 --- a/jdk/test/java/lang/Math/RoundTests.java +++ b/jdk/test/java/lang/Math/RoundTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 6430675 + * @bug 6430675 8010430 * @summary Check for correct implementation of {Math, StrictMath}.round */ public class RoundTests { @@ -32,6 +32,8 @@ public class RoundTests { failures += testNearFloatHalfCases(); failures += testNearDoubleHalfCases(); + failures += testUnityULPCases(); + failures += testSpecialCases(); if (failures > 0) { System.err.println("Testing {Math, StrictMath}.round incurred " @@ -95,4 +97,69 @@ public class RoundTests { return failures; } + + private static int testUnityULPCases() { + int failures = 0; + for (float sign : new float[]{-1, 1}) { + for (float v1 : new float[]{1 << 23, 1 << 24}) { + for (int k = -5; k <= 5; k++) { + float value = (v1 + k) * sign; + float actual = Math.round(value); + failures += Tests.test("Math.round", value, actual, value); + } + } + } + + if (failures != 0) { + System.out.println(); + } + + for (double sign : new double[]{-1, 1}) { + for (double v1 : new double[]{1L << 52, 1L << 53}) { + for (int k = -5; k <= 5; k++) { + double value = (v1 + k) * sign; + double actual = Math.round(value); + failures += Tests.test("Math.round", value, actual, value); + } + } + } + + return failures; + } + + private static int testSpecialCases() { + int failures = 0; + + failures += Tests.test("Math.round", Float.NaN, Math.round(Float.NaN), 0.0F); + failures += Tests.test("Math.round", Float.POSITIVE_INFINITY, + Math.round(Float.POSITIVE_INFINITY), Integer.MAX_VALUE); + failures += Tests.test("Math.round", Float.NEGATIVE_INFINITY, + Math.round(Float.NEGATIVE_INFINITY), Integer.MIN_VALUE); + failures += Tests.test("Math.round", -(float)Integer.MIN_VALUE, + Math.round(-(float)Integer.MIN_VALUE), Integer.MAX_VALUE); + failures += Tests.test("Math.round", (float) Integer.MIN_VALUE, + Math.round((float) Integer.MIN_VALUE), Integer.MIN_VALUE); + failures += Tests.test("Math.round", 0F, Math.round(0F), 0.0F); + failures += Tests.test("Math.round", Float.MIN_VALUE, + Math.round(Float.MIN_VALUE), 0.0F); + failures += Tests.test("Math.round", -Float.MIN_VALUE, + Math.round(-Float.MIN_VALUE), 0.0F); + + failures += Tests.test("Math.round", Double.NaN, Math.round(Double.NaN), 0.0); + failures += Tests.test("Math.round", Double.POSITIVE_INFINITY, + Math.round(Double.POSITIVE_INFINITY), Long.MAX_VALUE); + failures += Tests.test("Math.round", Double.NEGATIVE_INFINITY, + Math.round(Double.NEGATIVE_INFINITY), Long.MIN_VALUE); + failures += Tests.test("Math.round", -(double)Long.MIN_VALUE, + Math.round(-(double)Long.MIN_VALUE), Long.MAX_VALUE); + failures += Tests.test("Math.round", (double) Long.MIN_VALUE, + Math.round((double) Long.MIN_VALUE), Long.MIN_VALUE); + failures += Tests.test("Math.round", 0, Math.round(0), 0.0); + failures += Tests.test("Math.round", Double.MIN_VALUE, + Math.round(Double.MIN_VALUE), 0.0); + failures += Tests.test("Math.round", -Double.MIN_VALUE, + Math.round(-Double.MIN_VALUE), 0.0); + + return failures; + } } From f6e4c46294b1a9df3f613bd4f14d6a6fc6b7c30f Mon Sep 17 00:00:00 2001 From: Shanliang Jiang Date: Fri, 13 Sep 2013 10:48:12 +0200 Subject: [PATCH 59/75] 8023669: MBean*Info.hashCode : NPE Reviewed-by: dholmes, dfuchs, jbachorik --- .../javax/management/MBeanAttributeInfo.java | 3 +- .../management/MBeanConstructorInfo.java | 7 +- .../classes/javax/management/MBeanInfo.java | 20 +- .../javax/management/MBeanOperationInfo.java | 3 +- .../javax/management/MBeanParameterInfo.java | 3 +- .../MBeanInfo/MBeanInfoHashCodeNPETest.java | 176 ++++++++++++++++++ 6 files changed, 190 insertions(+), 22 deletions(-) create mode 100644 jdk/test/javax/management/MBeanInfo/MBeanInfoHashCodeNPETest.java diff --git a/jdk/src/share/classes/javax/management/MBeanAttributeInfo.java b/jdk/src/share/classes/javax/management/MBeanAttributeInfo.java index d99b9f7b8ad..a70fb3a3571 100644 --- a/jdk/src/share/classes/javax/management/MBeanAttributeInfo.java +++ b/jdk/src/share/classes/javax/management/MBeanAttributeInfo.java @@ -30,6 +30,7 @@ import java.security.AccessController; import com.sun.jmx.mbeanserver.GetPropertyAction; import com.sun.jmx.mbeanserver.Introspector; +import java.util.Objects; /** @@ -301,7 +302,7 @@ public class MBeanAttributeInfo extends MBeanFeatureInfo implements Cloneable { right and we needlessly hashed in the description and parameter array. */ public int hashCode() { - return getName().hashCode() ^ getType().hashCode(); + return Objects.hash(getName(), getType()); } private static boolean isIs(Method getter) { diff --git a/jdk/src/share/classes/javax/management/MBeanConstructorInfo.java b/jdk/src/share/classes/javax/management/MBeanConstructorInfo.java index c2bbe5ef886..ad2176367d2 100644 --- a/jdk/src/share/classes/javax/management/MBeanConstructorInfo.java +++ b/jdk/src/share/classes/javax/management/MBeanConstructorInfo.java @@ -29,6 +29,7 @@ import com.sun.jmx.mbeanserver.Introspector; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.Arrays; +import java.util.Objects; /** * Describes a constructor exposed by an MBean. Instances of this @@ -203,11 +204,7 @@ public class MBeanConstructorInfo extends MBeanFeatureInfo implements Cloneable quite long and yet the same between constructors. Likewise for the descriptor. */ public int hashCode() { - int hash = getName().hashCode(); - MBeanParameterInfo[] sig = fastGetSignature(); - for (int i = 0; i < sig.length; i++) - hash ^= sig[i].hashCode(); - return hash; + return Objects.hash(getName()) ^ Arrays.hashCode(fastGetSignature()); } private static MBeanParameterInfo[] constructorSignature(Constructor cn) { diff --git a/jdk/src/share/classes/javax/management/MBeanInfo.java b/jdk/src/share/classes/javax/management/MBeanInfo.java index ed04c347b70..f4a9581c520 100644 --- a/jdk/src/share/classes/javax/management/MBeanInfo.java +++ b/jdk/src/share/classes/javax/management/MBeanInfo.java @@ -36,6 +36,7 @@ import java.util.Map; import java.util.WeakHashMap; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Objects; import static javax.management.ImmutableDescriptor.nonNullDescriptor; @@ -515,24 +516,15 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead { if (hashCode != 0) return hashCode; - hashCode = - getClassName().hashCode() ^ - getDescriptor().hashCode() ^ - arrayHashCode(fastGetAttributes()) ^ - arrayHashCode(fastGetOperations()) ^ - arrayHashCode(fastGetConstructors()) ^ - arrayHashCode(fastGetNotifications()); + hashCode = Objects.hash(getClassName(), getDescriptor()) + ^ Arrays.hashCode(fastGetAttributes()) + ^ Arrays.hashCode(fastGetOperations()) + ^ Arrays.hashCode(fastGetConstructors()) + ^ Arrays.hashCode(fastGetNotifications()); return hashCode; } - private static int arrayHashCode(Object[] array) { - int hash = 0; - for (int i = 0; i < array.length; i++) - hash ^= array[i].hashCode(); - return hash; - } - /** * Cached results of previous calls to arrayGettersSafe. This is * a WeakHashMap so that we don't prevent a class from being diff --git a/jdk/src/share/classes/javax/management/MBeanOperationInfo.java b/jdk/src/share/classes/javax/management/MBeanOperationInfo.java index 66b13a94ef0..8effa04f8d8 100644 --- a/jdk/src/share/classes/javax/management/MBeanOperationInfo.java +++ b/jdk/src/share/classes/javax/management/MBeanOperationInfo.java @@ -29,6 +29,7 @@ import com.sun.jmx.mbeanserver.Introspector; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Objects; /** * Describes a management operation exposed by an MBean. Instances of @@ -309,7 +310,7 @@ public class MBeanOperationInfo extends MBeanFeatureInfo implements Cloneable { parameter array. */ @Override public int hashCode() { - return getName().hashCode() ^ getReturnType().hashCode(); + return Objects.hash(getName(), getReturnType()); } private static MBeanParameterInfo[] methodSignature(Method method) { diff --git a/jdk/src/share/classes/javax/management/MBeanParameterInfo.java b/jdk/src/share/classes/javax/management/MBeanParameterInfo.java index ec5a31fc93e..df3d59087df 100644 --- a/jdk/src/share/classes/javax/management/MBeanParameterInfo.java +++ b/jdk/src/share/classes/javax/management/MBeanParameterInfo.java @@ -25,6 +25,7 @@ package javax.management; +import java.util.Objects; /** * Describes an argument of an operation exposed by an MBean. @@ -143,6 +144,6 @@ public class MBeanParameterInfo extends MBeanFeatureInfo implements Cloneable { } public int hashCode() { - return getName().hashCode() ^ getType().hashCode(); + return Objects.hash(getName(), getType()); } } diff --git a/jdk/test/javax/management/MBeanInfo/MBeanInfoHashCodeNPETest.java b/jdk/test/javax/management/MBeanInfo/MBeanInfoHashCodeNPETest.java new file mode 100644 index 00000000000..bb35da38ba6 --- /dev/null +++ b/jdk/test/javax/management/MBeanInfo/MBeanInfoHashCodeNPETest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.modelmbean.DescriptorSupport; +import javax.management.openmbean.SimpleType; + +/* + * @test + * @bug 8023669 + * @summary Test that hashCode()throws NullPointerException + * @author Shanliang JIANG + * @run clean MBeanInfoHashCodeNPETest + * @run build MBeanInfoHashCodeNPETest + * @run main MBeanInfoHashCodeNPETest + */ +public class MBeanInfoHashCodeNPETest { + private static int failed = 0; + + public static void main(String[] args) throws Exception { + System.out.println("---MBeanInfoHashCodeNPETest-main ..."); + + // ---- + System.out.println("\n---Testing on MBeanAttributeInfo..."); + MBeanAttributeInfo mbeanAttributeInfo = new MBeanAttributeInfo( + null, SimpleType.INTEGER.getClassName(), "description", true, true, false); + test(mbeanAttributeInfo, "class name"); + + mbeanAttributeInfo = new MBeanAttributeInfo( + "name", null, "description", true, true, false); + test(mbeanAttributeInfo, "type"); + + mbeanAttributeInfo = new MBeanAttributeInfo( + "name", SimpleType.INTEGER.getClassName(), null, true, true, false); + test(mbeanAttributeInfo, "description"); + + // ---- + System.out.println("\n---Testing on MBeanConstructorInfo..."); + MBeanConstructorInfo mbeanConstructorInfo = new MBeanConstructorInfo( + null, "", new MBeanParameterInfo[]{}, new DescriptorSupport()); + test(mbeanConstructorInfo, "name"); + + mbeanConstructorInfo = new MBeanConstructorInfo( + "", null, new MBeanParameterInfo[]{}, new DescriptorSupport()); + test(mbeanConstructorInfo, "description"); + + mbeanConstructorInfo = new MBeanConstructorInfo( + "", "", null, new DescriptorSupport()); + test(mbeanConstructorInfo, "MBeanParameterInfo"); + + mbeanConstructorInfo = new MBeanConstructorInfo( + "", "", new MBeanParameterInfo[]{}, null); + test(mbeanConstructorInfo, "descriptor"); + + // ---- + System.out.println("\n---Testing on MBeanOperationInfo..."); + MBeanOperationInfo mbeanOperationInfo = new MBeanOperationInfo( + null, "description", new MBeanParameterInfo[]{}, "type", 1, new DescriptorSupport()); + test(mbeanOperationInfo, "name"); + + mbeanOperationInfo = new MBeanOperationInfo( + "name", null, new MBeanParameterInfo[]{}, "type", 1, new DescriptorSupport()); + test(mbeanOperationInfo, "description"); + + mbeanOperationInfo = new MBeanOperationInfo( + "name", "description", null, "type", 1, new DescriptorSupport()); + test(mbeanOperationInfo, "MBeanParameterInfo"); + + mbeanOperationInfo = new MBeanOperationInfo( + "name", "description", new MBeanParameterInfo[]{}, null, 1, new DescriptorSupport()); + test(mbeanOperationInfo, "type"); + + mbeanOperationInfo = new MBeanOperationInfo( + "name", "description", new MBeanParameterInfo[]{}, "type", -1, new DescriptorSupport()); + test(mbeanOperationInfo, "native impact"); + + mbeanOperationInfo = new MBeanOperationInfo( + "name", "description", new MBeanParameterInfo[]{}, "type", 1, null); + test(mbeanOperationInfo, "Descriptor"); + + // ---- + System.out.println("\n---Testing on MBeanParameterInfo..."); + MBeanParameterInfo mbeanParameterInfo = new MBeanParameterInfo( + null, "type", "description", new DescriptorSupport()); + test(mbeanParameterInfo, "name"); + + mbeanParameterInfo = new MBeanParameterInfo( + "name", null, "description", new DescriptorSupport()); + test(mbeanParameterInfo, "description"); + + mbeanParameterInfo = new MBeanParameterInfo( + "name", "type", null, new DescriptorSupport()); + test(mbeanParameterInfo, "description"); + + mbeanParameterInfo = new MBeanParameterInfo( + "name", "type", "description", null); + test(mbeanParameterInfo, "Descriptor"); + + // ---- + System.out.println("\n---Testing on MBeanInfo..."); + String className = "toto"; + String description = "titi"; + MBeanAttributeInfo[] attrInfos = new MBeanAttributeInfo[]{}; + MBeanConstructorInfo[] constrInfos = new MBeanConstructorInfo[]{}; + MBeanOperationInfo[] operaInfos = new MBeanOperationInfo[]{}; + MBeanNotificationInfo[] notifInfos = new MBeanNotificationInfo[]{}; + + MBeanInfo minfo = new MBeanInfo(null, description, attrInfos, constrInfos, operaInfos, notifInfos); + test(minfo, "class name"); + + minfo = new MBeanInfo(className, description, attrInfos, constrInfos, operaInfos, notifInfos); + test(minfo, "name"); + + minfo = new MBeanInfo(className, null, attrInfos, constrInfos, operaInfos, notifInfos); + test(minfo, "description"); + + minfo = new MBeanInfo(className, description, null, constrInfos, operaInfos, notifInfos); + test(minfo, "attrInfos"); + + minfo = new MBeanInfo(className, description, attrInfos, constrInfos, null, notifInfos); + test(minfo, "operaInfos"); + + minfo = new MBeanInfo(className, description, attrInfos, constrInfos, operaInfos, null); + test(minfo, "notifInfos"); + + Thread.sleep(100); + if (failed > 0) { + throw new RuntimeException("Test failed: "+failed); + } else { + System.out.println("---Test: PASSED"); + } + } + + private static void test(Object obj, String param) { + try { + obj.hashCode(); + System.out.println("OK: "+obj.getClass().getSimpleName()+".hashCode worked with a null "+param); + } catch (NullPointerException npe) { + System.out.println("--->KO!!! "+obj.getClass().getSimpleName()+".hashCode got NPE with a null "+param); + failed++; + } + + try { + obj.toString(); + System.out.println("OK: "+obj.getClass().getSimpleName()+".toString worked with a null "+param); + } catch (NullPointerException npe) { + System.out.println("--->KO!!! "+obj.getClass().getSimpleName()+".toString got NPE."); + failed++; + } + } +} From 276b809ff474d3655f6b3d9786dd20fc4a47664f Mon Sep 17 00:00:00 2001 From: Brent Christian Date: Thu, 12 Sep 2013 14:22:53 -0700 Subject: [PATCH 60/75] 8024009: Remove jdk.map.useRandomSeed system property Removed usage of hashSeed in Hashtable & WeakHashMap, and removed tests Reviewed-by: alanb, mduigou --- .../share/classes/java/util/Hashtable.java | 104 +++--------------- .../share/classes/java/util/WeakHashMap.java | 39 +------ .../java/util/Map/CheckRandomHashSeed.java | 91 --------------- jdk/test/java/util/Map/Collisions.java | 2 - 4 files changed, 19 insertions(+), 217 deletions(-) delete mode 100644 jdk/test/java/util/Map/CheckRandomHashSeed.java diff --git a/jdk/src/share/classes/java/util/Hashtable.java b/jdk/src/share/classes/java/util/Hashtable.java index 518bd17f5b7..dc50e9393ee 100644 --- a/jdk/src/share/classes/java/util/Hashtable.java +++ b/jdk/src/share/classes/java/util/Hashtable.java @@ -168,68 +168,6 @@ public class Hashtable /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = 1421746759512286392L; - private static class Holder { - // Unsafe mechanics - /** - * - */ - static final sun.misc.Unsafe UNSAFE; - - /** - * Offset of "final" hashSeed field we must set in - * readObject() method. - */ - static final long HASHSEED_OFFSET; - - static final boolean USE_HASHSEED; - - static { - String hashSeedProp = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction( - "jdk.map.useRandomSeed")); - boolean localBool = (null != hashSeedProp) - ? Boolean.parseBoolean(hashSeedProp) : false; - USE_HASHSEED = localBool; - - if (USE_HASHSEED) { - try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - HASHSEED_OFFSET = UNSAFE.objectFieldOffset( - Hashtable.class.getDeclaredField("hashSeed")); - } catch (NoSuchFieldException | SecurityException e) { - throw new InternalError("Failed to record hashSeed offset", e); - } - } else { - UNSAFE = null; - HASHSEED_OFFSET = 0; - } - } - } - - /** - * A randomizing value associated with this instance that is applied to - * hash code of keys to make hash collisions harder to find. - * - * Non-final so it can be set lazily, but be sure not to set more than once. - */ - transient final int hashSeed; - - /** - * Return an initial value for the hashSeed, or 0 if the random seed is not - * enabled. - */ - final int initHashSeed() { - if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) { - int seed = ThreadLocalRandom.current().nextInt(); - return (seed != 0) ? seed : 1; - } - return 0; - } - - private int hash(Object k) { - return hashSeed ^ k.hashCode(); - } - /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. @@ -251,7 +189,6 @@ public class Hashtable this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); - hashSeed = initHashSeed(); } /** @@ -395,7 +332,7 @@ public class Hashtable */ public synchronized boolean containsKey(Object key) { Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { @@ -423,7 +360,7 @@ public class Hashtable @SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { @@ -488,7 +425,7 @@ public class Hashtable rehash(); tab = table; - hash = hash(key); + hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } @@ -524,7 +461,7 @@ public class Hashtable // Makes sure the key is not already in the hashtable. Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry entry = (Entry)tab[index]; @@ -551,7 +488,7 @@ public class Hashtable */ public synchronized V remove(Object key) { Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -760,7 +697,7 @@ public class Hashtable Map.Entry entry = (Map.Entry)o; Object key = entry.getKey(); Entry[] tab = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) @@ -775,7 +712,7 @@ public class Hashtable Map.Entry entry = (Map.Entry) o; Object key = entry.getKey(); Entry[] tab = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") @@ -975,7 +912,7 @@ public class Hashtable // Makes sure the key is not already in the hashtable. Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry entry = (Entry)tab[index]; @@ -998,7 +935,7 @@ public class Hashtable Objects.requireNonNull(value); Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1021,7 +958,7 @@ public class Hashtable @Override public synchronized boolean replace(K key, V oldValue, V newValue) { Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1041,7 +978,7 @@ public class Hashtable @Override public synchronized V replace(K key, V value) { Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1060,7 +997,7 @@ public class Hashtable Objects.requireNonNull(mappingFunction); Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1084,7 +1021,7 @@ public class Hashtable Objects.requireNonNull(remappingFunction); Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1113,7 +1050,7 @@ public class Hashtable Objects.requireNonNull(remappingFunction); Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1148,7 +1085,7 @@ public class Hashtable Objects.requireNonNull(remappingFunction); Entry tab[] = table; - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry e = (Entry)tab[index]; @@ -1228,13 +1165,6 @@ public class Hashtable // Read in the length, threshold, and loadfactor s.defaultReadObject(); - // set hashMask - if (Holder.USE_HASHSEED) { - int seed = ThreadLocalRandom.current().nextInt(); - Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, - (seed != 0) ? seed : 1); - } - // Read the original length of the array and number of elements int origlength = s.readInt(); int elements = s.readInt(); @@ -1282,7 +1212,7 @@ public class Hashtable } // Makes sure the key is not already in the hashtable. // This should not happen in deserialized version. - int hash = hash(key); + int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { @@ -1347,7 +1277,7 @@ public class Hashtable } public int hashCode() { - return (Objects.hashCode(key) ^ Objects.hashCode(value)); + return hash ^ Objects.hashCode(value); } public String toString() { diff --git a/jdk/src/share/classes/java/util/WeakHashMap.java b/jdk/src/share/classes/java/util/WeakHashMap.java index 0299d296638..81f74be8e9e 100644 --- a/jdk/src/share/classes/java/util/WeakHashMap.java +++ b/jdk/src/share/classes/java/util/WeakHashMap.java @@ -190,39 +190,6 @@ public class WeakHashMap */ int modCount; - private static class Holder { - static final boolean USE_HASHSEED; - - static { - String hashSeedProp = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction( - "jdk.map.useRandomSeed")); - boolean localBool = (null != hashSeedProp) - ? Boolean.parseBoolean(hashSeedProp) : false; - USE_HASHSEED = localBool; - } - } - - /** - * A randomizing value associated with this instance that is applied to - * hash code of keys to make hash collisions harder to find. - * - * Non-final so it can be set lazily, but be sure not to set more than once. - */ - transient int hashSeed; - - /** - * Initialize the hashing mask value. - */ - final void initHashSeed() { - if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) { - // Do not set hashSeed more than once! - // assert hashSeed == 0; - int seed = ThreadLocalRandom.current().nextInt(); - hashSeed = (seed != 0) ? seed : 1; - } - } - @SuppressWarnings("unchecked") private Entry[] newTable(int n) { return (Entry[]) new Entry[n]; @@ -253,7 +220,6 @@ public class WeakHashMap table = newTable(capacity); this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); - initHashSeed(); } /** @@ -329,7 +295,7 @@ public class WeakHashMap * in lower bits. */ final int hash(Object k) { - int h = hashSeed ^ k.hashCode(); + int h = k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded @@ -783,8 +749,7 @@ public class WeakHashMap public int hashCode() { K k = getKey(); V v = getValue(); - return ((k==null ? 0 : k.hashCode()) ^ - (v==null ? 0 : v.hashCode())); + return Objects.hashCode(k) ^ Objects.hashCode(v); } public String toString() { diff --git a/jdk/test/java/util/Map/CheckRandomHashSeed.java b/jdk/test/java/util/Map/CheckRandomHashSeed.java deleted file mode 100644 index 2acf1bc8c9c..00000000000 --- a/jdk/test/java/util/Map/CheckRandomHashSeed.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 8005698 - * @summary Check operation of jdk.map.useRandomSeed property - * @run main CheckRandomHashSeed - * @run main/othervm -Djdk.map.useRandomSeed=false CheckRandomHashSeed - * @run main/othervm -Djdk.map.useRandomSeed=bogus CheckRandomHashSeed - * @run main/othervm -Djdk.map.useRandomSeed=true CheckRandomHashSeed true - * @author Brent Christian - */ -import java.lang.reflect.Field; -import java.util.Map; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Hashtable; -import java.util.WeakHashMap; - -public class CheckRandomHashSeed { - private final static String PROP_NAME = "jdk.map.useRandomSeed"; - static boolean expectRandom = false; - - public static void main(String[] args) { - if (args.length > 0 && args[0].equals("true")) { - expectRandom = true; - } - String hashSeedProp = System.getProperty(PROP_NAME); - boolean propSet = (null != hashSeedProp) - ? Boolean.parseBoolean(hashSeedProp) : false; - if (expectRandom != propSet) { - throw new Error("Error in test setup: " + (expectRandom ? "" : "not " ) + "expecting random hashSeed, but " + PROP_NAME + " is " + (propSet ? "" : "not ") + "enabled"); - } - - testMap(new WeakHashMap()); - testMap(new Hashtable()); - } - - private static void testMap(Map map) { - int hashSeed = getHashSeed(map); - boolean hashSeedIsZero = (hashSeed == 0); - - if (expectRandom != hashSeedIsZero) { - System.out.println("Test passed for " + map.getClass().getSimpleName() + " - expectRandom: " + expectRandom + ", hashSeed: " + hashSeed); - } else { - throw new Error ("Test FAILED for " + map.getClass().getSimpleName() + " - expectRandom: " + expectRandom + ", hashSeed: " + hashSeed); - } - } - - private static int getHashSeed(Map map) { - try { - if (map instanceof HashMap || map instanceof LinkedHashMap) { - map.put("Key", "Value"); - Field hashSeedField = HashMap.class.getDeclaredField("hashSeed"); - hashSeedField.setAccessible(true); - int hashSeed = hashSeedField.getInt(map); - return hashSeed; - } else { - map.put("Key", "Value"); - Field hashSeedField = map.getClass().getDeclaredField("hashSeed"); - hashSeedField.setAccessible(true); - int hashSeed = hashSeedField.getInt(map); - return hashSeed; - } - } catch(Exception e) { - e.printStackTrace(); - throw new Error(e); - } - } -} diff --git a/jdk/test/java/util/Map/Collisions.java b/jdk/test/java/util/Map/Collisions.java index b7170791777..05e9e1f95e0 100644 --- a/jdk/test/java/util/Map/Collisions.java +++ b/jdk/test/java/util/Map/Collisions.java @@ -25,8 +25,6 @@ * @test * @bug 7126277 * @run main Collisions -shortrun - * @run main/othervm -Djdk.map.althashing.threshold=0 Collisions -shortrun - * @run main/othervm -Djdk.map.useRandomSeed=true Collisions -shortrun * @summary Ensure Maps behave well with lots of hashCode() collisions. * @author Mike Duigou */ From 767ab8c9ae5c20f2be376b3b49768d000d37d8ae Mon Sep 17 00:00:00 2001 From: Mark Sheppard Date: Fri, 13 Sep 2013 12:20:53 +0100 Subject: [PATCH 61/75] 8024675: java/net/NetworkInterface/UniqueMacAddressesTest.java fails on Windows Amended test to add active, i.e. isUp(), NetworkInterfaces to test list Reviewed-by: alanb, chegar --- .../NetworkInterface/UniqueMacAddressesTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java b/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java index c2f5c495c73..4017c0702ed 100644 --- a/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java +++ b/jdk/test/java/net/NetworkInterface/UniqueMacAddressesTest.java @@ -118,11 +118,14 @@ public class UniqueMacAddressesTest { NetworkInterface netIf = null; while (nis.hasMoreElements()) { netIf = (NetworkInterface) nis.nextElement(); - macAddr = netIf.getHardwareAddress(); - if (macAddr != null) { - System.out - .println("Adding NetworkInterface " + netIf.getName()); - networkInterfaceList.add(netIf); + if (netIf.isUp()) { + macAddr = netIf.getHardwareAddress(); + if (macAddr != null) { + System.out.println("Adding NetworkInterface " + + netIf.getName() + " with mac address " + + createMacAddressString(netIf)); + networkInterfaceList.add(netIf); + } } } } From b59dc6762ed4fd9fa1d9285cdf8d1bc6e9277fa1 Mon Sep 17 00:00:00 2001 From: Mike Duigou Date: Fri, 13 Sep 2013 11:18:44 -0700 Subject: [PATCH 62/75] 8021591: Additional explicit null checks Reviewed-by: psandoz, alanb --- .../share/classes/java/util/Collections.java | 1 + .../share/classes/java/util/Hashtable.java | 3 + .../classes/java/util/IdentityHashMap.java | 2 + jdk/src/share/classes/java/util/Map.java | 9 +- jdk/src/share/classes/java/util/TreeMap.java | 2 +- .../util/concurrent/ConcurrentHashMap.java | 3 + .../classes/javax/security/auth/Subject.java | 4 +- .../util/Collection/CollectionDefaults.java | 108 ++++-- jdk/test/java/util/Collection/MOAT.java | 2 - .../testlibrary/CollectionAsserts.java | 58 ++-- .../testlibrary/CollectionSupplier.java | 309 +++++++----------- .../ExtendsAbstractCollection.java | 86 +++++ .../testlibrary/ExtendsAbstractList.java | 101 ++++++ .../testlibrary/ExtendsAbstractSet.java | 85 +++++ .../{Collection => List}/ListDefaults.java | 95 +++--- jdk/test/java/util/Map/Defaults.java | 46 ++- 16 files changed, 604 insertions(+), 310 deletions(-) create mode 100644 jdk/test/java/util/Collection/testlibrary/ExtendsAbstractCollection.java create mode 100644 jdk/test/java/util/Collection/testlibrary/ExtendsAbstractList.java create mode 100644 jdk/test/java/util/Collection/testlibrary/ExtendsAbstractSet.java rename jdk/test/java/util/{Collection => List}/ListDefaults.java (88%) diff --git a/jdk/src/share/classes/java/util/Collections.java b/jdk/src/share/classes/java/util/Collections.java index 2404b4fc9ad..947b6282db4 100644 --- a/jdk/src/share/classes/java/util/Collections.java +++ b/jdk/src/share/classes/java/util/Collections.java @@ -3900,6 +3900,7 @@ public class Collections { return batchRemove(c, true); } private boolean batchRemove(Collection c, boolean complement) { + Objects.requireNonNull(c); boolean modified = false; Iterator> it = iterator(); while (it.hasNext()) { diff --git a/jdk/src/share/classes/java/util/Hashtable.java b/jdk/src/share/classes/java/util/Hashtable.java index dc50e9393ee..b97a8e361bb 100644 --- a/jdk/src/share/classes/java/util/Hashtable.java +++ b/jdk/src/share/classes/java/util/Hashtable.java @@ -957,6 +957,8 @@ public class Hashtable @Override public synchronized boolean replace(K key, V oldValue, V newValue) { + Objects.requireNonNull(oldValue); + Objects.requireNonNull(newValue); Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @@ -977,6 +979,7 @@ public class Hashtable @Override public synchronized V replace(K key, V value) { + Objects.requireNonNull(value); Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; diff --git a/jdk/src/share/classes/java/util/IdentityHashMap.java b/jdk/src/share/classes/java/util/IdentityHashMap.java index a4bdc4b9efd..40808213894 100644 --- a/jdk/src/share/classes/java/util/IdentityHashMap.java +++ b/jdk/src/share/classes/java/util/IdentityHashMap.java @@ -997,6 +997,7 @@ public class IdentityHashMap * behavior when c is a smaller "normal" (non-identity-based) Set. */ public boolean removeAll(Collection c) { + Objects.requireNonNull(c); boolean modified = false; for (Iterator i = iterator(); i.hasNext(); ) { if (c.contains(i.next())) { @@ -1212,6 +1213,7 @@ public class IdentityHashMap * behavior when c is a smaller "normal" (non-identity-based) Set. */ public boolean removeAll(Collection c) { + Objects.requireNonNull(c); boolean modified = false; for (Iterator> i = iterator(); i.hasNext(); ) { if (c.contains(i.next())) { diff --git a/jdk/src/share/classes/java/util/Map.java b/jdk/src/share/classes/java/util/Map.java index 321233e6f8d..4340e6d9c8f 100644 --- a/jdk/src/share/classes/java/util/Map.java +++ b/jdk/src/share/classes/java/util/Map.java @@ -805,6 +805,10 @@ public interface Map { * return false; * }
    * + * The default implementation does not throw NullPointerException + * for maps that do not support null values if oldValue is null unless + * newValue is also null. + * * @param key key with which the specified value is associated * @param oldValue value expected to be associated with the specified key * @param newValue value to be associated with the specified key @@ -814,8 +818,11 @@ public interface Map { * (optional) * @throws ClassCastException if the class of a specified key or value * prevents it from being stored in this map - * @throws NullPointerException if a specified key or value is null, + * @throws NullPointerException if a specified key or newValue is null, * and this map does not permit null keys or values + * @throws NullPointerException if oldValue is null and this map does not + * permit null values + * (optional) * @throws IllegalArgumentException if some property of a specified key * or value prevents it from being stored in this map * @since 1.8 diff --git a/jdk/src/share/classes/java/util/TreeMap.java b/jdk/src/share/classes/java/util/TreeMap.java index 9a4681d771a..740456b7ecb 100644 --- a/jdk/src/share/classes/java/util/TreeMap.java +++ b/jdk/src/share/classes/java/util/TreeMap.java @@ -1012,7 +1012,7 @@ public class TreeMap int expectedModCount = modCount; for (Entry e = getFirstEntry(); e != null; e = successor(e)) { - e.value = Objects.requireNonNull(function.apply(e.key, e.value)); + e.value = function.apply(e.key, e.value); if (expectedModCount != modCount) { throw new ConcurrentModificationException(); diff --git a/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java b/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java index 1936e244946..9476bc411ba 100644 --- a/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -49,6 +49,7 @@ import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.Spliterator; import java.util.concurrent.ConcurrentMap; @@ -4410,6 +4411,7 @@ public class ConcurrentHashMap extends AbstractMap } public final boolean removeAll(Collection c) { + Objects.requireNonNull(c); boolean modified = false; for (Iterator it = iterator(); it.hasNext();) { if (c.contains(it.next())) { @@ -4421,6 +4423,7 @@ public class ConcurrentHashMap extends AbstractMap } public final boolean retainAll(Collection c) { + Objects.requireNonNull(c); boolean modified = false; for (Iterator it = iterator(); it.hasNext();) { if (!c.contains(it.next())) { diff --git a/jdk/src/share/classes/javax/security/auth/Subject.java b/jdk/src/share/classes/javax/security/auth/Subject.java index d5e4240a40a..ba6bda71f66 100644 --- a/jdk/src/share/classes/javax/security/auth/Subject.java +++ b/jdk/src/share/classes/javax/security/auth/Subject.java @@ -1186,7 +1186,7 @@ public final class Subject implements java.io.Serializable { } public boolean removeAll(Collection c) { - + Objects.requireNonNull(c); boolean modified = false; final Iterator e = iterator(); while (e.hasNext()) { @@ -1222,7 +1222,7 @@ public final class Subject implements java.io.Serializable { } public boolean retainAll(Collection c) { - + Objects.requireNonNull(c); boolean modified = false; boolean retain = false; final Iterator e = iterator(); diff --git a/jdk/test/java/util/Collection/CollectionDefaults.java b/jdk/test/java/util/Collection/CollectionDefaults.java index 36fd8da5469..594b67adae8 100644 --- a/jdk/test/java/util/Collection/CollectionDefaults.java +++ b/jdk/test/java/util/Collection/CollectionDefaults.java @@ -21,15 +21,19 @@ * questions. */ +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.SortedSet; + import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -38,43 +42,68 @@ import static org.testng.Assert.fail; import java.util.TreeMap; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.function.Predicate; +import java.util.function.Supplier; /** * @test - * @library testlibrary - * @build CollectionAsserts CollectionSupplier - * @run testng CollectionDefaults * @summary Unit tests for extension methods on Collection + * @library testlibrary + * @build CollectionAsserts CollectionSupplier ExtendsAbstractSet ExtendsAbstractCollection + * @run testng CollectionDefaults */ public class CollectionDefaults { public static final Predicate pEven = x -> 0 == x % 2; public static final Predicate pOdd = x -> 1 == x % 2; - private static final String[] SET_CLASSES = { - "java.util.HashSet", - "java.util.LinkedHashSet", - "java.util.TreeSet" + @SuppressWarnings("unchecked") + private static final Supplier[] TEST_CLASSES = { + // Collection + ExtendsAbstractCollection::new, + + // Lists + java.util.ArrayList::new, + java.util.LinkedList::new, + java.util.Vector::new, + java.util.concurrent.CopyOnWriteArrayList::new, + ExtendsAbstractList::new, + + // Sets + java.util.HashSet::new, + java.util.LinkedHashSet::new, + java.util.TreeSet::new, + java.util.concurrent.ConcurrentSkipListSet::new, + java.util.concurrent.CopyOnWriteArraySet::new, + ExtendsAbstractSet::new }; private static final int SIZE = 100; @DataProvider(name="setProvider", parallel=true) - public static Object[][] setCases() { + public static Iterator setCases() { final List cases = new LinkedList<>(); cases.add(new Object[] { new HashSet<>() }); cases.add(new Object[] { new LinkedHashSet<>() }); cases.add(new Object[] { new TreeSet<>() }); + cases.add(new Object[] { new java.util.concurrent.ConcurrentSkipListSet<>() }); + cases.add(new Object[] { new java.util.concurrent.CopyOnWriteArraySet<>() }); + + cases.add(new Object[] { new ExtendsAbstractSet<>() }); cases.add(new Object[] { Collections.newSetFromMap(new HashMap<>()) }); cases.add(new Object[] { Collections.newSetFromMap(new LinkedHashMap()) }); cases.add(new Object[] { Collections.newSetFromMap(new TreeMap<>()) }); + cases.add(new Object[] { Collections.newSetFromMap(new ConcurrentHashMap<>()) }); + cases.add(new Object[] { Collections.newSetFromMap(new ConcurrentSkipListMap<>()) }); - cases.add(new Object[] { new HashSet(){{add(42);}} }); - cases.add(new Object[] { new LinkedHashSet(){{add(42);}} }); - cases.add(new Object[] { new TreeSet(){{add(42);}} }); - return cases.toArray(new Object[0][cases.size()]); + cases.add(new Object[] { new HashSet(){{add(42);}} }); + cases.add(new Object[] { new ExtendsAbstractSet(){{add(42);}} }); + cases.add(new Object[] { new LinkedHashSet(){{add(42);}} }); + cases.add(new Object[] { new TreeSet(){{add(42);}} }); + return cases.iterator(); } @Test(dataProvider = "setProvider") @@ -82,57 +111,66 @@ public class CollectionDefaults { try { set.forEach(null); fail("expected NPE not thrown"); - } catch (NullPointerException npe) {} + } catch (NullPointerException expected) { + ; // expected + } try { set.removeIf(null); fail("expected NPE not thrown"); - } catch (NullPointerException npe) {} + } catch (NullPointerException expected) { + ; // expected + } } @Test public void testForEach() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(SET_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { - final Set original = ((Set) test.original); - final Set set = ((Set) test.collection); + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[]) TEST_CLASSES, SIZE); + + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final Collection original = test.expected; + final Collection set = test.collection; try { set.forEach(null); fail("expected NPE not thrown"); - } catch (NullPointerException npe) {} - if (test.className.equals("java.util.HashSet")) { - CollectionAsserts.assertContentsUnordered(set, original); + } catch (NullPointerException expected) { + ; // expected + } + if (set instanceof Set && !((set instanceof SortedSet) || (set instanceof LinkedHashSet))) { + CollectionAsserts.assertContentsUnordered(set, original, test.toString()); } else { - CollectionAsserts.assertContents(set, original); + CollectionAsserts.assertContents(set, original, test.toString()); } final List actual = new LinkedList<>(); set.forEach(actual::add); - if (test.className.equals("java.util.HashSet")) { - CollectionAsserts.assertContentsUnordered(actual, set); - CollectionAsserts.assertContentsUnordered(actual, original); + if (set instanceof Set && !((set instanceof SortedSet) || (set instanceof LinkedHashSet))) { + CollectionAsserts.assertContentsUnordered(actual, set, test.toString()); + CollectionAsserts.assertContentsUnordered(actual, original, test.toString()); } else { - CollectionAsserts.assertContents(actual, set); - CollectionAsserts.assertContents(actual, original); + CollectionAsserts.assertContents(actual, set, test.toString()); + CollectionAsserts.assertContents(actual, original, test.toString()); } } } @Test public void testRemoveIf() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(SET_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { - final Set original = ((Set) test.original); - final Set set = ((Set) test.collection); + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[]) TEST_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final Collection original = test.expected; + final Collection set = test.collection; try { set.removeIf(null); fail("expected NPE not thrown"); - } catch (NullPointerException npe) {} - if (test.className.equals("java.util.HashSet")) { - CollectionAsserts.assertContentsUnordered(set, original); + } catch (NullPointerException expected) { + ; // expected + } + if (set instanceof Set && !((set instanceof SortedSet) || (set instanceof LinkedHashSet))) { + CollectionAsserts.assertContentsUnordered(set, original, test.toString()); } else { - CollectionAsserts.assertContents(set, original); + CollectionAsserts.assertContents(set, original, test.toString()); } set.removeIf(pEven); diff --git a/jdk/test/java/util/Collection/MOAT.java b/jdk/test/java/util/Collection/MOAT.java index 37720016c3c..a039461e2b7 100644 --- a/jdk/test/java/util/Collection/MOAT.java +++ b/jdk/test/java/util/Collection/MOAT.java @@ -400,8 +400,6 @@ public class MOAT { // If add(null) succeeds, contains(null) & remove(null) should succeed //---------------------------------------------------------------- private static void testNullElement(Collection c) { - // !!!! 5018849: (coll) TreeSet.contains(null) does not agree with Javadoc - if (c instanceof TreeSet) return; try { check(c.add(null)); diff --git a/jdk/test/java/util/Collection/testlibrary/CollectionAsserts.java b/jdk/test/java/util/Collection/testlibrary/CollectionAsserts.java index a03f975152e..575dc796204 100644 --- a/jdk/test/java/util/Collection/testlibrary/CollectionAsserts.java +++ b/jdk/test/java/util/Collection/testlibrary/CollectionAsserts.java @@ -41,6 +41,10 @@ import static org.testng.Assert.fail; */ public class CollectionAsserts { + private CollectionAsserts() { + // no instances + } + public static void assertCountSum(Iterable it, int count, int sum) { assertCountSum(it.iterator(), count, sum); } @@ -117,10 +121,18 @@ public class CollectionAsserts { } public static void assertContents(Iterable actual, Iterable expected) { - assertContents(actual.iterator(), expected.iterator()); + assertContents(actual, expected, null); + } + + public static void assertContents(Iterable actual, Iterable expected, String msg) { + assertContents(actual.iterator(), expected.iterator(), msg); } public static void assertContents(Iterator actual, Iterator expected) { + assertContents(actual, expected, null); + } + + public static void assertContents(Iterator actual, Iterator expected, String msg) { List history = new ArrayList<>(); while (expected.hasNext()) { @@ -128,20 +140,23 @@ public class CollectionAsserts { List expectedData = new ArrayList<>(history); while (expected.hasNext()) expectedData.add(expected.next()); - fail(String.format("Premature end of data; expected=%s, found=%s", expectedData, history)); + fail(String.format("%s Premature end of data; expected=%s, found=%s", + (msg == null ? "" : msg), expectedData, history)); } T a = actual.next(); T e = expected.next(); history.add(a); if (!Objects.equals(a, e)) - fail(String.format("Data mismatch; preceding=%s, nextExpected=%s, nextFound=%s", history, e, a)); + fail(String.format("%s Data mismatch; preceding=%s, nextExpected=%s, nextFound=%s", + (msg == null ? "" : msg), history, e, a)); } if (actual.hasNext()) { List rest = new ArrayList<>(); while (actual.hasNext()) rest.add(actual.next()); - fail(String.format("Unexpected data %s after %s", rest, history)); + fail(String.format("%s Unexpected data %s after %s", + (msg == null ? "" : msg), rest, history)); } } @@ -151,30 +166,21 @@ public class CollectionAsserts { assertContents(actual, Arrays.asList(expected).iterator()); } - public static boolean equalsContentsUnordered(Iterable a, Iterable b) { - Set sa = new HashSet<>(); - for (T t : a) { - sa.add(t); - } - - Set sb = new HashSet<>(); - for (T t : b) { - sb.add(t); - } - - return Objects.equals(sa, sb); + public static> void assertContentsUnordered(Iterable actual, Iterable expected) { + assertContentsUnordered(actual, expected, null); } - public static> void assertContentsUnordered(Iterable actual, Iterable expected) { - ArrayList one = new ArrayList<>(); - for (T t : actual) - one.add(t); - ArrayList two = new ArrayList<>(); - for (T t : expected) - two.add(t); - Collections.sort(one); - Collections.sort(two); - assertContents(one, two); + public static> void assertContentsUnordered(Iterable actual, Iterable expected, String msg) { + List allExpected = new ArrayList<>(); + for (T t : expected) { + allExpected.add(t); + } + + for (T t : actual) { + assertTrue(allExpected.remove(t), msg + " element '" + String.valueOf(t) + "' not found"); + } + + assertTrue(allExpected.isEmpty(), msg + "expected contained additional elements"); } static void assertSplitContents(Iterable> splits, Iterable list) { diff --git a/jdk/test/java/util/Collection/testlibrary/CollectionSupplier.java b/jdk/test/java/util/Collection/testlibrary/CollectionSupplier.java index c26aaa7385f..37b3e53739c 100644 --- a/jdk/test/java/util/Collection/testlibrary/CollectionSupplier.java +++ b/jdk/test/java/util/Collection/testlibrary/CollectionSupplier.java @@ -29,13 +29,11 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Random; -import java.util.Set; import org.testng.TestException; import static org.testng.Assert.assertTrue; -import java.lang.reflect.Constructor; import java.util.Collection; import java.util.Collections; import java.util.function.Supplier; @@ -44,73 +42,61 @@ import java.util.function.Supplier; * @library * @summary A Supplier of test cases for Collection tests */ -public final class CollectionSupplier implements Supplier> { +public final class CollectionSupplier> implements Supplier>> { - private final String[] classNames; + private final Supplier[] classes; private final int size; /** * A Collection test case. */ - public static final class TestCase { + public static final class TestCase> { /** * The name of the test case. */ public final String name; - /** - * Class name of the instantiated Collection. - */ - public final String className; - /** * Unmodifiable reference collection, useful for comparisons. */ - public final Collection original; + public final List expected; /** * A modifiable test collection. */ - public final Collection collection; + public final C collection; /** * Create a Collection test case. + * * @param name name of the test case - * @param className class name of the instantiated collection - * @param original reference collection + * @param expected reference collection * @param collection the modifiable test collection */ - public TestCase(String name, String className, - Collection original, Collection collection) { + public TestCase(String name, C collection) { this.name = name; - this.className = className; - this.original = - List.class.isAssignableFrom(original.getClass()) ? - Collections.unmodifiableList((List) original) : - Set.class.isAssignableFrom(original.getClass()) ? - Collections.unmodifiableSet((Set) original) : - Collections.unmodifiableCollection(original); + this.expected = Collections.unmodifiableList( + Arrays.asList(collection.toArray(new Integer[0]))); this.collection = collection; } @Override public String toString() { - return name + " " + className + - "\n original: " + original + - "\n target: " + collection; + return name + " " + collection.getClass().toString(); } } /** * Shuffle a list using a PRNG with known seed for repeatability + * * @param list the list to be shuffled */ public static void shuffle(final List list) { // PRNG with known seed for repeatable tests final Random prng = new Random(13); final int size = list.size(); - for (int i=0; i < size; i++) { + for (int i = 0; i < size; i++) { // random index in interval [i, size) final int j = i + prng.nextInt(size - i); // swap elements at indices i & j @@ -127,178 +113,133 @@ public final class CollectionSupplier implements Supplier[] classes, int size) { + this.classes = Arrays.copyOf(classes, classes.length); this.size = size; } @Override - public Iterable get() { - try { - return getThrows(); - } catch (Exception e) { - throw new TestException(e); - } - } + public Iterable> get() { + final Collection> cases = new LinkedList<>(); + for (final Supplier type : classes) { + try { + final Collection empty = type.get(); + cases.add(new TestCase("empty", empty)); - private Iterable getThrows() throws Exception { - final Collection collections = new LinkedList<>(); - for (final String className : classNames) { - @SuppressWarnings("unchecked") - final Class> type = - (Class>) Class.forName(className); - final Constructor> - defaultConstructor = type.getConstructor(); - final Constructor> - copyConstructor = type.getConstructor(Collection.class); + final Collection single = type.get(); + single.add(42); + cases.add(new TestCase("single", single)); - final Collection empty = defaultConstructor.newInstance(); - collections.add(new TestCase("empty", - className, - copyConstructor.newInstance(empty), - empty)); - - final Collection single = defaultConstructor.newInstance(); - single.add(42); - collections.add(new TestCase("single", - className, - copyConstructor.newInstance(single), - single)); - - final Collection regular = defaultConstructor.newInstance(); - for (int i=0; i < size; i++) { - regular.add(i); - } - collections.add(new TestCase("regular", - className, - copyConstructor.newInstance(regular), - regular)); - - final Collection reverse = defaultConstructor.newInstance(); - for (int i=size; i >= 0; i--) { - reverse.add(i); - } - collections.add(new TestCase("reverse", - className, - copyConstructor.newInstance(reverse), - reverse)); - - final Collection odds = defaultConstructor.newInstance(); - for (int i=0; i < size; i++) { - odds.add((i * 2) + 1); - } - collections.add(new TestCase("odds", - className, - copyConstructor.newInstance(odds), - odds)); - - final Collection evens = defaultConstructor.newInstance(); - for (int i=0; i < size; i++) { - evens.add(i * 2); - } - collections.add(new TestCase("evens", - className, - copyConstructor.newInstance(evens), - evens)); - - final Collection fibonacci = defaultConstructor.newInstance(); - int prev2 = 0; - int prev1 = 1; - for (int i=0; i < size; i++) { - final int n = prev1 + prev2; - if (n < 0) { // stop on overflow - break; + final Collection regular = type.get(); + for (int i = 0; i < size; i++) { + regular.add(i); } - fibonacci.add(n); - prev2 = prev1; - prev1 = n; - } - collections.add(new TestCase("fibonacci", - className, - copyConstructor.newInstance(fibonacci), - fibonacci)); + cases.add(new TestCase("regular", regular)); + + final Collection reverse = type.get(); + for (int i = size; i >= 0; i--) { + reverse.add(i); + } + cases.add(new TestCase("reverse", reverse)); + + final Collection odds = type.get(); + for (int i = 0; i < size; i++) { + odds.add((i * 2) + 1); + } + cases.add(new TestCase("odds", odds)); + + final Collection evens = type.get(); + for (int i = 0; i < size; i++) { + evens.add(i * 2); + } + cases.add(new TestCase("evens", evens)); + + final Collection fibonacci = type.get(); + int prev2 = 0; + int prev1 = 1; + for (int i = 0; i < size; i++) { + final int n = prev1 + prev2; + if (n < 0) { // stop on overflow + break; + } + fibonacci.add(n); + prev2 = prev1; + prev1 = n; + } + cases.add(new TestCase("fibonacci", fibonacci)); // variants where the size of the backing storage != reported size - // created by removing half of the elements + // created by removing half of the elements + final Collection emptyWithSlack = type.get(); + emptyWithSlack.add(42); + assertTrue(emptyWithSlack.remove(42)); + cases.add(new TestCase("emptyWithSlack", emptyWithSlack)); - final Collection emptyWithSlack = defaultConstructor.newInstance(); - emptyWithSlack.add(42); - assertTrue(emptyWithSlack.remove(42)); - collections.add(new TestCase("emptyWithSlack", - className, - copyConstructor.newInstance(emptyWithSlack), - emptyWithSlack)); + final Collection singleWithSlack = type.get(); + singleWithSlack.add(42); + singleWithSlack.add(43); + assertTrue(singleWithSlack.remove(43)); + cases.add(new TestCase("singleWithSlack", singleWithSlack)); - final Collection singleWithSlack = defaultConstructor.newInstance(); - singleWithSlack.add(42); - singleWithSlack.add(43); - assertTrue(singleWithSlack.remove(43)); - collections.add(new TestCase("singleWithSlack", - className, - copyConstructor.newInstance(singleWithSlack), - singleWithSlack)); - - final Collection regularWithSlack = defaultConstructor.newInstance(); - for (int i=0; i < (2 * size); i++) { - regularWithSlack.add(i); - } - assertTrue(regularWithSlack.removeIf((x) -> {return x >= size;})); - collections.add(new TestCase("regularWithSlack", - className, - copyConstructor.newInstance(regularWithSlack), - regularWithSlack)); - - final Collection reverseWithSlack = defaultConstructor.newInstance(); - for (int i=2 * size; i >= 0; i--) { - reverseWithSlack.add(i); - } - assertTrue(reverseWithSlack.removeIf((x) -> {return x < size;})); - collections.add(new TestCase("reverseWithSlack", - className, - copyConstructor.newInstance(reverseWithSlack), - reverseWithSlack)); - - final Collection oddsWithSlack = defaultConstructor.newInstance(); - for (int i = 0; i < 2 * size; i++) { - oddsWithSlack.add((i * 2) + 1); - } - assertTrue(oddsWithSlack.removeIf((x) -> {return x >= size;})); - collections.add(new TestCase("oddsWithSlack", - className, - copyConstructor.newInstance(oddsWithSlack), - oddsWithSlack)); - - final Collection evensWithSlack = defaultConstructor.newInstance(); - for (int i = 0; i < 2 * size; i++) { - evensWithSlack.add(i * 2); - } - assertTrue(evensWithSlack.removeIf((x) -> {return x >= size;})); - collections.add(new TestCase("evensWithSlack", - className, - copyConstructor.newInstance(evensWithSlack), - evensWithSlack)); - - final Collection fibonacciWithSlack = defaultConstructor.newInstance(); - prev2 = 0; - prev1 = 1; - for (int i=0; i < size; i++) { - final int n = prev1 + prev2; - if (n < 0) { // stop on overflow - break; + final Collection regularWithSlack = type.get(); + for (int i = 0; i < (2 * size); i++) { + regularWithSlack.add(i); } - fibonacciWithSlack.add(n); - prev2 = prev1; - prev1 = n; - } - assertTrue(fibonacciWithSlack.removeIf((x) -> {return x < 20;})); - collections.add(new TestCase("fibonacciWithSlack", - className, - copyConstructor.newInstance(fibonacciWithSlack), - fibonacciWithSlack)); + assertTrue(regularWithSlack.removeIf((x) -> { + return x >= size; + })); + cases.add(new TestCase("regularWithSlack", regularWithSlack)); + final Collection reverseWithSlack = type.get(); + for (int i = 2 * size; i >= 0; i--) { + reverseWithSlack.add(i); + } + assertTrue(reverseWithSlack.removeIf((x) -> { + return x < size; + })); + cases.add(new TestCase("reverseWithSlack", reverseWithSlack)); + + final Collection oddsWithSlack = type.get(); + for (int i = 0; i < 2 * size; i++) { + oddsWithSlack.add((i * 2) + 1); + } + assertTrue(oddsWithSlack.removeIf((x) -> { + return x >= size; + })); + cases.add(new TestCase("oddsWithSlack", oddsWithSlack)); + + final Collection evensWithSlack = type.get(); + for (int i = 0; i < 2 * size; i++) { + evensWithSlack.add(i * 2); + } + assertTrue(evensWithSlack.removeIf((x) -> { + return x >= size; + })); + cases.add(new TestCase("evensWithSlack", evensWithSlack)); + + final Collection fibonacciWithSlack = type.get(); + prev2 = 0; + prev1 = 1; + for (int i = 0; i < size; i++) { + final int n = prev1 + prev2; + if (n < 0) { // stop on overflow + break; + } + fibonacciWithSlack.add(n); + prev2 = prev1; + prev1 = n; + } + assertTrue(fibonacciWithSlack.removeIf((x) -> { + return x < 20; + })); + cases.add(new TestCase("fibonacciWithSlack", + fibonacciWithSlack)); + } catch (Exception failed) { + throw new TestException(failed); + } } - return collections; + return cases; } } diff --git a/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractCollection.java b/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractCollection.java new file mode 100644 index 00000000000..a22d3d75d15 --- /dev/null +++ b/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractCollection.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.util.AbstractCollection; +import java.util.HashSet; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.function.Supplier; + +/** + * @library + * + * A simple mutable collection implementation that provides only default + * implementations of all methods. ie. none of the Collection interface default + * methods have overridden implementations. + * + * @param type of collection elements + */ +public class ExtendsAbstractCollection extends AbstractCollection { + + protected final Collection coll; + + public ExtendsAbstractCollection() { + this(ArrayList::new); + } + + public ExtendsAbstractCollection(Collection source) { + this(); + coll.addAll(source); + } + + protected ExtendsAbstractCollection(Supplier> backer) { + this.coll = backer.get(); + } + + public boolean add(E element) { + return coll.add(element); + } + + public boolean remove(Object element) { + return coll.remove(element); + } + + public Iterator iterator() { + return new Iterator() { + Iterator source = coll.iterator(); + + public boolean hasNext() { + return source.hasNext(); + } + + public E next() { + return source.next(); + } + + public void remove() { + source.remove(); + } + }; + } + + public int size() { + return coll.size(); + } +} diff --git a/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractList.java b/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractList.java new file mode 100644 index 00000000000..fb2af523d44 --- /dev/null +++ b/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractList.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.util.ArrayList; +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +/** + * @library + * + * A simple mutable list implementation that provides only default + * implementations of all methods. ie. none of the List interface default + * methods have overridden implementations. + * + * @param type of list elements + */ +public class ExtendsAbstractList extends AbstractList { + + protected final List list; + + public ExtendsAbstractList() { + this(ArrayList::new); + } + + protected ExtendsAbstractList(Supplier> supplier) { + this.list = supplier.get(); + } + + public ExtendsAbstractList(Collection source) { + this(); + addAll(source); + } + + public boolean add(E element) { + return list.add(element); + } + + public E get(int index) { + return list.get(index); + } + + public boolean remove(Object element) { + return list.remove(element); + } + + public E set(int index, E element) { + return list.set(index, element); + } + + public void add(int index, E element) { + list.add(index, element); + } + + public E remove(int index) { + return list.remove(index); + } + + public Iterator iterator() { + return new Iterator() { + Iterator source = list.iterator(); + + public boolean hasNext() { + return source.hasNext(); + } + + public E next() { + return source.next(); + } + + public void remove() { + source.remove(); + } + }; + } + + public int size() { + return list.size(); + } +} diff --git a/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractSet.java b/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractSet.java new file mode 100644 index 00000000000..23661a357a9 --- /dev/null +++ b/jdk/test/java/util/Collection/testlibrary/ExtendsAbstractSet.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.util.HashSet; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Supplier; + +/** + * @library + * + * A simple mutable set implementation that provides only default + * implementations of all methods. ie. none of the Set interface default methods + * have overridden implementations. + * + * @param type of set members + */ +public class ExtendsAbstractSet extends AbstractSet { + + protected final Set set; + + public ExtendsAbstractSet() { + this(HashSet::new); + } + + public ExtendsAbstractSet(Collection source) { + this(); + addAll(source); + } + + protected ExtendsAbstractSet(Supplier> backer) { + this.set = backer.get(); + } + + public boolean add(E element) { + return set.add(element); + } + + public boolean remove(Object element) { + return set.remove(element); + } + + public Iterator iterator() { + return new Iterator() { + Iterator source = set.iterator(); + + public boolean hasNext() { + return source.hasNext(); + } + + public E next() { + return source.next(); + } + + public void remove() { + source.remove(); + } + }; + } + + public int size() { + return set.size(); + } +} diff --git a/jdk/test/java/util/Collection/ListDefaults.java b/jdk/test/java/util/List/ListDefaults.java similarity index 88% rename from jdk/test/java/util/Collection/ListDefaults.java rename to jdk/test/java/util/List/ListDefaults.java index 0d7a29152f6..76734b4e4be 100644 --- a/jdk/test/java/util/Collection/ListDefaults.java +++ b/jdk/test/java/util/List/ListDefaults.java @@ -28,8 +28,6 @@ import java.util.Comparator; import java.util.List; import java.util.LinkedList; import java.util.Stack; -import java.util.TreeMap; -import java.util.TreeSet; import java.util.Vector; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -46,28 +44,30 @@ import static org.testng.Assert.fail; import java.lang.reflect.Constructor; import java.util.ConcurrentModificationException; import java.util.function.Predicate; +import java.util.function.Supplier; /** * @test - * @bug 8023367 - * @library testlibrary - * @build CollectionAsserts CollectionSupplier - * @run testng ListDefaults * @summary Unit tests for extension methods on List + * @bug 8023367 + * @library ../Collection/testlibrary + * @build CollectionAsserts CollectionSupplier ExtendsAbstractList + * @run testng ListDefaults */ public class ListDefaults { - private static final String[] LIST_CLASSES = { - "java.util.ArrayList", - "java.util.LinkedList", - "java.util.Vector", - "java.util.concurrent.CopyOnWriteArrayList" - }; + private static final Supplier[] LIST_CLASSES = { + java.util.ArrayList::new, + java.util.LinkedList::new, + java.util.Vector::new, + java.util.concurrent.CopyOnWriteArrayList::new, + ExtendsAbstractList::new + }; - private static final String[] LIST_CME_CLASSES = { - "java.util.ArrayList", - "java.util.Vector" - }; + private static final Supplier[] LIST_CME_CLASSES = { + java.util.ArrayList::new, + java.util.Vector::new + }; private static final Predicate pEven = x -> 0 == x % 2; private static final Predicate pOdd = x -> 1 == x % 2; @@ -139,13 +139,9 @@ public class ListDefaults { @Test public void testForEach() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); - final List list = ((List) test.collection); - } - for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final List original = ((List) test.expected); final List list = ((List) test.collection); try { @@ -182,10 +178,9 @@ public class ListDefaults { @Test public void testRemoveIf() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CLASSES, SIZE); - - for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final List original = ((List) test.expected); final List list = ((List) test.collection); try { @@ -201,7 +196,7 @@ public class ListDefaults { } for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); + final List original = ((List) test.expected); final List list = ((List) test.collection); list.removeIf(pOdd); for (int i : list) { @@ -217,7 +212,7 @@ public class ListDefaults { } for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); + final List original = ((List) test.expected); final List list = ((List) test.collection); final List listCopy = new ArrayList<>(list); if (original.size() > SUBLIST_SIZE) { @@ -274,9 +269,9 @@ public class ListDefaults { @Test public void testReplaceAll() throws Exception { final int scale = 3; - final CollectionSupplier supplier = new CollectionSupplier(LIST_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final List original = ((List) test.expected); final List list = ((List) test.collection); try { @@ -329,9 +324,9 @@ public class ListDefaults { @Test public void testSort() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { - final List original = ((List) test.original); + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final List original = ((List) test.expected); final List list = ((List) test.collection); CollectionSupplier.shuffle(list); list.sort(Integer::compare); @@ -378,17 +373,15 @@ public class ListDefaults { } @SuppressWarnings("unchecked") - final Class> type = - (Class>) Class.forName(test.className); - final Constructor> defaultConstructor = type.getConstructor(); + final Constructor> defaultConstructor = ((Class>)test.collection.getClass()).getConstructor(); final List incomparables = (List) defaultConstructor.newInstance(); - for (int i=0; i < test.original.size(); i++) { + for (int i=0; i < test.expected.size(); i++) { incomparables.add(new AtomicInteger(i)); } CollectionSupplier.shuffle(incomparables); incomparables.sort(ATOMIC_INTEGER_COMPARATOR); - for (int i=0; i < test.original.size(); i++) { + for (int i=0; i < test.expected.size(); i++) { assertEquals(i, incomparables.get(i).intValue()); } @@ -427,9 +420,10 @@ public class ListDefaults { @Test public void testForEachThrowsCME() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CME_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CME_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { final List list = ((List) test.collection); + if (list.size() <= 1) { continue; } @@ -448,9 +442,11 @@ public class ListDefaults { @Test public void testRemoveIfThrowsCME() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CME_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CME_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { + final List original = ((List) test.expected); final List list = ((List) test.collection); + if (list.size() <= 1) { continue; } @@ -469,9 +465,10 @@ public class ListDefaults { @Test public void testReplaceAllThrowsCME() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CME_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CME_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { final List list = ((List) test.collection); + if (list.size() <= 1) { continue; } @@ -490,9 +487,10 @@ public class ListDefaults { @Test public void testSortThrowsCME() throws Exception { - final CollectionSupplier supplier = new CollectionSupplier(LIST_CME_CLASSES, SIZE); - for (final CollectionSupplier.TestCase test : supplier.get()) { + final CollectionSupplier> supplier = new CollectionSupplier((Supplier>[])LIST_CME_CLASSES, SIZE); + for (final CollectionSupplier.TestCase> test : supplier.get()) { final List list = ((List) test.collection); + if (list.size() <= 1) { continue; } @@ -520,6 +518,7 @@ public class ListDefaults { cases.add(new Object[] { new LinkedList<>(Arrays.asList(DATA)) }); cases.add(new Object[] { new Vector<>(Arrays.asList(DATA)) }); cases.add(new Object[] { new CopyOnWriteArrayList<>(Arrays.asList(DATA)) }); + cases.add(new Object[] { new ExtendsAbstractList<>(Arrays.asList(DATA)) }); return cases.toArray(new Object[0][cases.size()]); } diff --git a/jdk/test/java/util/Map/Defaults.java b/jdk/test/java/util/Map/Defaults.java index 48a93525b7f..82470019048 100644 --- a/jdk/test/java/util/Map/Defaults.java +++ b/jdk/test/java/util/Map/Defaults.java @@ -155,7 +155,7 @@ public class Defaults { assertThrows( () -> { map.replaceAll((k,v) -> null); }, NullPointerException.class, - description); + description + " should not allow replacement with null value"); } @Test(dataProvider = "Map rw=true keys=withNull values=withNull") @@ -194,6 +194,15 @@ public class Defaults { assertSame(map.get(null), EXTRA_VALUE); } + @Test(dataProvider = "Map rw=true keys=nonNull values=nonNull") + public void testReplaceKVNoNulls(String description, Map map) { + assertTrue(map.containsKey(FIRST_KEY), "expected key missing"); + assertSame(map.get(FIRST_KEY), FIRST_VALUE, "found wrong value"); + assertThrows( () -> {map.replace(FIRST_KEY, null);}, NullPointerException.class, description + ": should throw NPE"); + assertSame(map.replace(FIRST_KEY, EXTRA_VALUE), FIRST_VALUE, description + ": replaced wrong value"); + assertSame(map.get(FIRST_KEY), EXTRA_VALUE, "found wrong value"); + } + @Test(dataProvider = "Map rw=true keys=all values=all") public void testReplaceKV(String description, Map map) { assertTrue(map.containsKey(KEYS[1])); @@ -224,6 +233,16 @@ public class Defaults { assertSame(map.get(null), EXTRA_VALUE); } + @Test(dataProvider = "Map rw=true keys=nonNull values=nonNull") + public void testReplaceKVVNoNulls(String description, Map map) { + assertTrue(map.containsKey(FIRST_KEY), "expected key missing"); + assertSame(map.get(FIRST_KEY), FIRST_VALUE, "found wrong value"); + assertThrows( () -> {map.replace(FIRST_KEY, FIRST_VALUE, null);}, NullPointerException.class, description + ": should throw NPE"); + assertThrows( () -> {if (!map.replace(FIRST_KEY, null, EXTRA_VALUE)) throw new NullPointerException("default returns false rather than throwing");}, NullPointerException.class, description + ": should throw NPE"); + assertTrue(map.replace(FIRST_KEY, FIRST_VALUE, EXTRA_VALUE), description + ": replaced wrong value"); + assertSame(map.get(FIRST_KEY), EXTRA_VALUE, "found wrong value"); + } + @Test(dataProvider = "Map rw=true keys=all values=all") public void testReplaceKVV(String description, Map map) { assertTrue(map.containsKey(KEYS[1])); @@ -470,6 +489,9 @@ public class Defaults { VALUES[each] = String.valueOf(each); } } + + private static final IntegerEnum FIRST_KEY = KEYS[0]; + private static final String FIRST_VALUE = VALUES[0]; private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY; private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE); @@ -583,6 +605,8 @@ public class Defaults { return Arrays.asList( // null key hostile new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls)}, + new Object[]{"TreeMap", makeMap(TreeMap::new, false, nulls)}, + new Object[]{"ExtendsAbstractMap(TreeMap)", makeMap(() -> {return new ExtendsAbstractMap(new TreeMap());}, false, nulls)}, new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls))} ); } @@ -591,10 +615,11 @@ public class Defaults { return Arrays.asList( // null key and value hostile new Object[]{"Hashtable", makeMap(Hashtable::new, false, false)}, - new Object[]{"TreeMap", makeMap(TreeMap::new, false, false)}, new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false, false)}, new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false, false)}, + new Object[]{"Collections.synchronizedMap(ConcurrentHashMap)", Collections.synchronizedMap(makeMap(ConcurrentHashMap::new, false, false))}, new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false, false), IntegerEnum.class, String.class)}, + new Object[]{"ExtendsAbstractMap(ConcurrentHashMap)", makeMap(() -> {return new ExtendsAbstractMap(new ConcurrentHashMap());}, false, false)}, new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false, false)} ); } @@ -641,18 +666,17 @@ public class Defaults { } public static void assertThrows(Thrower thrower, Class throwable, String message) { - Throwable result; + Throwable thrown; try { thrower.run(); - result = null; + thrown = null; } catch (Throwable caught) { - result = caught; + thrown = caught; } - assertInstance(result, throwable, - (null != message) - ? message - : "Failed to throw " + throwable.getCanonicalName()); + assertInstance(thrown, throwable, + ((null != message) ? message : "") + + " Failed to throw " + throwable.getCanonicalName()); } public static void assertThrows(Class throwable, String message, Thrower... throwers) { @@ -661,11 +685,11 @@ public class Defaults { } } - public static void assertInstance(T actual, Class expected) { + public static void assertInstance(Object actual, Class expected) { assertInstance(expected.isInstance(actual), null); } - public static void assertInstance(T actual, Class expected, String message) { + public static void assertInstance(Object actual, Class expected, String message) { assertTrue(expected.isInstance(actual), message); } From c712fac717b3874071b41400865f070b4fa77868 Mon Sep 17 00:00:00 2001 From: Mike Duigou Date: Fri, 13 Sep 2013 11:19:13 -0700 Subject: [PATCH 63/75] 8024014: TEST.groups - split sub-groups for jdk_collections, jdk_stream, jdk_concurrent, jdk_util_other from jdk_util Reviewed-by: mchung, dholmes, alanb --- jdk/test/TEST.groups | 58 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/jdk/test/TEST.groups b/jdk/test/TEST.groups index a59b38853ff..23c9a833209 100644 --- a/jdk/test/TEST.groups +++ b/jdk/test/TEST.groups @@ -20,6 +20,7 @@ # questions. # +# java.lang package and VM runtime support jdk_lang = \ java/lang \ -java/lang/management \ @@ -30,9 +31,64 @@ jdk_lang = \ jdk/lambda \ vm +# All of the java.util package jdk_util = \ + :jdk_util_other \ + :jdk_collections \ + :jdk_concurrent \ + :jdk_stream + +# All util components not part of some other util category +jdk_util_other = \ java/util \ - sun/util + sun/util \ + -:jdk_collections \ + -:jdk_concurrent \ + -:jdk_stream + +# java.util.concurrent (JSR-166) +# Maintained by JSR-166 EG (Doug Lea et al) +# Deque and PriorityQueue are also generally maintained by JSR-166 +jdk_concurrent = \ + java/util/concurrent \ + java/util/Deque \ + java/util/PriorityQueue + +# Java Collections Framework +jdk_collections = \ + java/util/AbstractCollection \ + java/util/AbstractList \ + java/util/AbstractMap \ + java/util/AbstractSequentialList \ + java/util/ArrayList \ + java/util/Arrays \ + java/util/BitSet \ + java/util/Collection \ + java/util/Collections \ + java/util/EnumMap \ + java/util/EnumSet \ + java/util/Comparator \ + java/util/Iterator \ + java/util/HashMap \ + java/util/Hashtable \ + java/util/IdentityHashMap \ + java/util/List \ + java/util/LinkedHashMap \ + java/util/LinkedHashSet \ + java/util/LinkedList \ + java/util/Map \ + java/util/NavigableMap \ + java/util/TimSort \ + java/util/TreeMap \ + java/util/Vector \ + java/util/WeakHashMap + +# java.util.stream (JSR-335) +jdk_stream = \ + java/util/Optional \ + java/util/SummaryStatistics \ + java/util/function \ + java/util/stream jdk_math = \ java/math From ac02958e79178cd05ac03c31757921a9268745c2 Mon Sep 17 00:00:00 2001 From: Brent Christian Date: Fri, 13 Sep 2013 11:26:44 -0700 Subject: [PATCH 64/75] 7199674: (props) user.home property does not return an accessible location in sandboxed environment [macosx] On MacOS X set user.home to value of NSHomeDirectory() Reviewed-by: alanb, ddehaven, mduigou --- jdk/make/common/Defs-macosx.gmk | 10 ++++------ jdk/make/java/java/Makefile | 1 + jdk/makefiles/CompileNativeLibraries.gmk | 2 ++ .../solaris/native/java/lang/java_props_macosx.c | 14 +++++++++++++- .../solaris/native/java/lang/java_props_macosx.h | 1 + jdk/src/solaris/native/java/lang/java_props_md.c | 9 ++++++++- 6 files changed, 29 insertions(+), 8 deletions(-) diff --git a/jdk/make/common/Defs-macosx.gmk b/jdk/make/common/Defs-macosx.gmk index 1b7aaa8ae20..befd2cfed5b 100644 --- a/jdk/make/common/Defs-macosx.gmk +++ b/jdk/make/common/Defs-macosx.gmk @@ -397,12 +397,10 @@ else INCLUDE_SA = true endif -ifdef CROSS_COMPILE_ARCH - # X11 headers are not under /usr/include - OTHER_CFLAGS += -I$(OPENWIN_HOME)/include - OTHER_CXXFLAGS += -I$(OPENWIN_HOME)/include - OTHER_CPPFLAGS += -I$(OPENWIN_HOME)/include -endif +# X11 headers are not under /usr/include +OTHER_CFLAGS += -I$(OPENWIN_HOME)/include +OTHER_CXXFLAGS += -I$(OPENWIN_HOME)/include +OTHER_CPPFLAGS += -I$(OPENWIN_HOME)/include LIB_LOCATION ?= $(LIBDIR) diff --git a/jdk/make/java/java/Makefile b/jdk/make/java/java/Makefile index 829cf933001..d2a663f9ae0 100644 --- a/jdk/make/java/java/Makefile +++ b/jdk/make/java/java/Makefile @@ -105,6 +105,7 @@ FILES_java += java/util/prefs/MacOSXPreferences.java \ java/util/prefs/MacOSXPreferencesFactory.java CFLAGS_$(VARIANT)/java_props_md.o = -Os -x objective-c +CFLAGS_$(VARIANT)/java_props_macosx.o = -Os -x objective-c endif # diff --git a/jdk/makefiles/CompileNativeLibraries.gmk b/jdk/makefiles/CompileNativeLibraries.gmk index 656ee3c4841..d507c92a594 100644 --- a/jdk/makefiles/CompileNativeLibraries.gmk +++ b/jdk/makefiles/CompileNativeLibraries.gmk @@ -211,6 +211,7 @@ ifneq ($(OPENJDK_TARGET_OS),macosx) LIBJAVA_EXCLUDE_FILES += java_props_macosx.c else BUILD_LIBJAVA_java_props_md.c_CFLAGS:=-x objective-c + BUILD_LIBJAVA_java_props_macosx.c_CFLAGS:=-x objective-c endif ifeq ($(OPENJDK_TARGET_OS),windows) @@ -252,6 +253,7 @@ $(eval $(call SetupNativeCompilation,BUILD_LIBJAVA,\ LDFLAGS_SUFFIX_linux:=$(LIBDL) $(BUILD_LIBFDLIBM),\ LDFLAGS_SUFFIX_macosx:=-L$(JDK_OUTPUTDIR)/objs/ -lfdlibm \ -framework CoreFoundation \ + -framework Foundation \ -framework Security -framework SystemConfiguration, \ LDFLAGS_SUFFIX_windows:=-export:winFileHandleOpen -export:handleLseek \ jvm.lib $(BUILD_LIBFDLIBM) $(WIN_VERIFY_LIB) \ diff --git a/jdk/src/solaris/native/java/lang/java_props_macosx.c b/jdk/src/solaris/native/java/lang/java_props_macosx.c index f9ad2e474b1..13fc80d3052 100644 --- a/jdk/src/solaris/native/java/lang/java_props_macosx.c +++ b/jdk/src/solaris/native/java/lang/java_props_macosx.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "java_props_macosx.h" @@ -271,9 +272,20 @@ static char * createConvertedException(CFStringRef cf_original) { return c_exception; } +/* + * Method for fetching the user.home path and storing it in the property list. + * For signed .apps running in the Mac App Sandbox, user.home is set to the + * app's sandbox container. + */ +void setUserHome(java_props_t *sprops) { + if (sprops == NULL) { return; } + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + sprops->user_home = createUTF8CString((CFStringRef)NSHomeDirectory()); + [pool drain]; +} /* - * Method for fetching proxy info and storing it in the propery list. + * Method for fetching proxy info and storing it in the property list. */ void setProxyProperties(java_props_t *sProps) { if (sProps == NULL) return; diff --git a/jdk/src/solaris/native/java/lang/java_props_macosx.h b/jdk/src/solaris/native/java/lang/java_props_macosx.h index b311804835d..19ec220ed4b 100644 --- a/jdk/src/solaris/native/java/lang/java_props_macosx.h +++ b/jdk/src/solaris/native/java/lang/java_props_macosx.h @@ -27,6 +27,7 @@ char *setupMacOSXLocale(int cat); void setOSNameAndVersion(java_props_t *sprops); +void setUserHome(java_props_t *sprops); void setProxyProperties(java_props_t *sProps); enum PreferredToolkit_enum { diff --git a/jdk/src/solaris/native/java/lang/java_props_md.c b/jdk/src/solaris/native/java/lang/java_props_md.c index 58e5e4543d7..dba053e8f4a 100644 --- a/jdk/src/solaris/native/java/lang/java_props_md.c +++ b/jdk/src/solaris/native/java/lang/java_props_md.c @@ -591,7 +591,14 @@ GetJavaProperties(JNIEnv *env) { struct passwd *pwent = getpwuid(getuid()); sprops.user_name = pwent ? strdup(pwent->pw_name) : "?"; - sprops.user_home = pwent ? strdup(pwent->pw_dir) : "?"; +#ifdef MACOSX + setUserHome(&sprops); +#else + sprops.user_home = pwent ? strdup(pwent->pw_dir) : NULL; +#endif + if (sprops.user_home == NULL) { + sprops.user_home = "?"; + } } /* User TIMEZONE */ From 320e8d21e860d431a21d98f6c44956a7f790e3e4 Mon Sep 17 00:00:00 2001 From: Lance Andersen Date: Fri, 13 Sep 2013 19:10:31 -0400 Subject: [PATCH 65/75] 8014967: EBehavior of DriverManager.registerDriver(dr) is unspecified if driver is null Reviewed-by: alanb --- jdk/src/share/classes/java/sql/DriverManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jdk/src/share/classes/java/sql/DriverManager.java b/jdk/src/share/classes/java/sql/DriverManager.java index 797a920d230..bd16f92de7b 100644 --- a/jdk/src/share/classes/java/sql/DriverManager.java +++ b/jdk/src/share/classes/java/sql/DriverManager.java @@ -326,6 +326,7 @@ public class DriverManager { * @param driver the new JDBC Driver that is to be registered with the * {@code DriverManager} * @exception SQLException if a database access error occurs + * @exception NullPointerException if {@code driver} is null */ public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { @@ -345,6 +346,7 @@ public class DriverManager { * @param da the {@code DriverAction} implementation to be used when * {@code DriverManager#deregisterDriver} is called * @exception SQLException if a database access error occurs + * @exception NullPointerException if {@code driver} is null */ public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) From 9bed48236a0b365c5478d5df85fc820eca1dc3f2 Mon Sep 17 00:00:00 2001 From: Henry Jen Date: Fri, 6 Sep 2013 15:36:00 -0700 Subject: [PATCH 66/75] 8024825: Some fixes are missing from java.util.stream spec update Reviewed-by: mduigou --- .../java/util/stream/ReferencePipeline.java | 3 - .../classes/java/util/stream/Stream.java | 6 +- .../java/util/stream/package-info.java | 142 +++++++++--------- 3 files changed, 78 insertions(+), 73 deletions(-) diff --git a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java index 9d6aa59c8a7..0efd978f21c 100644 --- a/jdk/src/share/classes/java/util/stream/ReferencePipeline.java +++ b/jdk/src/share/classes/java/util/stream/ReferencePipeline.java @@ -170,7 +170,6 @@ abstract class ReferencePipeline } @Override - @SuppressWarnings("unchecked") public void accept(P_OUT u) { if (predicate.test(u)) downstream.accept(u); @@ -264,7 +263,6 @@ abstract class ReferencePipeline } @Override - @SuppressWarnings("unchecked") public void accept(P_OUT u) { try (Stream result = mapper.apply(u)) { // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it @@ -370,7 +368,6 @@ abstract class ReferencePipeline Sink opWrapSink(int flags, Sink sink) { return new Sink.ChainedReference(sink) { @Override - @SuppressWarnings("unchecked") public void accept(P_OUT u) { action.accept(u); downstream.accept(u); diff --git a/jdk/src/share/classes/java/util/stream/Stream.java b/jdk/src/share/classes/java/util/stream/Stream.java index 4a9f05f5103..a48d59d39d9 100644 --- a/jdk/src/share/classes/java/util/stream/Stream.java +++ b/jdk/src/share/classes/java/util/stream/Stream.java @@ -567,8 +567,8 @@ public interface Stream extends BaseStream> { /** * Performs a reduction on the - * elements of this stream, using the provided identity, accumulation - * function, and combining functions. This is equivalent to: + * elements of this stream, using the provided identity, accumulation and + * combining functions. This is equivalent to: *
    {@code
          *     U result = identity;
          *     for (T element : this stream)
    @@ -886,7 +886,7 @@ public interface Stream extends BaseStream> {
          * @return the new stream
          */
         @SafeVarargs
    -    @SuppressWarnings("varargs")
    +    @SuppressWarnings("varargs") // Creating a stream from an array is safe
         public static Stream of(T... values) {
             return Arrays.stream(values);
         }
    diff --git a/jdk/src/share/classes/java/util/stream/package-info.java b/jdk/src/share/classes/java/util/stream/package-info.java
    index 2c01847ace3..440054eb468 100644
    --- a/jdk/src/share/classes/java/util/stream/package-info.java
    +++ b/jdk/src/share/classes/java/util/stream/package-info.java
    @@ -64,7 +64,7 @@
      *     operations and terminal (value- or side-effect-producing) operations.
      *     Intermediate operations are always lazy.
      *     
  • Possibly unbounded. While collections have a finite size, streams - * need not. Short-circuting operations such as {@code limit(n)} or + * need not. Short-circuiting operations such as {@code limit(n)} or * {@code findFirst()} can allow computations on infinite streams to * complete in finite time.
  • *
  • Consumable. The elements of a stream are only visited once during @@ -96,13 +96,13 @@ * *

    Stream operations and pipelines

    * - *

    Stream operations are - * divided into intermediate and terminal operations, and are - * combined to form stream pipelines. A stream pipeline consists of a - * source (such as a {@code Collection}, an array, a generator function, or an - * I/O channel); followed by zero or more intermediate operations such - * as {@code Stream.filter} or {@code Stream.map}; and a terminal - * operation such as {@code Stream.forEach} or {@code Stream.reduce}. + *

    Stream operations are divided into intermediate and + * terminal operations, and are combined to form stream + * pipelines. A stream pipeline consists of a source (such as a + * {@code Collection}, an array, a generator function, or an I/O channel); + * followed by zero or more intermediate operations such as + * {@code Stream.filter} or {@code Stream.map}; and a terminal operation such + * as {@code Stream.forEach} or {@code Stream.reduce}. * *

    Intermediate operations return a new stream. They are always * lazy; executing an intermediate operation such as @@ -176,9 +176,10 @@ * do: * *

    {@code
    - *     int sumOfWeights = widgets.}{@code parallelStream()}{@code .filter(b -> b.getColor() == RED)
    - *                                                .mapToInt(b -> b.getWeight())
    - *                                                .sum();
    + *     int sumOfWeights = widgets.}parallelStream(){@code
    + *                               .filter(b -> b.getColor() == RED)
    + *                               .mapToInt(b -> b.getWeight())
    + *                               .sum();
      * }
    * *

    The only difference between the serial and parallel versions of this @@ -485,13 +486,13 @@ * as it collects together the desired results into a result container such * as a {@code Collection}. * A {@code collect} operation requires three functions: - * a factory function to construct new instances of the result container, an + * a supplier function to construct new instances of the result container, an * accumulator function to incorporate an input element into a result * container, and a combining function to merge the contents of one result * container into another. The form of this is very similar to the general * form of ordinary reduction: *

    {@code
    - *  R collect(Supplier resultFactory,
    + *  R collect(Supplier supplier,
      *               BiConsumer accumulator,
      *               BiConsumer combiner);
      * }
    @@ -525,10 +526,10 @@ * {@code ArrayList}, and the combiner simply uses {@link java.util.ArrayList#addAll addAll} * to copy the strings from one container into the other. * - *

    The three aspects of {@code collect} -- supplier, accumulator, and combiner -- - * are tightly coupled. We can use the abstraction of - * of a {@link java.util.stream.Collector} to capture all three aspects. - * The above example for collecting strings into a {@code List} can be rewritten + *

    The three aspects of {@code collect} -- supplier, accumulator, and + * combiner -- are tightly coupled. We can use the abstraction of a + * {@link java.util.stream.Collector} to capture all three aspects. The + * above example for collecting strings into a {@code List} can be rewritten * using a standard {@code Collector} as: *

    {@code
      *     List strings = stream.map(Object::toString)
    @@ -545,7 +546,7 @@
      * 
    {@code
      *     Collector summingSalaries
      *         = Collectors.summingInt(Employee::getSalary);
    - * } 
    + * }
    * * (The {@code ?} for the second type parameter merely indicates that we don't * care about the intermediate representation used by this collector.) @@ -557,14 +558,15 @@ * Map salariesByDept * = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment, * summingSalaries)); - * }
  • + * }
    * *

    As with the regular reduction operation, {@code collect()} operations can - * only be parallelized if appropriate conditions are met. For any partially accumulated result, - * combining it with an empty result container must produce an equivalent - * result. That is, for a partially accumulated result {@code p} that is the - * result of any series of accumulator and combiner invocations, {@code p} must - * be equivalent to {@code combiner.apply(p, supplier.get())}. + * only be parallelized if appropriate conditions are met. For any partially + * accumulated result, combining it with an empty result container must + * produce an equivalent result. That is, for a partially accumulated result + * {@code p} that is the result of any series of accumulator and combiner + * invocations, {@code p} must be equivalent to + * {@code combiner.apply(p, supplier.get())}. * *

    Further, however the computation is split, it must produce an equivalent * result. For any input elements {@code t1} and {@code t2}, the results @@ -580,7 +582,7 @@ * A a3 = supplier.get(); * accumulator.accept(a3, t2); * R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting - * }

    + * }
    * *

    Here, equivalence generally means according to {@link java.lang.Object#equals(Object)}. * but in some cases equivalence may be relaxed to account for differences in @@ -596,8 +598,8 @@ * .collect(Collectors.groupingBy(Transaction::getBuyer)); * }

    * it may actually be counterproductive to perform the operation in parallel. - * This is because the combining step (merging one {@code Map} into another by key) - * can be expensive for some {@code Map} implementations. + * This is because the combining step (merging one {@code Map} into another by + * key) can be expensive for some {@code Map} implementations. * *

    Suppose, however, that the result container used in this reduction * was a concurrently modifiable collection -- such as a @@ -605,8 +607,8 @@ * invocations of the accumulator could actually deposit their results * concurrently into the same shared result container, eliminating the need for * the combiner to merge distinct result containers. This potentially provides - * a boost to the parallel execution performance. We call this a concurrent - * reduction. + * a boost to the parallel execution performance. We call this a + * concurrent reduction. * *

    A {@link java.util.stream.Collector} that supports concurrent reduction is * marked with the {@link java.util.stream.Collector.Characteristics#CONCURRENT} @@ -635,11 +637,11 @@ * (where {@link java.util.stream.Collectors#groupingByConcurrent} is the * concurrent equivalent of {@code groupingBy}). * - *

    Note that if it is important that the elements for a given key appear in the - * order they appear in the source, then we cannot use a concurrent reduction, - * as ordering is one of the casualties of concurrent insertion. We would then - * be constrained to implement either a sequential reduction or a merge-based - * parallel reduction. + *

    Note that if it is important that the elements for a given key appear in + * the order they appear in the source, then we cannot use a concurrent + * reduction, as ordering is one of the casualties of concurrent insertion. + * We would then be constrained to implement either a sequential reduction or + * a merge-based parallel reduction. * *

    Associativity

    * @@ -656,8 +658,8 @@ * So we can evaluate {@code (a op b)} in parallel with {@code (c op d)}, and * then invoke {@code op} on the results. * - *

    Examples of associative operations include numeric addition, min, and max, - * and string concatenation. + *

    Examples of associative operations include numeric addition, min, and + * max, and string concatenation. * *

    Low-level stream construction

    * @@ -665,48 +667,54 @@ * {@link java.util.Collection#stream()} or {@link java.util.Arrays#stream(Object[])} * to obtain a stream. How are those stream-bearing methods implemented? * - *

    The class {@link java.util.stream.StreamSupport} has a number of low-level - * methods for creating a stream, all using some form of a {@link java.util.Spliterator}. - * A spliterator is the parallel analogue of an {@link java.util.Iterator}; it - * describes a (possibly infinite) collection of elements, with support for - * sequentially advancing, bulk traversal, and splitting off some portion of the - * input into another spliterator which can be processed in parallel. At the - * lowest level, all streams are driven by a spliterator. + *

    The class {@link java.util.stream.StreamSupport} has a number of + * low-level methods for creating a stream, all using some form of a + * {@link java.util.Spliterator}. A spliterator is the parallel analogue of an + * {@link java.util.Iterator}; it describes a (possibly infinite) collection of + * elements, with support for sequentially advancing, bulk traversal, and + * splitting off some portion of the input into another spliterator which can + * be processed in parallel. At the lowest level, all streams are driven by a + * spliterator. * - *

    There are a number of implementation choices in implementing a spliterator, - * nearly all of which are tradeoffs between simplicity of implementation and - * runtime performance of streams using that spliterator. The simplest, but - * least performant, way to create a spliterator is to create one from an iterator - * using {@link java.util.Spliterators#spliteratorUnknownSize(java.util.Iterator, int)}. + *

    There are a number of implementation choices in implementing a + * spliterator, nearly all of which are tradeoffs between simplicity of + * implementation and runtime performance of streams using that spliterator. + * The simplest, but least performant, way to create a spliterator is to + * create one from an iterator using + * {@link java.util.Spliterators#spliteratorUnknownSize(java.util.Iterator, int)}. * While such a spliterator will work, it will likely offer poor parallel - * performance, since we have lost sizing information (how big is the underlying - * data set), as well as being constrained to a simplistic splitting algorithm. + * performance, since we have lost sizing information (how big is the + * underlying data set), as well as being constrained to a simplistic + * splitting algorithm. * - *

    A higher-quality spliterator will provide balanced and known-size splits, - * accurate sizing information, and a number of other + *

    A higher-quality spliterator will provide balanced and known-size + * splits, accurate sizing information, and a number of other * {@link java.util.Spliterator#characteristics() characteristics} of the * spliterator or data that can be used by implementations to optimize * execution. * - *

    Spliterators for mutable data sources have an additional challenge; timing - * of binding to the data, since the data could change between the time the - * spliterator is created and the time the stream pipeline is executed. Ideally, - * a spliterator for a stream would report a characteristic of {@code IMMUTABLE} - * or {@code CONCURRENT}; if not it should be late-binding. - * If a source cannot directly supply a recommended spliterator, it may - * indirectly supply a spliterator using a {@code Supplier}, and construct a - * stream via the {@code Supplier}-accepting versions of + *

    Spliterators for mutable data sources have an additional challenge; + * timing of binding to the data, since the data could change between the time + * the spliterator is created and the time the stream pipeline is executed. + * Ideally, a spliterator for a stream would report a characteristic of + + * {@code IMMUTABLE} or {@code CONCURRENT}; if not it should be + * late-binding. If a source + * cannot directly supply a recommended spliterator, it may indirectly supply + * a spliterator using a {@code Supplier}, and construct a stream via the + * {@code Supplier}-accepting versions of * {@link java.util.stream.StreamSupport#stream(Supplier, int, boolean) stream()}. * The spliterator is obtained from the supplier only after the terminal * operation of the stream pipeline commences. * - *

    These requirements significantly reduce the scope of potential interference - * between mutations of the stream source and execution of stream pipelines. - * Streams based on spliterators with the desired characteristics, or those using - * the Supplier-based factory forms, are immune to modifications of the data - * source prior to commencement of the terminal operation (provided the behavioral - * parameters to the stream operations meet the required criteria for non-interference - * and statelessness). See Non-Interference + *

    These requirements significantly reduce the scope of potential + * interference between mutations of the stream source and execution of stream + * pipelines. Streams based on spliterators with the desired characteristics, + * or those using the Supplier-based factory forms, are immune to + * modifications of the data source prior to commencement of the terminal + * operation (provided the behavioral parameters to the stream operations meet + * the required criteria for non-interference and statelessness). See + * Non-Interference * for more details. * * @since 1.8 From e2940a0e2548efe210a16a66d3dd040207db2387 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Sat, 14 Sep 2013 13:55:04 -0400 Subject: [PATCH 67/75] 8023639: Difference between LocalTime.now(Clock.systemDefaultZone()) and LocalTime.now() executed successively is more than 100 000 000 nanoseconds for slow machines Test timed out on a slow machine; it is not a conformance test and should be in the test subtree Reviewed-by: darcy, sherman --- .../java/time/tck/java/time/TCKLocalTime.java | 11 ---------- .../time/test/java/time/TestLocalTime.java | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/jdk/test/java/time/tck/java/time/TCKLocalTime.java b/jdk/test/java/time/tck/java/time/TCKLocalTime.java index e0ef94cdadc..dbba80d1cec 100644 --- a/jdk/test/java/time/tck/java/time/TCKLocalTime.java +++ b/jdk/test/java/time/tck/java/time/TCKLocalTime.java @@ -282,17 +282,6 @@ public class TCKLocalTime extends AbstractDateTimeTest { check(LocalTime.MAX, 23, 59, 59, 999999999); } - //----------------------------------------------------------------------- - // now() - //----------------------------------------------------------------------- - @Test - public void now() { - LocalTime expected = LocalTime.now(Clock.systemDefaultZone()); - LocalTime test = LocalTime.now(); - long diff = Math.abs(test.toNanoOfDay() - expected.toNanoOfDay()); - assertTrue(diff < 100000000); // less than 0.1 secs - } - //----------------------------------------------------------------------- // now(ZoneId) //----------------------------------------------------------------------- diff --git a/jdk/test/java/time/test/java/time/TestLocalTime.java b/jdk/test/java/time/test/java/time/TestLocalTime.java index ddd7b0f572e..d7f04ab5af6 100644 --- a/jdk/test/java/time/test/java/time/TestLocalTime.java +++ b/jdk/test/java/time/test/java/time/TestLocalTime.java @@ -61,7 +61,9 @@ package test.java.time; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; +import java.time.Clock; import java.time.LocalTime; import org.testng.annotations.Test; @@ -176,4 +178,23 @@ public class TestLocalTime extends AbstractTest { } } + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test + public void now() { + // Warmup the TimeZone data so the following test does not include + // one-time initialization + LocalTime expected = LocalTime.now(Clock.systemDefaultZone()); + + expected = LocalTime.now(Clock.systemDefaultZone()); + LocalTime test = LocalTime.now(); + long diff = test.toNanoOfDay() - expected.toNanoOfDay(); + if (diff < 0) { + // Adjust if for rollover around midnight + diff += 24 * 60 * 60 * 1_000_000_000L; // Nanos Per Day + } + assertTrue(diff < 500000000); // less than 0.5 secs + } + } From e763c78a79d31afd5b9ce9fb04a808dc4f837a4c Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Sat, 14 Sep 2013 13:55:06 -0400 Subject: [PATCH 68/75] 8023556: Update javadoc for start of Meiji era Correct the javadoc in JapaneseEra.MEIJI to match the implementation Reviewed-by: darcy, sherman --- .../classes/java/time/chrono/JapaneseEra.java | 2 +- .../time/test/java/time/TestLocalTime.java | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/jdk/src/share/classes/java/time/chrono/JapaneseEra.java b/jdk/src/share/classes/java/time/chrono/JapaneseEra.java index 46eee0b59f8..2dbbde3aac2 100644 --- a/jdk/src/share/classes/java/time/chrono/JapaneseEra.java +++ b/jdk/src/share/classes/java/time/chrono/JapaneseEra.java @@ -104,7 +104,7 @@ public final class JapaneseEra static final sun.util.calendar.Era[] ERA_CONFIG; /** - * The singleton instance for the 'Meiji' era (1868-09-08 - 1912-07-29) + * The singleton instance for the 'Meiji' era (1868-01-01 - 1912-07-29) * which has the value -1. */ public static final JapaneseEra MEIJI = new JapaneseEra(-1, LocalDate.of(1868, 1, 1)); diff --git a/jdk/test/java/time/test/java/time/TestLocalTime.java b/jdk/test/java/time/test/java/time/TestLocalTime.java index d7f04ab5af6..8e25a85f198 100644 --- a/jdk/test/java/time/test/java/time/TestLocalTime.java +++ b/jdk/test/java/time/test/java/time/TestLocalTime.java @@ -73,6 +73,9 @@ import org.testng.annotations.Test; */ @Test public class TestLocalTime extends AbstractTest { + static final long NANOS_PER_SECOND = 1_000_000_000L; + static final long NANOS_PER_MINUTE = 60 * NANOS_PER_SECOND; + static final long NANOS_PER_DAY = 24 * 60 * NANOS_PER_MINUTE; //----------------------------------------------------------------------- @Test @@ -182,19 +185,27 @@ public class TestLocalTime extends AbstractTest { // now() //----------------------------------------------------------------------- @Test + @SuppressWarnings("unused") public void now() { // Warmup the TimeZone data so the following test does not include // one-time initialization - LocalTime expected = LocalTime.now(Clock.systemDefaultZone()); + LocalTime.now(Clock.systemDefaultZone()); - expected = LocalTime.now(Clock.systemDefaultZone()); - LocalTime test = LocalTime.now(); - long diff = test.toNanoOfDay() - expected.toNanoOfDay(); - if (diff < 0) { - // Adjust if for rollover around midnight - diff += 24 * 60 * 60 * 1_000_000_000L; // Nanos Per Day + long diff = Integer.MAX_VALUE; + for (int i = 0; i < 2; i++) { + LocalTime expected = LocalTime.now(Clock.systemDefaultZone()); + LocalTime test = LocalTime.now(); + diff = test.toNanoOfDay() - expected.toNanoOfDay(); + // Normalize for wrap-around midnight + diff = Math.floorMod(NANOS_PER_DAY + diff, NANOS_PER_DAY); + if (diff < 100000000) { + break; + } + // A second iteration may be needed if the clock changed + // due to a DST change between the two calls to now. } - assertTrue(diff < 500000000); // less than 0.5 secs + assertTrue(diff < 100000000, // less than 0.1 sec + "LocalTime.now vs LocalTime.now(Clock.systemDefaultZone()) not close"); } } From bef51c4a0895b8b525ee33d22281fdf56eea8431 Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Mon, 2 Sep 2013 11:59:57 +0200 Subject: [PATCH 69/75] 8010293: java/util/concurrent/ConcurrentHashMap/toArray.java fails intermittently Co-authored-by: Doug Lea Co-authored-by: Peter Levart Reviewed-by: forax, chegar, alanb --- .../util/concurrent/ConcurrentHashMap.java | 302 ++++++++++++------ .../concurrent/ConcurrentHashMap/toArray.java | 64 ++-- 2 files changed, 241 insertions(+), 125 deletions(-) diff --git a/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java b/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java index 9476bc411ba..c8c52a76ffd 100644 --- a/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/jdk/src/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -374,27 +374,26 @@ public class ConcurrentHashMap extends AbstractMap * The table is resized when occupancy exceeds a percentage * threshold (nominally, 0.75, but see below). Any thread * noticing an overfull bin may assist in resizing after the - * initiating thread allocates and sets up the replacement - * array. However, rather than stalling, these other threads may - * proceed with insertions etc. The use of TreeBins shields us - * from the worst case effects of overfilling while resizes are in + * initiating thread allocates and sets up the replacement array. + * However, rather than stalling, these other threads may proceed + * with insertions etc. The use of TreeBins shields us from the + * worst case effects of overfilling while resizes are in * progress. Resizing proceeds by transferring bins, one by one, - * from the table to the next table. To enable concurrency, the - * next table must be (incrementally) prefilled with place-holders - * serving as reverse forwarders to the old table. Because we are - * using power-of-two expansion, the elements from each bin must - * either stay at same index, or move with a power of two - * offset. We eliminate unnecessary node creation by catching - * cases where old nodes can be reused because their next fields - * won't change. On average, only about one-sixth of them need - * cloning when a table doubles. The nodes they replace will be - * garbage collectable as soon as they are no longer referenced by - * any reader thread that may be in the midst of concurrently - * traversing table. Upon transfer, the old table bin contains - * only a special forwarding node (with hash field "MOVED") that - * contains the next table as its key. On encountering a - * forwarding node, access and update operations restart, using - * the new table. + * from the table to the next table. However, threads claim small + * blocks of indices to transfer (via field transferIndex) before + * doing so, reducing contention. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. On + * average, only about one-sixth of them need cloning when a table + * doubles. The nodes they replace will be garbage collectable as + * soon as they are no longer referenced by any reader thread that + * may be in the midst of concurrently traversing table. Upon + * transfer, the old table bin contains only a special forwarding + * node (with hash field "MOVED") that contains the next table as + * its key. On encountering a forwarding node, access and update + * operations restart, using the new table. * * Each bin transfer requires its bin lock, which can stall * waiting for locks while resizing. However, because other @@ -402,13 +401,19 @@ public class ConcurrentHashMap extends AbstractMap * locks, average aggregate waits become shorter as resizing * progresses. The transfer operation must also ensure that all * accessible bins in both the old and new table are usable by any - * traversal. This is arranged by proceeding from the last bin - * (table.length - 1) up towards the first. Upon seeing a - * forwarding node, traversals (see class Traverser) arrange to - * move to the new table without revisiting nodes. However, to - * ensure that no intervening nodes are skipped, bin splitting can - * only begin after the associated reverse-forwarders are in - * place. + * traversal. This is arranged in part by proceeding from the + * last bin (table.length - 1) up towards the first. Upon seeing + * a forwarding node, traversals (see class Traverser) arrange to + * move to the new table without revisiting nodes. To ensure that + * no intervening nodes are skipped even when moved out of order, + * a stack (see class TableStack) is created on first encounter of + * a forwarding node during a traversal, to maintain its place if + * later processing the current table. The need for these + * save/restore mechanics is relatively rare, but when one + * forwarding node is encountered, typically many more will be. + * So Traversers use a simple caching scheme to avoid creating so + * many new TableStack nodes. (Thanks to Peter Levart for + * suggesting use of a stack here.) * * The traversal scheme also applies to partial traversals of * ranges of bins (via an alternate Traverser constructor) @@ -776,11 +781,6 @@ public class ConcurrentHashMap extends AbstractMap */ private transient volatile int transferIndex; - /** - * The least available table index to split while resizing. - */ - private transient volatile int transferOrigin; - /** * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. */ @@ -1377,7 +1377,8 @@ public class ConcurrentHashMap extends AbstractMap } int segmentShift = 32 - sshift; int segmentMask = ssize - 1; - @SuppressWarnings("unchecked") Segment[] segments = (Segment[]) + @SuppressWarnings("unchecked") + Segment[] segments = (Segment[]) new Segment[DEFAULT_CONCURRENCY_LEVEL]; for (int i = 0; i < segments.length; ++i) segments[i] = new Segment(LOAD_FACTOR); @@ -1420,8 +1421,10 @@ public class ConcurrentHashMap extends AbstractMap long size = 0L; Node p = null; for (;;) { - @SuppressWarnings("unchecked") K k = (K) s.readObject(); - @SuppressWarnings("unchecked") V v = (V) s.readObject(); + @SuppressWarnings("unchecked") + K k = (K) s.readObject(); + @SuppressWarnings("unchecked") + V v = (V) s.readObject(); if (k != null && v != null) { p = new Node(spread(k.hashCode()), k, v, p); ++size; @@ -1439,8 +1442,8 @@ public class ConcurrentHashMap extends AbstractMap int sz = (int)size; n = tableSizeFor(sz + (sz >>> 1) + 1); } - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] tab = (Node[])new Node[n]; + @SuppressWarnings("unchecked") + Node[] tab = (Node[])new Node[n]; int mask = n - 1; long added = 0L; while (p != null) { @@ -2200,8 +2203,8 @@ public class ConcurrentHashMap extends AbstractMap try { if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = (Node[])new Node[n]; + @SuppressWarnings("unchecked") + Node[] nt = (Node[])new Node[n]; table = tab = nt; sc = n - (n >>> 2); } @@ -2246,7 +2249,7 @@ public class ConcurrentHashMap extends AbstractMap while (s >= (long)(sc = sizeCtl) && (tab = table) != null && tab.length < MAXIMUM_CAPACITY) { if (sc < 0) { - if (sc == -1 || transferIndex <= transferOrigin || + if (sc == -1 || transferIndex <= 0 || (nt = nextTable) == null) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) @@ -2266,10 +2269,13 @@ public class ConcurrentHashMap extends AbstractMap Node[] nextTab; int sc; if ((f instanceof ForwardingNode) && (nextTab = ((ForwardingNode)f).nextTable) != null) { - if (nextTab == nextTable && tab == table && - transferIndex > transferOrigin && (sc = sizeCtl) < -1 && - U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) - transfer(tab, nextTab); + while (transferIndex > 0 && nextTab == nextTable && + (sc = sizeCtl) < -1) { + if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { + transfer(tab, nextTab); + break; + } + } return nextTab; } return table; @@ -2291,8 +2297,8 @@ public class ConcurrentHashMap extends AbstractMap if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if (table == tab) { - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = (Node[])new Node[n]; + @SuppressWarnings("unchecked") + Node[] nt = (Node[])new Node[n]; table = nt; sc = n - (n >>> 2); } @@ -2319,36 +2325,27 @@ public class ConcurrentHashMap extends AbstractMap stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // initiating try { - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = (Node[])new Node[n << 1]; + @SuppressWarnings("unchecked") + Node[] nt = (Node[])new Node[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; - transferOrigin = n; transferIndex = n; - ForwardingNode rev = new ForwardingNode(tab); - for (int k = n; k > 0;) { // progressively reveal ready slots - int nextk = (k > stride) ? k - stride : 0; - for (int m = nextk; m < k; ++m) - nextTab[m] = rev; - for (int m = n + nextk; m < n + k; ++m) - nextTab[m] = rev; - U.putOrderedInt(this, TRANSFERORIGIN, k = nextk); - } } int nextn = nextTab.length; ForwardingNode fwd = new ForwardingNode(nextTab); boolean advance = true; boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { - int nextIndex, nextBound, fh; Node f; + Node f; int fh; while (advance) { + int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; - else if ((nextIndex = transferIndex) <= transferOrigin) { + else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } @@ -2362,29 +2359,22 @@ public class ConcurrentHashMap extends AbstractMap } } if (i < 0 || i >= n || i + n >= nextn) { + int sc; if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } - for (int sc;;) { - if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) { - if (sc != -1) - return; - finishing = advance = true; - i = n; // recheck before commit - break; - } - } - } - else if ((f = tabAt(tab, i)) == null) { - if (casTabAt(tab, i, null, fwd)) { - setTabAt(nextTab, i, null); - setTabAt(nextTab, i + n, null); - advance = true; + if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) { + if (sc != -1) + return; + finishing = advance = true; + i = n; // recheck before commit } } + else if ((f = tabAt(tab, i)) == null) + advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) advance = true; // already processed else { @@ -3223,6 +3213,18 @@ public class ConcurrentHashMap extends AbstractMap /* ----------------Table Traversal -------------- */ + /** + * Records the table, its length, and current traversal index for a + * traverser that must process a region of a forwarded table before + * proceeding with current table. + */ + static final class TableStack { + int length; + int index; + Node[] tab; + TableStack next; + } + /** * Encapsulates traversal for methods such as containsValue; also * serves as a base class for other iterators and spliterators. @@ -3247,6 +3249,7 @@ public class ConcurrentHashMap extends AbstractMap static class Traverser { Node[] tab; // current table; updated if resized Node next; // the next entry to use + TableStack stack, spare; // to save/restore on ForwardingNodes int index; // index of bin to use next int baseIndex; // current index of initial table int baseLimit; // index bound for initial table @@ -3268,16 +3271,17 @@ public class ConcurrentHashMap extends AbstractMap if ((e = next) != null) e = e.next; for (;;) { - Node[] t; int i, n; K ek; // must use locals in checks + Node[] t; int i, n; // must use locals in checks if (e != null) return next = e; if (baseIndex >= baseLimit || (t = tab) == null || (n = t.length) <= (i = index) || i < 0) return next = null; - if ((e = tabAt(t, index)) != null && e.hash < 0) { + if ((e = tabAt(t, i)) != null && e.hash < 0) { if (e instanceof ForwardingNode) { tab = ((ForwardingNode)e).nextTable; e = null; + pushState(t, i, n); continue; } else if (e instanceof TreeBin) @@ -3285,10 +3289,49 @@ public class ConcurrentHashMap extends AbstractMap else e = null; } - if ((index += baseSize) >= n) - index = ++baseIndex; // visit upper slots if present + if (stack != null) + recoverState(n); + else if ((index = i + baseSize) >= n) + index = ++baseIndex; // visit upper slots if present } } + + /** + * Saves traversal state upon encountering a forwarding node. + */ + private void pushState(Node[] t, int i, int n) { + TableStack s = spare; // reuse if possible + if (s != null) + spare = s.next; + else + s = new TableStack(); + s.tab = t; + s.length = n; + s.index = i; + s.next = stack; + stack = s; + } + + /** + * Possibly pops traversal state. + * + * @param n length of current table + */ + private void recoverState(int n) { + TableStack s; int len; + while ((s = stack) != null && (index += (len = s.length)) >= n) { + n = len; + index = s.index; + tab = s.tab; + s.tab = null; + TableStack next = s.next; + s.next = spare; // save for reuse + stack = next; + spare = s; + } + if (s == null && (index += baseSize) >= n) + index = ++baseIndex; + } } /** @@ -4722,6 +4765,7 @@ public class ConcurrentHashMap extends AbstractMap abstract static class BulkTask extends CountedCompleter { Node[] tab; // same as Traverser Node next; + TableStack stack, spare; int index; int baseIndex; int baseLimit; @@ -4750,16 +4794,17 @@ public class ConcurrentHashMap extends AbstractMap if ((e = next) != null) e = e.next; for (;;) { - Node[] t; int i, n; K ek; // must use locals in checks + Node[] t; int i, n; if (e != null) return next = e; if (baseIndex >= baseLimit || (t = tab) == null || (n = t.length) <= (i = index) || i < 0) return next = null; - if ((e = tabAt(t, index)) != null && e.hash < 0) { + if ((e = tabAt(t, i)) != null && e.hash < 0) { if (e instanceof ForwardingNode) { tab = ((ForwardingNode)e).nextTable; e = null; + pushState(t, i, n); continue; } else if (e instanceof TreeBin) @@ -4767,10 +4812,41 @@ public class ConcurrentHashMap extends AbstractMap else e = null; } - if ((index += baseSize) >= n) - index = ++baseIndex; // visit upper slots if present + if (stack != null) + recoverState(n); + else if ((index = i + baseSize) >= n) + index = ++baseIndex; } } + + private void pushState(Node[] t, int i, int n) { + TableStack s = spare; + if (s != null) + spare = s.next; + else + s = new TableStack(); + s.tab = t; + s.length = n; + s.index = i; + s.next = stack; + stack = s; + } + + private void recoverState(int n) { + TableStack s; int len; + while ((s = stack) != null && (index += (len = s.length)) >= n) { + n = len; + index = s.index; + tab = s.tab; + s.tab = null; + TableStack next = s.next; + s.next = spare; // save for reuse + stack = next; + spare = s; + } + if (s == null && (index += baseSize) >= n) + index = ++baseIndex; + } } /* @@ -5229,7 +5305,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") ReduceKeysTask + @SuppressWarnings("unchecked") + ReduceKeysTask t = (ReduceKeysTask)c, s = t.rights; while (s != null) { @@ -5276,7 +5353,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") ReduceValuesTask + @SuppressWarnings("unchecked") + ReduceValuesTask t = (ReduceValuesTask)c, s = t.rights; while (s != null) { @@ -5321,7 +5399,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") ReduceEntriesTask + @SuppressWarnings("unchecked") + ReduceEntriesTask t = (ReduceEntriesTask)c, s = t.rights; while (s != null) { @@ -5374,7 +5453,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysTask + @SuppressWarnings("unchecked") + MapReduceKeysTask t = (MapReduceKeysTask)c, s = t.rights; while (s != null) { @@ -5427,7 +5507,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesTask + @SuppressWarnings("unchecked") + MapReduceValuesTask t = (MapReduceValuesTask)c, s = t.rights; while (s != null) { @@ -5480,7 +5561,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesTask + @SuppressWarnings("unchecked") + MapReduceEntriesTask t = (MapReduceEntriesTask)c, s = t.rights; while (s != null) { @@ -5533,7 +5615,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsTask + @SuppressWarnings("unchecked") + MapReduceMappingsTask t = (MapReduceMappingsTask)c, s = t.rights; while (s != null) { @@ -5585,7 +5668,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysToDoubleTask + @SuppressWarnings("unchecked") + MapReduceKeysToDoubleTask t = (MapReduceKeysToDoubleTask)c, s = t.rights; while (s != null) { @@ -5634,7 +5718,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesToDoubleTask + @SuppressWarnings("unchecked") + MapReduceValuesToDoubleTask t = (MapReduceValuesToDoubleTask)c, s = t.rights; while (s != null) { @@ -5683,7 +5768,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesToDoubleTask + @SuppressWarnings("unchecked") + MapReduceEntriesToDoubleTask t = (MapReduceEntriesToDoubleTask)c, s = t.rights; while (s != null) { @@ -5732,7 +5818,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsToDoubleTask + @SuppressWarnings("unchecked") + MapReduceMappingsToDoubleTask t = (MapReduceMappingsToDoubleTask)c, s = t.rights; while (s != null) { @@ -5781,7 +5868,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysToLongTask + @SuppressWarnings("unchecked") + MapReduceKeysToLongTask t = (MapReduceKeysToLongTask)c, s = t.rights; while (s != null) { @@ -5830,7 +5918,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesToLongTask + @SuppressWarnings("unchecked") + MapReduceValuesToLongTask t = (MapReduceValuesToLongTask)c, s = t.rights; while (s != null) { @@ -5879,7 +5968,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesToLongTask + @SuppressWarnings("unchecked") + MapReduceEntriesToLongTask t = (MapReduceEntriesToLongTask)c, s = t.rights; while (s != null) { @@ -5928,7 +6018,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsToLongTask + @SuppressWarnings("unchecked") + MapReduceMappingsToLongTask t = (MapReduceMappingsToLongTask)c, s = t.rights; while (s != null) { @@ -5977,7 +6068,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysToIntTask + @SuppressWarnings("unchecked") + MapReduceKeysToIntTask t = (MapReduceKeysToIntTask)c, s = t.rights; while (s != null) { @@ -6026,7 +6118,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesToIntTask + @SuppressWarnings("unchecked") + MapReduceValuesToIntTask t = (MapReduceValuesToIntTask)c, s = t.rights; while (s != null) { @@ -6075,7 +6168,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesToIntTask + @SuppressWarnings("unchecked") + MapReduceEntriesToIntTask t = (MapReduceEntriesToIntTask)c, s = t.rights; while (s != null) { @@ -6124,7 +6218,8 @@ public class ConcurrentHashMap extends AbstractMap result = r; CountedCompleter c; for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsToIntTask + @SuppressWarnings("unchecked") + MapReduceMappingsToIntTask t = (MapReduceMappingsToIntTask)c, s = t.rights; while (s != null) { @@ -6140,7 +6235,6 @@ public class ConcurrentHashMap extends AbstractMap private static final sun.misc.Unsafe U; private static final long SIZECTL; private static final long TRANSFERINDEX; - private static final long TRANSFERORIGIN; private static final long BASECOUNT; private static final long CELLSBUSY; private static final long CELLVALUE; @@ -6155,8 +6249,6 @@ public class ConcurrentHashMap extends AbstractMap (k.getDeclaredField("sizeCtl")); TRANSFERINDEX = U.objectFieldOffset (k.getDeclaredField("transferIndex")); - TRANSFERORIGIN = U.objectFieldOffset - (k.getDeclaredField("transferOrigin")); BASECOUNT = U.objectFieldOffset (k.getDeclaredField("baseCount")); CELLSBUSY = U.objectFieldOffset diff --git a/jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java b/jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java index 0280c0c4658..81108ac9c55 100644 --- a/jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java +++ b/jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java @@ -23,39 +23,53 @@ /* * @test - * @bug 4486658 + * @bug 4486658 8010293 * @summary thread safety of toArray methods of subCollections * @author Martin Buchholz */ -import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.IntStream; -public class toArray { +public class ToArray { public static void main(String[] args) throws Throwable { + // Execute a number of times to increase the probability of + // failure if there is an issue + for (int i = 0; i < 16; i++) { + executeTest(); + } + } + + static void executeTest() throws Throwable { final Throwable throwable[] = new Throwable[1]; - final int maxSize = 1000; - final ConcurrentHashMap m - = new ConcurrentHashMap(); + final ConcurrentHashMap m = new ConcurrentHashMap<>(); - final Thread t1 = new Thread() { public void run() { - for (int i = 0; i < maxSize; i++) - m.put(i,i);}}; + // Number of workers equal to the number of processors + // Each worker will put globally unique keys into the map + final int nWorkers = Runtime.getRuntime().availableProcessors(); + final int sizePerWorker = 1024; + final int maxSize = nWorkers * sizePerWorker; - final Thread t2 = new Thread() { - public Throwable exception = null; + // The foreman keeps checking that the size of the arrays + // obtained from the key and value sets is never less than the + // previously observed size and is never greater than the maximum size + // NOTE: these size constraints are not specific to toArray and are + // applicable to any form of traversal of the collection views + CompletableFuture foreman = CompletableFuture.runAsync(new Runnable() { private int prevSize = 0; private boolean checkProgress(Object[] a) { int size = a.length; if (size < prevSize) throw new RuntimeException("WRONG WAY"); - if (size > maxSize) throw new RuntimeException("OVERSHOOT"); + if (size > maxSize) throw new RuntimeException("OVERSHOOT"); if (size == maxSize) return true; prevSize = size; return false; } + @Override public void run() { try { Integer[] empty = new Integer[0]; @@ -65,15 +79,25 @@ public class toArray { if (checkProgress(m.values().toArray(empty))) return; if (checkProgress(m.keySet().toArray(empty))) return; } - } catch (Throwable t) { - throwable[0] = t; - }}}; + } + catch (Throwable t) { + throwable[0] = t; + } + } + }); - t2.start(); - t1.start(); + // Create workers + // Each worker will put globally unique keys into the map + CompletableFuture[] workers = IntStream.range(0, nWorkers). + mapToObj(w -> CompletableFuture.runAsync(() -> { + for (int i = 0, o = w * sizePerWorker; i < sizePerWorker; i++) + m.put(o + i, i); + })). + toArray(CompletableFuture[]::new); - t1.join(); - t2.join(); + // Wait for workers and then foreman to complete + CompletableFuture.allOf(workers).join(); + foreman.join(); if (throwable[0] != null) throw throwable[0]; From 4df3876c56f6b810120159af2959e59f90bab69b Mon Sep 17 00:00:00 2001 From: Paul Sandoz Date: Sun, 15 Sep 2013 16:13:41 +0200 Subject: [PATCH 70/75] 8024837: Rename java/util/concurrent/ConcurrentHashMap/toArray.java to ToArray.java Reviewed-by: alanb --- .../concurrent/ConcurrentHashMap/{toArray.java => ToArray.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename jdk/test/java/util/concurrent/ConcurrentHashMap/{toArray.java => ToArray.java} (98%) diff --git a/jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java b/jdk/test/java/util/concurrent/ConcurrentHashMap/ToArray.java similarity index 98% rename from jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java rename to jdk/test/java/util/concurrent/ConcurrentHashMap/ToArray.java index 81108ac9c55..dca07084162 100644 --- a/jdk/test/java/util/concurrent/ConcurrentHashMap/toArray.java +++ b/jdk/test/java/util/concurrent/ConcurrentHashMap/ToArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it From 344c38fc804de7d28b95d044c4f45ad007933ba6 Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Sun, 15 Sep 2013 11:16:58 -0700 Subject: [PATCH 71/75] 7186311: (props) "Unicode" is misspelled as "Uniocde" in JavaDoc and error message To correct the typo Reviewed-by: alanb, chegar --- .../tools/src/build/tools/generatecharacter/CharacterName.java | 2 +- jdk/src/share/classes/java/util/Properties.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jdk/make/tools/src/build/tools/generatecharacter/CharacterName.java b/jdk/make/tools/src/build/tools/generatecharacter/CharacterName.java index 0872100b7db..833a835c0ce 100644 --- a/jdk/make/tools/src/build/tools/generatecharacter/CharacterName.java +++ b/jdk/make/tools/src/build/tools/generatecharacter/CharacterName.java @@ -11,7 +11,7 @@ public class CharacterName { FileReader reader = null; try { if (args.length != 2) { - System.err.println("Usage: java CharacterName UniocdeData.txt uniName.dat"); + System.err.println("Usage: java CharacterName UnicodeData.txt uniName.dat"); System.exit(1); } diff --git a/jdk/src/share/classes/java/util/Properties.java b/jdk/src/share/classes/java/util/Properties.java index ed0bf857d66..073c7771630 100644 --- a/jdk/src/share/classes/java/util/Properties.java +++ b/jdk/src/share/classes/java/util/Properties.java @@ -304,7 +304,7 @@ class Properties extends Hashtable { * preceded by a backslash still yield single and double quote * characters, respectively. * - *

  • Only a single 'u' character is allowed in a Uniocde escape + *
  • Only a single 'u' character is allowed in a Unicode escape * sequence. * * From 763eb8d2e341667f9f8470df39a6bb0ff84b20ce Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Sun, 15 Sep 2013 13:58:47 -0700 Subject: [PATCH 72/75] 8020687: Deflater.setLevel does not work as expected To clarify the api to match the existing implementation behavior Reviewed-by: alanb --- jdk/src/share/classes/java/util/zip/Deflater.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/jdk/src/share/classes/java/util/zip/Deflater.java b/jdk/src/share/classes/java/util/zip/Deflater.java index 085287996be..c4521bec667 100644 --- a/jdk/src/share/classes/java/util/zip/Deflater.java +++ b/jdk/src/share/classes/java/util/zip/Deflater.java @@ -261,6 +261,12 @@ class Deflater { /** * Sets the compression strategy to the specified value. + * + *

    If the compression strategy is changed, the next invocation + * of {@code deflate} will compress the input available so far with + * the old strategy (and may be flushed); the new strategy will take + * effect only after that invocation. + * * @param strategy the new compression strategy * @exception IllegalArgumentException if the compression strategy is * invalid @@ -283,7 +289,13 @@ class Deflater { } /** - * Sets the current compression level to the specified value. + * Sets the compression level to the specified value. + * + *

    If the compression level is changed, the next invocation + * of {@code deflate} will compress the input available so far + * with the old level (and may be flushed); the new level will + * take effect only after that invocation. + * * @param level the new compression level (0-9) * @exception IllegalArgumentException if the compression level is invalid */ From 4d540aa581e7f9ecb8b74cf519238d182b42e28d Mon Sep 17 00:00:00 2001 From: Mark Sheppard Date: Mon, 16 Sep 2013 14:51:48 +0100 Subject: [PATCH 73/75] 6458027: Disabling IPv6 on a specific network interface causes problems Added a check to test if an interface is configured for IPv6 to native code TwoStacklainDatagramSocketImpl: getMulticastInterface, setMulticastInterface Reviewed-by: chegar, michaelm --- .../native/java/net/NetworkInterface.c | 2 +- .../net/TwoStacksPlainDatagramSocketImpl.c | 160 ++++++++++++------ .../SetGetNetworkInterfaceTest.java | 125 ++++++++++++++ 3 files changed, 230 insertions(+), 57 deletions(-) create mode 100644 jdk/test/java/net/MulticastSocket/SetGetNetworkInterfaceTest.java diff --git a/jdk/src/windows/native/java/net/NetworkInterface.c b/jdk/src/windows/native/java/net/NetworkInterface.c index c548930a913..98606fab667 100644 --- a/jdk/src/windows/native/java/net/NetworkInterface.c +++ b/jdk/src/windows/native/java/net/NetworkInterface.c @@ -900,7 +900,7 @@ JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isUp0 MIB_IFROW *ifRowP; ifRowP = getIF(index); if (ifRowP != NULL) { - ret = ifRowP->dwAdminStatus == 1 && + ret = ifRowP->dwAdminStatus == MIB_IF_ADMIN_STATUS_UP && (ifRowP->dwOperStatus == MIB_IF_OPER_STATUS_OPERATIONAL || ifRowP->dwOperStatus == MIB_IF_OPER_STATUS_CONNECTED); free(ifRowP); diff --git a/jdk/src/windows/native/java/net/TwoStacksPlainDatagramSocketImpl.c b/jdk/src/windows/native/java/net/TwoStacksPlainDatagramSocketImpl.c index 5adadd28e0f..77496d195cf 100644 --- a/jdk/src/windows/native/java/net/TwoStacksPlainDatagramSocketImpl.c +++ b/jdk/src/windows/native/java/net/TwoStacksPlainDatagramSocketImpl.c @@ -43,6 +43,7 @@ #include "java_net_SocketOptions.h" #include "java_net_NetworkInterface.h" +#include "NetworkInterface.h" #include "jvm.h" #include "jni_util.h" #include "net_util.h" @@ -1644,6 +1645,33 @@ static int getIndexFromIf (JNIEnv *env, jobject nif) { return (*env)->GetIntField(env, nif, ni_indexID); } +static int isAdapterIpv6Enabled(JNIEnv *env, int index) { + netif *ifList, *curr; + int ipv6Enabled = 0; + if (getAllInterfacesAndAddresses (env, &ifList) < 0) { + return ipv6Enabled; + } + + /* search by index */ + curr = ifList; + while (curr != NULL) { + if (index == curr->index) { + break; + } + curr = curr->next; + } + + /* if found ipv6Index != 0 then interface is configured with IPV6 */ + if ((curr != NULL) && (curr->ipv6Index !=0)) { + ipv6Enabled = 1; + } + + /* release the interface list */ + free_netif(ifList); + + return ipv6Enabled; +} + /* * Sets the multicast interface. * @@ -1703,7 +1731,6 @@ static void setMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, struct in_addr in; in.s_addr = htonl(getInetAddress_addr(env, value)); - if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&in, sizeof(in)) < 0) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", @@ -1734,19 +1761,20 @@ static void setMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, } index = (*env)->GetIntField(env, value, ni_indexID); - if (setsockopt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_IF, + if ( isAdapterIpv6Enabled(env, index) != 0 ) { + if (setsockopt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char*)&index, sizeof(index)) < 0) { - if (errno == EINVAL && index > 0) { - JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", - "IPV6_MULTICAST_IF failed (interface has IPv4 " - "address only?)"); - } else { - NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", + if (errno == EINVAL && index > 0) { + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", + "IPV6_MULTICAST_IF failed (interface has IPv4 " + "address only?)"); + } else { + NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); + } + return; } - return; } - /* If there are any IPv4 addresses on this interface then * repeat the operation on the IPv4 fd */ @@ -1797,7 +1825,6 @@ Java_java_net_TwoStacksPlainDatagramSocketImpl_socketNativeSetOption(JNIEnv *env char c; } optval; int ipv6_supported = ipv6_available(); - fd = getFD(env, this); if (ipv6_supported) { @@ -1898,42 +1925,21 @@ Java_java_net_TwoStacksPlainDatagramSocketImpl_socketNativeSetOption(JNIEnv *env } /* - * Return the multicast interface: * - * SocketOptions.IP_MULTICAST_IF - * IPv4: Query IPPROTO_IP/IP_MULTICAST_IF - * Create InetAddress - * IP_MULTICAST_IF returns struct ip_mreqn on 2.2 - * kernel but struct in_addr on 2.4 kernel - * IPv6: Query IPPROTO_IPV6 / IPV6_MULTICAST_IF or - * obtain from impl is Linux 2.2 kernel - * If index == 0 return InetAddress representing - * anyLocalAddress. - * If index > 0 query NetworkInterface by index - * and returns addrs[0] + * called by getMulticastInterface to retrieve a NetworkInterface + * configured for IPv4. + * The ipv4Mode parameter, is a closet boolean, which allows for a NULL return, + * or forces the creation of a NetworkInterface object with null data. + * It relates to its calling context in getMulticastInterface. + * ipv4Mode == 1, the context is IPV4 processing only. + * ipv4Mode == 0, the context is IPV6 processing * - * SocketOptions.IP_MULTICAST_IF2 - * IPv4: Query IPPROTO_IP/IP_MULTICAST_IF - * Query NetworkInterface by IP address and - * return the NetworkInterface that the address - * is bound too. - * IPv6: Query IPPROTO_IPV6 / IPV6_MULTICAST_IF - * (except Linux .2 kernel) - * Query NetworkInterface by index and - * return NetworkInterface. */ -jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, jint opt) { - jboolean isIPV4 = !ipv6_available() || fd1 == -1; - - /* - * IPv4 implementation - */ - if (isIPV4) { +static jobject getIPv4NetworkInterface (JNIEnv *env, jobject this, int fd, jint opt, int ipv4Mode) { static jclass inet4_class; static jmethodID inet4_ctrID; - static jclass ni_class; - static jmethodID ni_ctrID; + static jclass ni_class; static jmethodID ni_ctrID; static jfieldID ni_indexID; static jfieldID ni_addrsID; @@ -1944,7 +1950,6 @@ jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, jint o struct in_addr in; struct in_addr *inP = ∈ int len = sizeof(struct in_addr); - if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char *)inP, &len) < 0) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", @@ -1996,23 +2001,57 @@ jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, jint o if (ni) { return ni; } + if (ipv4Mode) { + ni = (*env)->NewObject(env, ni_class, ni_ctrID, 0); + CHECK_NULL_RETURN(ni, NULL); - /* - * The address doesn't appear to be bound at any known - * NetworkInterface. Therefore we construct a NetworkInterface - * with this address. - */ - ni = (*env)->NewObject(env, ni_class, ni_ctrID, 0); - CHECK_NULL_RETURN(ni, NULL); - - (*env)->SetIntField(env, ni, ni_indexID, -1); - addrArray = (*env)->NewObjectArray(env, 1, inet4_class, NULL); - CHECK_NULL_RETURN(addrArray, NULL); - (*env)->SetObjectArrayElement(env, addrArray, 0, addr); - (*env)->SetObjectField(env, ni, ni_addrsID, addrArray); + (*env)->SetIntField(env, ni, ni_indexID, -1); + addrArray = (*env)->NewObjectArray(env, 1, inet4_class, NULL); + CHECK_NULL_RETURN(addrArray, NULL); + (*env)->SetObjectArrayElement(env, addrArray, 0, addr); + (*env)->SetObjectField(env, ni, ni_addrsID, addrArray); + } else { + ni = NULL; + } return ni; - } +} +/* + * Return the multicast interface: + * + * SocketOptions.IP_MULTICAST_IF + * IPv4: Query IPPROTO_IP/IP_MULTICAST_IF + * Create InetAddress + * IP_MULTICAST_IF returns struct ip_mreqn on 2.2 + * kernel but struct in_addr on 2.4 kernel + * IPv6: Query IPPROTO_IPV6 / IPV6_MULTICAST_IF or + * obtain from impl is Linux 2.2 kernel + * If index == 0 return InetAddress representing + * anyLocalAddress. + * If index > 0 query NetworkInterface by index + * and returns addrs[0] + * + * SocketOptions.IP_MULTICAST_IF2 + * IPv4: Query IPPROTO_IP/IP_MULTICAST_IF + * Query NetworkInterface by IP address and + * return the NetworkInterface that the address + * is bound too. + * IPv6: Query IPPROTO_IPV6 / IPV6_MULTICAST_IF + * (except Linux .2 kernel) + * Query NetworkInterface by index and + * return NetworkInterface. + */ +jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, jint opt) { + jboolean isIPV4 = !ipv6_available() || fd1 == -1; + + /* + * IPv4 implementation + */ + if (isIPV4) { + jobject netObject = NULL; // return is either an addr or a netif + netObject = getIPv4NetworkInterface(env, this, fd, opt, 1); + return netObject; + } /* * IPv6 implementation @@ -2103,6 +2142,13 @@ jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, jint o addr = (*env)->GetObjectArrayElement(env, addrArray, 0); return addr; + } else if (index == 0) { // index == 0 typically means IPv6 not configured on the interfaces + // falling back to treat interface as configured for IPv4 + jobject netObject = NULL; + netObject = getIPv4NetworkInterface(env, this, fd, opt, 0); + if (netObject != NULL) { + return netObject; + } } /* @@ -2127,6 +2173,8 @@ jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, int fd1, jint o } return NULL; } + + /* * Returns relevant info as a jint. * diff --git a/jdk/test/java/net/MulticastSocket/SetGetNetworkInterfaceTest.java b/jdk/test/java/net/MulticastSocket/SetGetNetworkInterfaceTest.java new file mode 100644 index 00000000000..b46bdbedbce --- /dev/null +++ b/jdk/test/java/net/MulticastSocket/SetGetNetworkInterfaceTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @bug 6458027 + * @summary Disabling IPv6 on a specific network interface causes problems. + * + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.net.MulticastSocket; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Arrays; +import java.util.Enumeration; + + +public class SetGetNetworkInterfaceTest { + + public static void main(String[] args) throws Exception { + + boolean passed = true; + try { + MulticastSocket ms = new MulticastSocket(); + Enumeration networkInterfaces = NetworkInterface + .getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface netIf = networkInterfaces.nextElement(); + if (isNetworkInterfaceTestable(netIf)) { + printNetIfDetails(netIf); + ms.setNetworkInterface(netIf); + NetworkInterface msNetIf = ms.getNetworkInterface(); + if (netIf.equals(msNetIf)) { + System.out.println(" OK"); + } else { + System.out.println("FAILED!!!"); + printNetIfDetails(msNetIf); + passed = false; + } + System.out.println("------------------"); + } + } + } catch (IOException e) { + e.printStackTrace(); + passed = false; + } + if (!passed) { + throw new RuntimeException("Test Fail"); + } + System.out.println("Test passed "); + } + + private static boolean isNetworkInterfaceTestable(NetworkInterface netIf) throws Exception { + System.out.println("checking netif == " + netIf.getName()); + return (netIf.isUp() && netIf.supportsMulticast() && isIpAddrAvailable(netIf)); + } + + private static boolean isIpAddrAvailable (NetworkInterface netIf) { + boolean ipAddrAvailable = false; + byte[] nullIpAddr = {'0', '0', '0', '0'}; + byte[] testIpAddr = null; + + Enumeration ipAddresses = netIf.getInetAddresses(); + while (ipAddresses.hasMoreElements()) { + InetAddress testAddr = ipAddresses.nextElement(); + testIpAddr = testAddr.getAddress(); + if ((testIpAddr != null) && (!Arrays.equals(testIpAddr, nullIpAddr))) { + ipAddrAvailable = true; + break; + } else { + System.out.println("ignore netif " + netIf.getName()); + } + } + return ipAddrAvailable; + } + + private static void printNetIfDetails(NetworkInterface ni) + throws SocketException { + System.out.println("Name " + ni.getName() + " index " + ni.getIndex()); + Enumeration en = ni.getInetAddresses(); + while (en.hasMoreElements()) { + System.out.println(" InetAdress: " + en.nextElement()); + } + System.out.println("HardwareAddress: " + createMacAddrString(ni)); + System.out.println("loopback: " + ni.isLoopback() + "; pointToPoint: " + + ni.isPointToPoint() + "; virtual: " + ni.isVirtual() + + "; MTU: " + ni.getMTU()); + } + + private static String createMacAddrString(NetworkInterface netIf) + throws SocketException { + byte[] macAddr = netIf.getHardwareAddress(); + StringBuilder sb = new StringBuilder(); + if (macAddr != null) { + for (int i = 0; i < macAddr.length; i++) { + sb.append(String.format("%02X%s", macAddr[i], + (i < macAddr.length - 1) ? "-" : "")); + } + } + return sb.toString(); + } +} From 43e0cb63278d4ab1a1b4443e1bee83daefbf751c Mon Sep 17 00:00:00 2001 From: Henry Jen Date: Mon, 16 Sep 2013 10:28:20 -0700 Subject: [PATCH 74/75] 8024874: Copy-paste typo in the spec for j.u.Comparator.thenComparing(Function, Comparator) Reviewed-by: mduigou --- jdk/src/share/classes/java/util/Comparator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk/src/share/classes/java/util/Comparator.java b/jdk/src/share/classes/java/util/Comparator.java index b19481df198..6f9d1663e69 100644 --- a/jdk/src/share/classes/java/util/Comparator.java +++ b/jdk/src/share/classes/java/util/Comparator.java @@ -230,7 +230,7 @@ public interface Comparator { * @param keyComparator the {@code Comparator} used to compare the sort key * @return a lexicographic-order comparator composed of this comparator * and then comparing on the key extracted by the keyExtractor function - * @throws NullPointerException if the argument is null. + * @throws NullPointerException if either argument is null. * @see #comparing(Function, Comparator) * @see #thenComparing(Comparator) * @since 1.8 From 508e958dfe7856e132e0b9c42573220ce5002ceb Mon Sep 17 00:00:00 2001 From: Lance Andersen Date: Tue, 17 Sep 2013 07:56:56 -0400 Subject: [PATCH 75/75] 7097386: Correct error in Predicate javadoc example Reviewed-by: alanb, shade --- .../classes/javax/sql/rowset/Predicate.java | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/jdk/src/share/classes/javax/sql/rowset/Predicate.java b/jdk/src/share/classes/javax/sql/rowset/Predicate.java index 63d76fdca6b..5b9676d67f4 100644 --- a/jdk/src/share/classes/javax/sql/rowset/Predicate.java +++ b/jdk/src/share/classes/javax/sql/rowset/Predicate.java @@ -56,44 +56,43 @@ import java.sql.*; *

    {@code
      *    public class Range implements Predicate {
      *
    - *       private Object lo[];
    - *       private Object hi[];
    - *       private int idx[];
    + *       private int[] lo;
    + *       private int[] hi;
    + *       private int[] idx;
      *
    - *       public Range(Object[] lo, Object[] hi, int[] idx) {
    + *       public Range(int[] lo, int[] hi, int[] idx) {
      *          this.lo = lo;
      *          this.hi = hi;
      *          this.idx = idx;
      *       }
      *
      *      public boolean evaluate(RowSet rs) {
    - *          CachedRowSet crs = (CachedRowSet)rs;
    - *          boolean bool1,bool2;
      *
      *          // Check the present row determine if it lies
      *          // within the filtering criteria.
      *
      *          for (int i = 0; i < idx.length; i++) {
    + *             int value;
    + *             try {
    + *                 value = (Integer) rs.getObject(idx[i]);
    + *             } catch (SQLException ex) {
    + *                 Logger.getLogger(Range.class.getName()).log(Level.SEVERE, null, ex);
    + *                 return false;
    + *             }
      *
    - *              if ((rs.getObject(idx[i]) >= lo[i]) &&
    - *                  (rs.getObject(idx[i]) >= hi[i]) {
    - *                  bool1 = true; // within filter constraints
    - *              } else {
    - *                  bool2 = true; // outside of filter constraints
    - *              }
    - *          }
    - *
    - *          if (bool2) {
    - *             return false;
    - *          } else {
    - *             return true;
    - *          }
    + *             if (value < lo[i] && value > hi[i]) {
    + *                 // outside of filter constraints
    + *                 return false;
    + *             }
    + *         }
    + *         // Within filter constraints
    + *        return true;
      *      }
    - *  }
    + *   }
      * }
    *

    * The example above implements a simple range predicate. Note, that - * implementations should but are not required to provider String + * implementations should but are not required to provide String * and integer index based constructors to provide for JDBC RowSet Implementation * applications that use both column identification conventions. *