From 386bef4e01536f98f3b44a8988f0dc0a352ccf8d Mon Sep 17 00:00:00 2001 From: Martin Balao Date: Wed, 24 Jul 2024 02:39:35 +0000 Subject: [PATCH 01/12] 8336499: Failure when creating non-CRT RSA private keys in SunPKCS11 Co-authored-by: Francisco Ferrari Bihurriet Co-authored-by: Martin Balao Reviewed-by: fferrari, valeriep --- .../classes/sun/security/pkcs11/P11Key.java | 100 +++++++++++------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java index 837a3a3dba1..d528f1b8485 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java @@ -561,47 +561,73 @@ static class P11RSAPrivateKeyInternal extends P11PrivateKey { static P11RSAPrivateKeyInternal of(Session session, long keyID, String algorithm, int keyLength, CK_ATTRIBUTE[] attrs, boolean keySensitive) { - if (keySensitive) { - return new P11RSAPrivateKeyInternal(session, keyID, algorithm, + P11RSAPrivateKeyInternal p11Key = null; + if (!keySensitive) { + // Key is not sensitive: try to interpret as CRT or non-CRT. + p11Key = asCRT(session, keyID, algorithm, keyLength, attrs); + if (p11Key == null) { + p11Key = asNonCRT(session, keyID, algorithm, keyLength, + attrs); + } + } + if (p11Key == null) { + // Key is sensitive or there was a failure while querying its + // attributes: handle as opaque. + p11Key = new P11RSAPrivateKeyInternal(session, keyID, algorithm, keyLength, attrs); - } else { - CK_ATTRIBUTE[] rsaAttrs = new CK_ATTRIBUTE[] { - new CK_ATTRIBUTE(CKA_MODULUS), - new CK_ATTRIBUTE(CKA_PRIVATE_EXPONENT), - new CK_ATTRIBUTE(CKA_PUBLIC_EXPONENT), - new CK_ATTRIBUTE(CKA_PRIME_1), - new CK_ATTRIBUTE(CKA_PRIME_2), - new CK_ATTRIBUTE(CKA_EXPONENT_1), - new CK_ATTRIBUTE(CKA_EXPONENT_2), - new CK_ATTRIBUTE(CKA_COEFFICIENT), - }; - boolean isCRT = true; - Session tempSession = null; - try { - tempSession = session.token.getOpSession(); - session.token.p11.C_GetAttributeValue(tempSession.id(), - keyID, rsaAttrs); - for (CK_ATTRIBUTE attr : rsaAttrs) { - isCRT &= (attr.pValue instanceof byte[]); - if (!isCRT) break; + } + return p11Key; + } + + private static CK_ATTRIBUTE[] tryFetchAttributes(Session session, + long keyID, long... attrTypes) { + int i = 0; + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[attrTypes.length]; + for (long attrType : attrTypes) { + attrs[i++] = new CK_ATTRIBUTE(attrType); + } + try { + session.token.p11.C_GetAttributeValue(session.id(), keyID, + attrs); + for (CK_ATTRIBUTE attr : attrs) { + if (!(attr.pValue instanceof byte[])) { + return null; } - } catch (PKCS11Exception e) { - // ignore, assume not available - isCRT = false; - } finally { - session.token.releaseSession(tempSession); - } - BigInteger n = rsaAttrs[0].getBigInteger(); - BigInteger d = rsaAttrs[1].getBigInteger(); - if (isCRT) { - return new P11RSAPrivateKey(session, keyID, algorithm, - keyLength, attrs, n, d, - Arrays.copyOfRange(rsaAttrs, 2, rsaAttrs.length)); - } else { - return new P11RSAPrivateNonCRTKey(session, keyID, - algorithm, keyLength, attrs, n, d); } + return attrs; + } catch (PKCS11Exception ignored) { + // ignore, assume not available + return null; + } + } + + private static P11RSAPrivateKeyInternal asCRT(Session session, + long keyID, String algorithm, int keyLength, + CK_ATTRIBUTE[] attrs) { + CK_ATTRIBUTE[] rsaCRTAttrs = tryFetchAttributes(session, keyID, + CKA_MODULUS, CKA_PRIVATE_EXPONENT, CKA_PUBLIC_EXPONENT, + CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, + CKA_COEFFICIENT); + if (rsaCRTAttrs == null) { + return null; + } + return new P11RSAPrivateKey(session, keyID, algorithm, keyLength, + attrs, rsaCRTAttrs[0].getBigInteger(), + rsaCRTAttrs[1].getBigInteger(), + Arrays.copyOfRange(rsaCRTAttrs, 2, rsaCRTAttrs.length)); + } + + private static P11RSAPrivateKeyInternal asNonCRT(Session session, + long keyID, String algorithm, int keyLength, + CK_ATTRIBUTE[] attrs) { + CK_ATTRIBUTE[] rsaNonCRTAttrs = tryFetchAttributes(session, keyID, + CKA_MODULUS, CKA_PRIVATE_EXPONENT); + if (rsaNonCRTAttrs == null) { + return null; } + return new P11RSAPrivateNonCRTKey(session, keyID, algorithm, + keyLength, attrs, rsaNonCRTAttrs[0].getBigInteger(), + rsaNonCRTAttrs[1].getBigInteger()); } protected transient BigInteger n; From 3f3f899b6e33b158dbe71cf8be0fc83ade7a7668 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 24 Jul 2024 07:21:45 +0000 Subject: [PATCH 02/12] 8336827: compiler/vectorization/TestFloat16VectorConvChain.java timeouts on ppc64 platforms after JDK-8335860 Reviewed-by: kvn, mdoerr, shade --- .../compiler/vectorization/TestFloat16VectorConvChain.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorConvChain.java b/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorConvChain.java index 9a2b9d6b848..384cc411d3e 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorConvChain.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorConvChain.java @@ -24,6 +24,8 @@ /** * @test * @summary Test Float16 vector conversion chain. +* @requires (vm.cpu.features ~= ".*avx512vl.*" | vm.cpu.features ~= ".*f16c.*") | os.arch == "aarch64" +* | (os.arch == "riscv64" & vm.cpu.features ~= ".*zfh.*") * @library /test/lib / * @run driver compiler.vectorization.TestFloat16VectorConvChain */ From 122622bb1645ffd6db0227af965f5a9f03c084dc Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 24 Jul 2024 12:13:15 +0000 Subject: [PATCH 03/12] 8336927: Missing equals and hashCode in java.lang.classfile.Annotation Reviewed-by: asotona --- .../classfile/impl/AnnotationImpl.java | 22 +++------------- test/jdk/jdk/classfile/AnnotationTest.java | 25 ++++++++++++++++++- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java index 8290fcd286e..03353e272a6 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AnnotationImpl.java @@ -32,24 +32,10 @@ import static java.lang.classfile.ClassFile.*; -public final class AnnotationImpl implements Annotation, Util.Writable { - private final Utf8Entry className; - private final List elements; - - public AnnotationImpl(Utf8Entry className, - List elems) { - this.className = className; - this.elements = List.copyOf(elems); - } - - @Override - public Utf8Entry className() { - return className; - } - - @Override - public List elements() { - return elements; +public record AnnotationImpl(Utf8Entry className, List elements) + implements Annotation, Util.Writable { + public AnnotationImpl { + elements = List.copyOf(elements); } @Override diff --git a/test/jdk/jdk/classfile/AnnotationTest.java b/test/jdk/jdk/classfile/AnnotationTest.java index 0226affe7c2..4ed3b2141ad 100644 --- a/test/jdk/jdk/classfile/AnnotationTest.java +++ b/test/jdk/jdk/classfile/AnnotationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -207,4 +207,27 @@ void testAnnosNoCPB() { assertAnno(mannos.get(0), "LAnno;", true); assertAnno(fannos.get(0), "LAnno;", true); } + + @Test + void testEquality() { + assertEquals(Annotation.of(CD_Object), Annotation.of(ClassDesc.of("java.lang.Object"))); + assertNotEquals(Annotation.of(CD_Object), Annotation.of(CD_String)); + assertEquals(Annotation.of(CD_Object, AnnotationElement.of("fly", AnnotationValue.ofInt(5))), + Annotation.of(CD_Object, AnnotationElement.ofInt("fly", 5))); + assertEquals(AnnotationElement.ofFloat("one", 1.2F), + AnnotationElement.ofFloat("one", 1.2F)); + assertEquals(AnnotationElement.ofFloat("one", 1.2F), + AnnotationElement.of("one", AnnotationValue.ofFloat(1.2F))); + assertNotEquals(AnnotationElement.ofFloat("one", 1.2F), + AnnotationElement.ofFloat("two", 1.2F)); + assertNotEquals(AnnotationElement.ofFloat("one", 1.2F), + AnnotationElement.ofFloat("one", 2.1F)); + assertNotEquals(AnnotationElement.ofFloat("one", 1.2F), + AnnotationElement.ofDouble("one", 1.2F)); + assertEquals(AnnotationValue.ofInt(23), AnnotationValue.ofInt(23)); + assertNotEquals(AnnotationValue.ofInt(23), AnnotationValue.ofInt(42)); + assertNotEquals(AnnotationValue.ofInt(23), AnnotationValue.ofLong(23)); + assertEquals(AnnotationValue.ofAnnotation(Annotation.of(CD_Object)), + AnnotationValue.ofAnnotation(Annotation.of(Object.class.describeConstable().orElseThrow()))); + } } From a32fbca139025b3d2d988bd70494a42318eae797 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Wed, 24 Jul 2024 15:12:53 +0000 Subject: [PATCH 04/12] 8336485: jdk/jfr/jcmd/TestJcmdView.java RuntimeException: 'Invoked Concurrent' missing from stdout/stderr Reviewed-by: mgronlun --- test/jdk/jdk/jfr/jcmd/TestJcmdView.java | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/jdk/jdk/jfr/jcmd/TestJcmdView.java b/test/jdk/jdk/jfr/jcmd/TestJcmdView.java index bf3e3f6ba88..44db54e7850 100644 --- a/test/jdk/jdk/jfr/jcmd/TestJcmdView.java +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -47,6 +47,7 @@ public class TestJcmdView { public static void main(String... args) throws Throwable { CountDownLatch jvmInformation = new CountDownLatch(1); CountDownLatch systemGC = new CountDownLatch(1); + CountDownLatch threadSleep = new CountDownLatch(1); CountDownLatch gcHeapSummary = new CountDownLatch(1); CountDownLatch oldCollection = new CountDownLatch(1); CountDownLatch garbageCollection = new CountDownLatch(1); @@ -56,6 +57,7 @@ public static void main(String... args) throws Throwable { rs.setMaxSize(Long.MAX_VALUE); rs.enable("jdk.JVMInformation").with("period", "beginChunk"); rs.enable("jdk.SystemGC"); + rs.enable("jdk.ThreadSleep").withoutThreshold().withStackTrace(); rs.enable("jdk.GCHeapSummary"); rs.enable("jdk.GarbageCollection"); rs.enable("jdk.OldGarbageCollection"); @@ -70,6 +72,11 @@ public static void main(String... args) throws Throwable { System.out.println(e); storeLastTimestamp(e); }); + rs.onEvent("jdk.ThreadSleep", e -> { + threadSleep.countDown(); + System.out.println(e); + storeLastTimestamp(e); + }); rs.onEvent("jdk.GCHeapSummary", e -> { gcHeapSummary.countDown(); System.out.println(e); @@ -90,9 +97,12 @@ public static void main(String... args) throws Throwable { System.gc(); System.gc(); System.gc(); + // Emit thread sleep event + Thread.sleep(1); // Wait for them being in the repository jvmInformation.await(); systemGC.await(); + threadSleep.await(); gcHeapSummary.await(); oldCollection.await(); // Wait for Instant.now() to advance 1 s past the last event timestamp. @@ -160,13 +170,13 @@ private static void testTableView() throws Throwable { private static void testEventType() throws Throwable { OutputAnalyzer output = JcmdHelper.jcmd( - "JFR.view", "verbose=true", "width=300", "cell-height=100", "SystemGC"); + "JFR.view", "verbose=true", "width=300", "cell-height=100", "ThreadSleep"); // Verify title - output.shouldContain("System GC"); + output.shouldContain("Thread Sleep"); // Verify headings - output.shouldContain("Invoked Concurrent"); + output.shouldContain("Sleep Time"); // Verify verbose headings - output.shouldContain("invokedConcurrent"); + output.shouldContain("time"); // Verify thread value output.shouldContain(Thread.currentThread().getName()); // Verify stack frame From a3ebf2699e2c96eb80f629c392aac067e16d957a Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Wed, 24 Jul 2024 15:13:02 +0000 Subject: [PATCH 05/12] 8336316: JFR: Use SettingControl::getValue() instead of setValue() for ActiveSetting event Reviewed-by: mgronlun --- .../share/classes/jdk/jfr/internal/Control.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java index 16f827a2df5..05c75a0e48e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, 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 @@ -108,15 +108,18 @@ public void setValue(String value) { // VM events requires no access control context try { delegate.setValue(value); + lastValue = delegate.getValue(); } catch (Throwable t) { Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occurred when setting value \"" + value + "\" for " + getClass()); + lastValue = null; } } else { - AccessController.doPrivileged(new PrivilegedAction() { + lastValue = AccessController.doPrivileged(new PrivilegedAction() { @Override - public Void run() { + public String run() { try { delegate.setValue(value); + return delegate.getValue(); } catch (Throwable t) { // Prevent malicious user to propagate exception callback in the wrong context Logger.log(LogTag.JFR_SETTING, LogLevel.WARN, "Exception occurred when setting value \"" + value + "\" for " + getClass()); @@ -125,7 +128,6 @@ public Void run() { } }, context); } - lastValue = value; } From 0a16eb7dbfa8a2f5884ddc925e1de65cb73bbcf8 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 24 Jul 2024 15:43:53 +0000 Subject: [PATCH 06/12] 8336815: Several methods in java.net.Socket and ServerSocket do not specify behavior when already bound, connected or closed Reviewed-by: alanb --- .../share/classes/java/net/ServerSocket.java | 31 +-- .../share/classes/java/net/Socket.java | 101 +++++----- .../ServerSocket/ClosedServerSocketTest.java | 123 ++++++++++++ .../jdk/java/net/Socket/ClosedSocketTest.java | 176 ++++++++++++++++++ 4 files changed, 367 insertions(+), 64 deletions(-) create mode 100644 test/jdk/java/net/ServerSocket/ClosedServerSocketTest.java create mode 100644 test/jdk/java/net/Socket/ClosedSocketTest.java diff --git a/src/java.base/share/classes/java/net/ServerSocket.java b/src/java.base/share/classes/java/net/ServerSocket.java index b3e570c858a..bb0fae2b88d 100644 --- a/src/java.base/share/classes/java/net/ServerSocket.java +++ b/src/java.base/share/classes/java/net/ServerSocket.java @@ -328,8 +328,8 @@ private SocketImpl getImpl() throws SocketException { * an ephemeral port and a valid local address to bind the socket. * * @param endpoint The IP address and port number to bind to. - * @throws IOException if the bind operation fails, or if the socket - * is already bound. + * @throws IOException if the bind operation fails, the socket + * is already bound or the socket is closed. * @throws SecurityException if a {@code SecurityManager} is present and * its {@code checkListen} method doesn't allow the operation. * @throws IllegalArgumentException if endpoint is a @@ -357,8 +357,8 @@ public void bind(SocketAddress endpoint) throws IOException { * @param endpoint The IP address and port number to bind to. * @param backlog requested maximum length of the queue of * incoming connections. - * @throws IOException if the bind operation fails, or if the socket - * is already bound. + * @throws IOException if the bind operation fails, the socket + * is already bound or the socket is closed. * @throws SecurityException if a {@code SecurityManager} is present and * its {@code checkListen} method doesn't allow the operation. * @throws IllegalArgumentException if endpoint is a @@ -518,7 +518,7 @@ public SocketAddress getLocalSocketAddress() { * client socket implementation factory}, if one has been set. * * @throws IOException if an I/O error occurs when waiting for a - * connection. + * connection, the socket is not bound or the socket is closed. * @throws SecurityException if a security manager exists and its * {@code checkAccept} method doesn't allow the operation. * @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and @@ -736,6 +736,9 @@ private void ensureCompatible(SocketImpl si) throws IOException { *

If this socket has an associated channel then the channel is closed * as well. * + *

Once closed, several of the methods defined by this class will throw + * an exception if invoked on the closed socket. + * * @throws IOException if an I/O error occurs when closing the socket. */ public void close() throws IOException { @@ -806,8 +809,8 @@ public boolean isClosed() { * operation to have effect. * * @param timeout the specified timeout, in milliseconds - * @throws SocketException if there is an error in the underlying protocol, - * such as a TCP error + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @throws IllegalArgumentException if {@code timeout} is negative * @since 1.1 * @see #getSoTimeout() @@ -824,7 +827,7 @@ public void setSoTimeout(int timeout) throws SocketException { * Retrieve setting for {@link SocketOptions#SO_TIMEOUT SO_TIMEOUT}. * 0 returns implies that the option is disabled (i.e., timeout of infinity). * @return the {@link SocketOptions#SO_TIMEOUT SO_TIMEOUT} value - * @throws IOException if an I/O error occurs + * @throws IOException if an I/O error occurs or the socket is closed. * @since 1.1 * @see #setSoTimeout(int) */ @@ -887,8 +890,8 @@ public void setReuseAddress(boolean on) throws SocketException { * * @return a {@code boolean} indicating whether or not * {@code SO_REUSEADDR} is enabled. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, such as a TCP error, + * or the socket is closed. * @since 1.4 * @see #setReuseAddress(boolean) */ @@ -1001,8 +1004,8 @@ public static synchronized void setSocketFactory(SocketImplFactory fac) throws I * requested value but the TCP receive window in sockets accepted from * this ServerSocket will be no larger than 64K bytes. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * * @param size the size to which to set the receive buffer * size. This value must be greater than 0. @@ -1029,8 +1032,8 @@ public void setReceiveBufferSize(int size) throws SocketException { *

Note, the value actually set in the accepted socket is determined by * calling {@link Socket#getReceiveBufferSize()}. * @return the value of the {@code SO_RCVBUF} option for this {@code Socket}. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @see #setReceiveBufferSize(int) * @since 1.4 */ diff --git a/src/java.base/share/classes/java/net/Socket.java b/src/java.base/share/classes/java/net/Socket.java index 99a7bb6ca52..23c225fbb2d 100644 --- a/src/java.base/share/classes/java/net/Socket.java +++ b/src/java.base/share/classes/java/net/Socket.java @@ -683,7 +683,8 @@ void setConnected() { * * * @param endpoint the {@code SocketAddress} - * @throws IOException if an error occurs during the connection + * @throws IOException if an error occurs during the connection, the socket + * is already connected or the socket is closed * @throws java.nio.channels.IllegalBlockingModeException * if this socket has an associated channel, * and the channel is in non-blocking mode @@ -717,7 +718,8 @@ public void connect(SocketAddress endpoint) throws IOException { * * @param endpoint the {@code SocketAddress} * @param timeout the timeout value to be used in milliseconds. - * @throws IOException if an error occurs during the connection + * @throws IOException if an error occurs during the connection, the socket + * is already connected or the socket is closed * @throws SocketTimeoutException if timeout expires before connecting * @throws java.nio.channels.IllegalBlockingModeException * if this socket has an associated channel, @@ -780,8 +782,8 @@ public void connect(SocketAddress endpoint, int timeout) throws IOException { * an ephemeral port and a valid local address to bind the socket. * * @param bindpoint the {@code SocketAddress} to bind to - * @throws IOException if the bind operation fails, or if the socket - * is already bound. + * @throws IOException if the bind operation fails, the socket + * is already bound or the socket is closed. * @throws IllegalArgumentException if bindpoint is a * SocketAddress subclass not supported by this socket * @throws SecurityException if a security manager exists and its @@ -1174,8 +1176,8 @@ public void close() throws IOException { * will close the associated socket. * * @return an output stream for writing bytes to this socket. - * @throws IOException if an I/O error occurs when creating the - * output stream or if the socket is not connected. + * @throws IOException if an I/O error occurs when creating the + * output stream, the socket is not connected or the socket is closed. */ public OutputStream getOutputStream() throws IOException { int s = state; @@ -1251,8 +1253,8 @@ public void close() throws IOException { * @param on {@code true} to enable {@code TCP_NODELAY}, * {@code false} to disable. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * * @since 1.1 * @@ -1269,8 +1271,8 @@ public void setTcpNoDelay(boolean on) throws SocketException { * * @return a {@code boolean} indicating whether or not * {@code TCP_NODELAY} is enabled. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @since 1.1 * @see #setTcpNoDelay(boolean) */ @@ -1289,9 +1291,9 @@ public boolean getTcpNoDelay() throws SocketException { * * @param on whether or not to linger on. * @param linger how long to linger for, if on is true. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. - * @throws IllegalArgumentException if the linger value is negative. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. + * @throws IllegalArgumentException if the linger value is negative. * @since 1.1 * @see #getSoLinger() */ @@ -1318,8 +1320,8 @@ public void setSoLinger(boolean on, int linger) throws SocketException { * The setting only affects socket close. * * @return the setting for {@code SO_LINGER}. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @since 1.1 * @see #setSoLinger(boolean, int) */ @@ -1368,8 +1370,8 @@ public void sendUrgentData(int data) throws IOException { * @param on {@code true} to enable {@code SO_OOBINLINE}, * {@code false} to disable. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * * @since 1.4 * @@ -1387,8 +1389,8 @@ public void setOOBInline(boolean on) throws SocketException { * @return a {@code boolean} indicating whether or not * {@code SO_OOBINLINE} is enabled. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @since 1.4 * @see #setOOBInline(boolean) */ @@ -1409,8 +1411,8 @@ public boolean getOOBInline() throws SocketException { * to have effect. * * @param timeout the specified timeout, in milliseconds. - * @throws SocketException if there is an error in the underlying protocol, - * such as a TCP error + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @throws IllegalArgumentException if {@code timeout} is negative * @since 1.1 * @see #getSoTimeout() @@ -1428,8 +1430,8 @@ public void setSoTimeout(int timeout) throws SocketException { * 0 returns implies that the option is disabled (i.e., timeout of infinity). * * @return the setting for {@code SO_TIMEOUT} - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * * @since 1.1 * @see #setSoTimeout(int) @@ -1455,14 +1457,12 @@ public int getSoTimeout() throws SocketException { *

Because {@code SO_SNDBUF} is a hint, applications that want to verify * what size the buffers were set to should call {@link #getSendBufferSize()}. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. - * * @param size the size to which to set the send buffer * size. This value must be greater than 0. * - * @throws IllegalArgumentException if the - * value is 0 or is negative. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. + * @throws IllegalArgumentException if the value is 0 or is negative. * * @see #getSendBufferSize() * @since 1.2 @@ -1481,8 +1481,8 @@ public void setSendBufferSize(int size) throws SocketException { * for output on this {@code Socket}. * @return the value of the {@code SO_SNDBUF} option for this {@code Socket}. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * * @see #setSendBufferSize(int) * @since 1.2 @@ -1529,8 +1529,8 @@ public int getSendBufferSize() throws SocketException { * @throws IllegalArgumentException if the value is 0 or is * negative. * - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * * @see #getReceiveBufferSize() * @see ServerSocket#setReceiveBufferSize(int) @@ -1550,8 +1550,8 @@ public void setReceiveBufferSize(int size) throws SocketException { * for input on this {@code Socket}. * * @return the value of the {@code SO_RCVBUF} option for this {@code Socket}. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @see #setReceiveBufferSize(int) * @since 1.2 */ @@ -1570,8 +1570,8 @@ public int getReceiveBufferSize() throws SocketException { * Enable/disable {@link StandardSocketOptions#SO_KEEPALIVE SO_KEEPALIVE}. * * @param on whether or not to have socket keep alive turned on. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @since 1.3 * @see #getKeepAlive() */ @@ -1586,8 +1586,8 @@ public void setKeepAlive(boolean on) throws SocketException { * * @return a {@code boolean} indicating whether or not * {@code SO_KEEPALIVE} is enabled. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @since 1.3 * @see #setKeepAlive(boolean) */ @@ -1637,8 +1637,8 @@ public boolean getKeepAlive() throws SocketException { * would be placed into the sin6_flowinfo field of the IP header. * * @param tc an {@code int} value for the bitset. - * @throws SocketException if there is an error setting the - * traffic class or type-of-service + * @throws SocketException if there is an error setting the traffic class or type-of-service, + * or the socket is closed. * @since 1.4 * @see #getTrafficClass * @see StandardSocketOptions#IP_TOS @@ -1661,8 +1661,8 @@ public void setTrafficClass(int tc) throws SocketException { * set using the {@link #setTrafficClass(int)} method on this Socket. * * @return the traffic class or type-of-service already set - * @throws SocketException if there is an error obtaining the - * traffic class or type-of-service value. + * @throws SocketException if there is an error obtaining the traffic class + * or type-of-service value, or the socket is closed. * @since 1.4 * @see #setTrafficClass(int) * @see StandardSocketOptions#IP_TOS @@ -1715,8 +1715,8 @@ public void setReuseAddress(boolean on) throws SocketException { * * @return a {@code boolean} indicating whether or not * {@code SO_REUSEADDR} is enabled. - * @throws SocketException if there is an error - * in the underlying protocol, such as a TCP error. + * @throws SocketException if there is an error in the underlying protocol, + * such as a TCP error, or the socket is closed. * @since 1.4 * @see #setReuseAddress(boolean) */ @@ -1733,8 +1733,9 @@ public boolean getReuseAddress() throws SocketException { * will throw a {@link SocketException}. *

* Once a socket has been closed, it is not available for further networking - * use (i.e. can't be reconnected or rebound). A new socket needs to be - * created. + * use (i.e. can't be reconnected or rebound) and several of the methods defined + * by this class will throw an exception if invoked on the closed socket. A new + * socket needs to be created. * *

Closing this socket will also close the socket's * {@link java.io.InputStream InputStream} and @@ -1767,8 +1768,8 @@ public void close() throws IOException { * socket, the stream's {@code available} method will return 0, and its * {@code read} methods will return {@code -1} (end of stream). * - * @throws IOException if an I/O error occurs when shutting down this - * socket. + * @throws IOException if an I/O error occurs when shutting down this socket, the + * socket is not connected or the socket is closed. * * @since 1.3 * @see java.net.Socket#shutdownOutput() @@ -1797,8 +1798,8 @@ public void shutdownInput() throws IOException { * shutdownOutput() on the socket, the stream will throw * an IOException. * - * @throws IOException if an I/O error occurs when shutting down this - * socket. + * @throws IOException if an I/O error occurs when shutting down this socket, the socket + * is not connected or the socket is closed. * * @since 1.3 * @see java.net.Socket#shutdownInput() diff --git a/test/jdk/java/net/ServerSocket/ClosedServerSocketTest.java b/test/jdk/java/net/ServerSocket/ClosedServerSocketTest.java new file mode 100644 index 00000000000..81d5cc9c0d0 --- /dev/null +++ b/test/jdk/java/net/ServerSocket/ClosedServerSocketTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2024, 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.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketException; +import java.net.StandardSocketOptions; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @summary verifies that the APIs on java.net.ServerSocket throw expected exceptions + * when invoked on a closed ServerSocket + * @run junit ClosedServerSocketTest + */ +public class ClosedServerSocketTest { + + private static final InetAddress loopback = InetAddress.getLoopbackAddress(); + private static final InetSocketAddress loopbackEphemeral = new InetSocketAddress(loopback, 0); + + /** + * Verifies that various operations that specify to throw an IOException on a + * closed ServerSocket, do indeed throw it. + */ + @Test + public void testIOExceptionThrown() throws Exception { + try (final ServerSocket ss = new ServerSocket()) { + // close and then invoke the operations on the ServerSocket + ss.close(); + assertTrue(ss.isClosed(), "ServerSocket isn't closed"); + assertThrows(IOException.class, + ss::accept, + "accept() when already closed didn't throw IOException"); + assertThrows(IOException.class, + () -> ss.bind(loopbackEphemeral), + "bind() when already closed didn't throw IOException"); + assertThrows(IOException.class, + () -> ss.bind(loopbackEphemeral, 10), + "bind(SocketAddress, int) when already closed didn't throw IOException"); + assertThrows(IOException.class, + () -> ss.getOption(StandardSocketOptions.SO_RCVBUF), + "getOption() when already closed didn't throw IOException"); + assertThrows(IOException.class, + ss::getSoTimeout, + "getSoTimeout() when already closed didn't throw IOException"); + assertThrows(IOException.class, + () -> ss.setOption(StandardSocketOptions.SO_RCVBUF, 1024), + "setOption() when already closed didn't throw IOException"); + } + } + + /** + * Verifies that various operations that specify to throw a SocketOperation on a + * closed ServerSocket, do indeed throw it. + */ + @Test + public void testSocketExceptionThrown() throws Exception { + try (final ServerSocket ss = new ServerSocket()) { + // close and then invoke the operations on the ServerSocket + ss.close(); + assertTrue(ss.isClosed(), "ServerSocket isn't closed"); + assertThrowsExactly(SocketException.class, + ss::getReceiveBufferSize, + "getReceiveBufferSize() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + ss::getReuseAddress, + "getReuseAddress() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> ss.setReceiveBufferSize(1024), + "setReceiveBufferSize() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> ss.setReuseAddress(false), + "setReuseAddress() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> ss.setSoTimeout(1000), + "setSoTimeout() when already closed didn't throw SocketException"); + } + } + + /** + * Verifies that various operations that aren't expected to throw an exception on a + * closed ServerSocket, complete normally. + */ + @Test + public void testNoExceptionThrown() throws Exception { + try (final ServerSocket ss = new ServerSocket()) { + // close and then invoke the operations on the ServerSocket + ss.close(); + assertTrue(ss.isClosed(), "ServerSocket isn't closed"); + ss.getInetAddress(); + ss.getLocalPort(); + ss.getLocalSocketAddress(); + ss.isBound(); + ss.supportedOptions(); + } + } +} diff --git a/test/jdk/java/net/Socket/ClosedSocketTest.java b/test/jdk/java/net/Socket/ClosedSocketTest.java new file mode 100644 index 00000000000..794f3b9cb18 --- /dev/null +++ b/test/jdk/java/net/Socket/ClosedSocketTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024, 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.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.StandardSocketOptions; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @summary verifies that the APIs on java.net.Socket throw expected exceptions + * when invoked on a closed socket + * @run junit ClosedSocketTest + */ +public class ClosedSocketTest { + + private static final InetAddress loopback = InetAddress.getLoopbackAddress(); + private static final InetSocketAddress loopbackEphemeral = new InetSocketAddress(loopback, 0); + + /** + * Verifies that various operations that specify to throw an IOException on a closed socket, + * do indeed throw it. + */ + @Test + public void testIOExceptionThrown() throws Exception { + try (final Socket s = new Socket()) { + // close and then invoke the operation on the socket + s.close(); + assertTrue(s.isClosed(), "socket isn't closed"); + assertThrows(IOException.class, () -> s.bind(loopbackEphemeral), + "bind() when already closed didn't throw IOException"); + // connect() will never get to the stage of attempting + // a connection against this port + final int dummyPort = 12345; + assertThrows(IOException.class, + () -> s.connect(new InetSocketAddress(loopback, dummyPort)), + "connect() when already closed didn't throw IOException"); + assertThrows(IOException.class, + () -> s.connect(new InetSocketAddress(loopback, dummyPort), 10), + "connect(SocketAddress, int) when already closed didn't throw IOException"); + assertThrows(IOException.class, + () -> s.getOption(StandardSocketOptions.SO_RCVBUF), + "getOption() when already closed didn't throw IOException"); + assertThrows(IOException.class, + s::getOutputStream, + "getOutputStream() when already closed didn't throw IOException"); + assertThrows(IOException.class, + s::shutdownInput, + "shutdownInput() when already closed didn't throw IOException"); + assertThrows(IOException.class, + s::shutdownOutput, + "shutdownOutput() when already closed didn't throw IOException"); + } + } + + /** + * Verifies that various operations that specify to throw a SocketOperation on a closed socket, + * do indeed throw it. + */ + @Test + public void testSocketExceptionThrown() throws Exception { + try (final Socket s = new Socket()) { + // close and then invoke the operations on the socket + s.close(); + assertTrue(s.isClosed(), "socket isn't closed"); + assertThrowsExactly(SocketException.class, + s::getKeepAlive, + "getKeepAlive() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getOOBInline, + "getOOBInline() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getReceiveBufferSize, + "getReceiveBufferSize() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getReuseAddress, + "getReuseAddress() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getSendBufferSize, + "getSendBufferSize() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getSoLinger, + "getSoLinger() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getSoTimeout, + "getSoTimeout() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getTcpNoDelay, + "getTcpNoDelay() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + s::getTrafficClass, + "getTrafficClass() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setKeepAlive(false), + "setKeepAlive() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setOOBInline(false), + "setOOBInline() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setOption(StandardSocketOptions.SO_RCVBUF, 1024), + "setOption() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setReceiveBufferSize(1024), + "setReceiveBufferSize() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setReuseAddress(false), + "setReuseAddress() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setSendBufferSize(1024), + "setSendBufferSize() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setSoLinger(false, 0), + "setSoLinger() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setSoTimeout(1000), + "setSoTimeout() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setTcpNoDelay(false), + "setTcpNoDelay() when already closed didn't throw SocketException"); + assertThrowsExactly(SocketException.class, + () -> s.setTrafficClass(123), + "setTrafficClass() when already closed didn't throw SocketException"); + } + } + + /** + * Verifies that various operations that aren't expected to throw an exception on a + * closed socket, complete normally. + */ + @Test + public void testNoExceptionThrown() throws Exception { + try (final Socket s = new Socket()) { + // close and then invoke various operation on the socket and don't expect an exception + s.close(); + assertTrue(s.isClosed(), "socket isn't closed"); + s.getInetAddress(); + s.getLocalAddress(); + s.getLocalPort(); + s.getLocalSocketAddress(); + s.getPort(); + s.getRemoteSocketAddress(); + s.isBound(); + s.isConnected(); + s.isInputShutdown(); + s.isOutputShutdown(); + s.supportedOptions(); + } + } +} From 9de474a2341eafd49c308869cc3cbcc3581b9eb3 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Wed, 24 Jul 2024 16:48:34 +0000 Subject: [PATCH 07/12] 8336679: Add @implSpec for the default implementations in Process.waitFor() Reviewed-by: bpb, jlu, liach --- .../share/classes/java/lang/Process.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Process.java b/src/java.base/share/classes/java/lang/Process.java index a01a1f93fd2..af6753a236b 100644 --- a/src/java.base/share/classes/java/lang/Process.java +++ b/src/java.base/share/classes/java/lang/Process.java @@ -442,10 +442,13 @@ public final BufferedWriter outputWriter(Charset charset) { * terminated and the timeout value is less than, or equal to, zero, then * this method returns immediately with the value {@code false}. * - *

The default implementation of this method polls the {@code exitValue} - * to check if the process has terminated. Concrete implementations of this - * class are strongly encouraged to override this method with a more - * efficient implementation. + * @implSpec + * The default implementation of this method polls the {@code exitValue} + * to check if the process has terminated. + * + * @implNote + * Concrete implementations of this class are strongly encouraged to + * override this method with a more efficient implementation. * * @param timeout the maximum time to wait * @param unit the time unit of the {@code timeout} argument @@ -486,10 +489,13 @@ public boolean waitFor(long timeout, TimeUnit unit) * terminated and the duration is not positive, then * this method returns immediately with the value {@code false}. * - *

The default implementation of this method polls the {@code exitValue} - * to check if the process has terminated. Concrete implementations of this - * class are strongly encouraged to override this method with a more - * efficient implementation. + * @implSpec + * The default implementation of this method polls the {@code exitValue} + * to check if the process has terminated. + * + * @implNote + * Concrete implementations of this class are strongly encouraged to + * override this method with a more efficient implementation. * * @param duration the maximum duration to wait; if not positive, * this method returns immediately. From d393ab6bf5939d563222c0adf8927d8180466b7f Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 24 Jul 2024 20:08:13 +0000 Subject: [PATCH 08/12] 8336787: Examine java.text.Format API for implSpec usage Reviewed-by: liach --- src/java.base/share/classes/java/text/Format.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/text/Format.java b/src/java.base/share/classes/java/text/Format.java index 6aa6b866bae..9926bbfbb15 100644 --- a/src/java.base/share/classes/java/text/Format.java +++ b/src/java.base/share/classes/java/text/Format.java @@ -149,12 +149,12 @@ protected Format() { /** * Formats an object to produce a string. - * This method returns a string that would be equal to the string returned by + * + * @implSpec This method returns a string that would be equal to the string returned by *

* {@link #format(Object, StringBuffer, FieldPosition) format}(obj, * new StringBuffer(), new FieldPosition(0)).toString(); *
- * * @param obj The object to format * @return Formatted string. * @throws IllegalArgumentException if the Format cannot format the given @@ -207,11 +207,11 @@ StringBuf format(Object obj, * to define what the legal values are for each attribute in the * {@code AttributedCharacterIterator}, but typically the attribute * key is also used as the attribute value. - *

The default implementation creates an - * {@code AttributedCharacterIterator} with no attributes. Subclasses - * that support fields should override this and create an - * {@code AttributedCharacterIterator} with meaningful attributes. * + * @apiNote Subclasses that support fields should override this and create an + * {@code AttributedCharacterIterator} with meaningful attributes. + * @implSpec The default implementation creates an + * {@code AttributedCharacterIterator} with no attributes. * @throws NullPointerException if obj is null. * @throws IllegalArgumentException when the Format cannot format the * given object. From ea968c84deca16d6ffb6d1a24fb301d6e0bc5199 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 24 Jul 2024 20:14:00 +0000 Subject: [PATCH 09/12] 8336847: Use pattern match switch in NumberFormat classes Reviewed-by: liach, naoto --- .../java/text/CompactNumberFormat.java | 104 +++++++--------- .../classes/java/text/DecimalFormat.java | 113 ++++++++---------- .../share/classes/java/text/NumberFormat.java | 44 +++---- 3 files changed, 118 insertions(+), 143 deletions(-) diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java index e3fc7632535..0cb5eb88079 100644 --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java @@ -539,58 +539,42 @@ public CompactNumberFormat(String decimalPattern, public final StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition fieldPosition) { - - if (number == null) { - throw new IllegalArgumentException("Cannot format null as a number"); - } - - if (number instanceof Long || number instanceof Integer - || number instanceof Short || number instanceof Byte - || number instanceof AtomicInteger - || number instanceof AtomicLong - || (number instanceof BigInteger - && ((BigInteger) number).bitLength() < 64)) { - return format(((Number) number).longValue(), toAppendTo, - fieldPosition); - } else if (number instanceof BigDecimal) { - return format((BigDecimal) number, StringBufFactory.of(toAppendTo), fieldPosition).asStringBuffer(); - } else if (number instanceof BigInteger) { - return format((BigInteger) number, StringBufFactory.of(toAppendTo), fieldPosition).asStringBuffer(); - } else if (number instanceof Number) { - return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); - } else { - throw new IllegalArgumentException("Cannot format " - + number.getClass().getName() + " as a number"); - } + return switch (number) { + case Long l -> format(l.longValue(), toAppendTo, fieldPosition); + case Integer i -> format(i.longValue(), toAppendTo, fieldPosition); + case Short s -> format(s.longValue(), toAppendTo, fieldPosition); + case Byte b -> format(b.longValue(), toAppendTo, fieldPosition); + case AtomicInteger ai -> format(ai.longValue(), toAppendTo, fieldPosition); + case AtomicLong al -> format(al.longValue(), toAppendTo, fieldPosition); + case BigInteger bi when bi.bitLength() < 64 -> format(bi.longValue(), toAppendTo, fieldPosition); + case BigDecimal bd -> format(bd, StringBufFactory.of(toAppendTo), fieldPosition).asStringBuffer(); + case BigInteger bi -> format(bi, StringBufFactory.of(toAppendTo), fieldPosition).asStringBuffer(); + case Number n -> format(n.doubleValue(), toAppendTo, fieldPosition); + case null -> throw new IllegalArgumentException("Cannot format null as a number"); + default -> throw new IllegalArgumentException( + String.format("Cannot format %s as a number", number.getClass().getName())); + }; } @Override StringBuf format(Object number, StringBuf toAppendTo, FieldPosition fieldPosition) { - - if (number == null) { - throw new IllegalArgumentException("Cannot format null as a number"); - } - - if (number instanceof Long || number instanceof Integer - || number instanceof Short || number instanceof Byte - || number instanceof AtomicInteger - || number instanceof AtomicLong - || (number instanceof BigInteger - && ((BigInteger) number).bitLength() < 64)) { - return format(((Number) number).longValue(), toAppendTo, - fieldPosition); - } else if (number instanceof BigDecimal) { - return format((BigDecimal) number, toAppendTo, fieldPosition); - } else if (number instanceof BigInteger) { - return format((BigInteger) number, toAppendTo, fieldPosition); - } else if (number instanceof Number) { - return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); - } else { - throw new IllegalArgumentException("Cannot format " - + number.getClass().getName() + " as a number"); - } + return switch (number) { + case Long l -> format(l.longValue(), toAppendTo, fieldPosition); + case Integer i -> format(i.longValue(), toAppendTo, fieldPosition); + case Short s -> format(s.longValue(), toAppendTo, fieldPosition); + case Byte b -> format(b.longValue(), toAppendTo, fieldPosition); + case AtomicInteger ai -> format(ai.longValue(), toAppendTo, fieldPosition); + case AtomicLong al -> format(al.longValue(), toAppendTo, fieldPosition); + case BigInteger bi when bi.bitLength() < 64 -> format(bi.longValue(), toAppendTo, fieldPosition); + case BigDecimal bd -> format(bd, toAppendTo, fieldPosition); + case BigInteger bi -> format(bi, toAppendTo, fieldPosition); + case Number n -> format(n.doubleValue(), toAppendTo, fieldPosition); + case null -> throw new IllegalArgumentException("Cannot format null as a number"); + default -> throw new IllegalArgumentException( + String.format("Cannot format %s as a number", number.getClass().getName())); + }; } /** @@ -1182,22 +1166,20 @@ public AttributedCharacterIterator formatToCharacterIterator(Object obj) { CharacterIteratorFieldDelegate delegate = new CharacterIteratorFieldDelegate(); StringBuf sb = StringBufFactory.of(); - - if (obj instanceof Double || obj instanceof Float) { - format(((Number) obj).doubleValue(), sb, delegate); - } else if (obj instanceof Long || obj instanceof Integer - || obj instanceof Short || obj instanceof Byte - || obj instanceof AtomicInteger || obj instanceof AtomicLong) { - format(((Number) obj).longValue(), sb, delegate); - } else if (obj instanceof BigDecimal) { - format((BigDecimal) obj, sb, delegate); - } else if (obj instanceof BigInteger) { - format((BigInteger) obj, sb, delegate, false); - } else if (obj == null) { - throw new NullPointerException( + switch (obj) { + case Double d -> format(d.doubleValue(), sb, delegate); + case Float f -> format(f.doubleValue(), sb, delegate); + case Long l -> format(l.longValue(), sb, delegate); + case Integer i -> format(i.longValue(), sb, delegate); + case Short s -> format(s.longValue(), sb, delegate); + case Byte b -> format(b.longValue(), sb, delegate); + case AtomicInteger ai -> format(ai.longValue(), sb, delegate); + case AtomicLong al -> format(al.longValue(), sb, delegate); + case BigDecimal bd -> format(bd, sb, delegate); + case BigInteger bi -> format(bi, sb, delegate, false); + case null -> throw new NullPointerException( "formatToCharacterIterator must be passed non-null object"); - } else { - throw new IllegalArgumentException( + default -> throw new IllegalArgumentException( "Cannot format given Object as a Number"); } return delegate.getIterator(sb.toString()); diff --git a/src/java.base/share/classes/java/text/DecimalFormat.java b/src/java.base/share/classes/java/text/DecimalFormat.java index cc8e9e8662c..07a1c6932e7 100644 --- a/src/java.base/share/classes/java/text/DecimalFormat.java +++ b/src/java.base/share/classes/java/text/DecimalFormat.java @@ -548,44 +548,38 @@ public DecimalFormat (String pattern, DecimalFormatSymbols symbols) { public final StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) { - if (number instanceof Long || number instanceof Integer || - number instanceof Short || number instanceof Byte || - number instanceof AtomicInteger || - number instanceof AtomicLong || - (number instanceof BigInteger && - ((BigInteger)number).bitLength () < 64)) { - return format(((Number)number).longValue(), toAppendTo, pos); - } else if (number instanceof BigDecimal) { - return format((BigDecimal)number, StringBufFactory.of(toAppendTo), pos).asStringBuffer(); - } else if (number instanceof BigInteger) { - return format((BigInteger)number, StringBufFactory.of(toAppendTo), pos).asStringBuffer(); - } else if (number instanceof Number) { - return format(((Number)number).doubleValue(), toAppendTo, pos); - } else { - throw new IllegalArgumentException("Cannot format given Object as a Number"); - } + return switch (number) { + case Long l -> format(l.longValue(), toAppendTo, pos); + case Integer i -> format(i.longValue(), toAppendTo, pos); + case Short s -> format(s.longValue(), toAppendTo, pos); + case Byte b -> format(b.longValue(), toAppendTo, pos); + case AtomicInteger ai -> format(ai.longValue(), toAppendTo, pos); + case AtomicLong al -> format(al.longValue(), toAppendTo, pos); + case BigInteger bi when bi.bitLength() < 64 -> format(bi.longValue(), toAppendTo, pos); + case BigDecimal bd -> format(bd, StringBufFactory.of(toAppendTo), pos).asStringBuffer(); + case BigInteger bi -> format(bi, StringBufFactory.of(toAppendTo), pos).asStringBuffer(); + case Number n -> format(n.doubleValue(), toAppendTo, pos); + case null, default -> throw new IllegalArgumentException("Cannot format given Object as a Number"); + }; } @Override final StringBuf format(Object number, StringBuf toAppendTo, FieldPosition pos) { - if (number instanceof Long || number instanceof Integer || - number instanceof Short || number instanceof Byte || - number instanceof AtomicInteger || - number instanceof AtomicLong || - (number instanceof BigInteger && - ((BigInteger) number).bitLength() < 64)) { - return format(((Number) number).longValue(), toAppendTo, pos); - } else if (number instanceof BigDecimal) { - return format((BigDecimal) number, toAppendTo, pos); - } else if (number instanceof BigInteger) { - return format((BigInteger) number, toAppendTo, pos); - } else if (number instanceof Number) { - return format(((Number) number).doubleValue(), toAppendTo, pos); - } else { - throw new IllegalArgumentException("Cannot format given Object as a Number"); - } + return switch (number) { + case Long l -> format(l.longValue(), toAppendTo, pos); + case Integer i -> format(i.longValue(), toAppendTo, pos); + case Short s -> format(s.longValue(), toAppendTo, pos); + case Byte b -> format(b.longValue(), toAppendTo, pos); + case AtomicInteger ai -> format(ai.longValue(), toAppendTo, pos); + case AtomicLong al -> format(al.longValue(), toAppendTo, pos); + case BigInteger bi when bi.bitLength() < 64 -> format(bi.longValue(), toAppendTo, pos); + case BigDecimal bd -> format(bd, toAppendTo, pos); + case BigInteger bi -> format(bi, toAppendTo, pos); + case Number n -> format(n.doubleValue(), toAppendTo, pos); + case null, default -> throw new IllegalArgumentException("Cannot format given Object as a Number"); + }; } /** @@ -1021,25 +1015,23 @@ StringBuf format(BigInteger number, StringBuf result, @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { CharacterIteratorFieldDelegate delegate = - new CharacterIteratorFieldDelegate(); + new CharacterIteratorFieldDelegate(); StringBuf sb = StringBufFactory.of(); - - if (obj instanceof Double || obj instanceof Float) { - format(((Number)obj).doubleValue(), sb, delegate); - } else if (obj instanceof Long || obj instanceof Integer || - obj instanceof Short || obj instanceof Byte || - obj instanceof AtomicInteger || obj instanceof AtomicLong) { - format(((Number)obj).longValue(), sb, delegate); - } else if (obj instanceof BigDecimal) { - format((BigDecimal)obj, sb, delegate); - } else if (obj instanceof BigInteger) { - format((BigInteger)obj, sb, delegate, false); - } else if (obj == null) { - throw new NullPointerException( - "formatToCharacterIterator must be passed non-null object"); - } else { - throw new IllegalArgumentException( - "Cannot format given Object as a Number"); + switch (obj) { + case Double d -> format(d.doubleValue(), sb, delegate); + case Float f -> format(f.doubleValue(), sb, delegate); + case Long l -> format(l.longValue(), sb, delegate); + case Integer i -> format(i.longValue(), sb, delegate); + case Short s -> format(s.longValue(), sb, delegate); + case Byte b -> format(b.longValue(), sb, delegate); + case AtomicInteger ai -> format(ai.longValue(), sb, delegate); + case AtomicLong al -> format(al.longValue(), sb, delegate); + case BigDecimal bd -> format(bd, sb, delegate); + case BigInteger bi -> format(bi, sb, delegate, false); + case null -> throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + default -> throw new IllegalArgumentException( + "Cannot format given Object as a Number"); } return delegate.getIterator(sb.toString()); } @@ -1790,22 +1782,23 @@ String fastFormat(double d) { } /** - * Sets the {@code DigitList} used by this {@code DecimalFormat} + * Utility method that sets the {@code DigitList} used by this {@code DecimalFormat} * instance. + * * @param number the number to format * @param isNegative true, if the number is negative; false otherwise * @param maxDigits the max digits + * @throws AssertionError if provided a Number subclass that is not supported + * by {@code DigitList} */ void setDigitList(Number number, boolean isNegative, int maxDigits) { - - if (number instanceof Double) { - digitList.set(isNegative, (Double) number, maxDigits, true); - } else if (number instanceof BigDecimal) { - digitList.set(isNegative, (BigDecimal) number, maxDigits, true); - } else if (number instanceof Long) { - digitList.set(isNegative, (Long) number, maxDigits); - } else if (number instanceof BigInteger) { - digitList.set(isNegative, (BigInteger) number, maxDigits); + switch (number) { + case Double d -> digitList.set(isNegative, d, maxDigits, true); + case BigDecimal bd -> digitList.set(isNegative, bd, maxDigits, true); + case Long l -> digitList.set(isNegative, l, maxDigits); + case BigInteger bi -> digitList.set(isNegative, bi, maxDigits); + default -> throw new AssertionError( + String.format("DigitList does not support %s", number.getClass().getName())); } } diff --git a/src/java.base/share/classes/java/text/NumberFormat.java b/src/java.base/share/classes/java/text/NumberFormat.java index b905f3bd8af..e397d4ba15d 100644 --- a/src/java.base/share/classes/java/text/NumberFormat.java +++ b/src/java.base/share/classes/java/text/NumberFormat.java @@ -302,34 +302,34 @@ protected NumberFormat() { public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) { - if (number instanceof Long || number instanceof Integer || - number instanceof Short || number instanceof Byte || - number instanceof AtomicInteger || number instanceof AtomicLong || - (number instanceof BigInteger && - ((BigInteger)number).bitLength() < 64)) { - return format(((Number)number).longValue(), toAppendTo, pos); - } else if (number instanceof Number) { - return format(((Number)number).doubleValue(), toAppendTo, pos); - } else { - throw new IllegalArgumentException("Cannot format given Object as a Number"); - } + return switch (number) { + case Long l -> format(l.longValue(), toAppendTo, pos); + case Integer i -> format(i.longValue(), toAppendTo, pos); + case Short s -> format(s.longValue(), toAppendTo, pos); + case Byte b -> format(b.longValue(), toAppendTo, pos); + case AtomicInteger ai -> format(ai.longValue(), toAppendTo, pos); + case AtomicLong al -> format(al.longValue(), toAppendTo, pos); + case BigInteger bi when bi.bitLength() < 64 -> format(bi.longValue(), toAppendTo, pos); + case Number n -> format(n.doubleValue(), toAppendTo, pos); + case null, default -> throw new IllegalArgumentException("Cannot format given Object as a Number"); + }; } @Override StringBuf format(Object number, StringBuf toAppendTo, FieldPosition pos) { - if (number instanceof Long || number instanceof Integer || - number instanceof Short || number instanceof Byte || - number instanceof AtomicInteger || number instanceof AtomicLong || - (number instanceof BigInteger && - ((BigInteger) number).bitLength() < 64)) { - return format(((Number) number).longValue(), toAppendTo, pos); - } else if (number instanceof Number) { - return format(((Number) number).doubleValue(), toAppendTo, pos); - } else { - throw new IllegalArgumentException("Cannot format given Object as a Number"); - } + return switch (number) { + case Long l -> format(l.longValue(), toAppendTo, pos); + case Integer i -> format(i.longValue(), toAppendTo, pos); + case Short s -> format(s.longValue(), toAppendTo, pos); + case Byte b -> format(b.longValue(), toAppendTo, pos); + case AtomicInteger ai -> format(ai.longValue(), toAppendTo, pos); + case AtomicLong al -> format(al.longValue(), toAppendTo, pos); + case BigInteger bi when bi.bitLength() < 64 -> format(bi.longValue(), toAppendTo, pos); + case Number n -> format(n.doubleValue(), toAppendTo, pos); + case null, default -> throw new IllegalArgumentException("Cannot format given Object as a Number"); + }; } /** From d76e43e6a085372b2e0f851eaef37b3d54174ff1 Mon Sep 17 00:00:00 2001 From: Andrey Turbanov Date: Wed, 24 Jul 2024 20:26:11 +0000 Subject: [PATCH 10/12] 8336755: Remove unused UNALIGNED field from view buffers Reviewed-by: alanb, liach, bpb --- .../classes/java/nio/Direct-X-Buffer.java.template | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template index e8551bd78c6..6c0da829331 100644 --- a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template +++ b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template @@ -56,14 +56,6 @@ class Direct$Type$Buffer$RW$$BO$ { #if[rw] - - // Cached unaligned-access capability - protected static final boolean UNALIGNED = Bits.unaligned(); - - // Base address, used in all indexing calculations - // NOTE: moved up to Buffer.java for speed in JNI GetDirectBufferAddress - // protected long address; - // An object attached to this buffer. If this buffer is a view of another // buffer then we use this field to keep a reference to that buffer to // ensure that its memory isn't freed before we are done with it. @@ -74,6 +66,8 @@ class Direct$Type$Buffer$RW$$BO$ } #if[byte] + // Cached unaligned-access capability + static final boolean UNALIGNED = Bits.unaligned(); private record Deallocator(long address, long size, int capacity) implements Runnable { private Deallocator { From 1ab97b78b7b00428afe52529b882ca8ea604b9c8 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Wed, 24 Jul 2024 21:15:18 +0000 Subject: [PATCH 11/12] 8337067: Test runtime/classFileParserBug/Bad_NCDFE_Msg.java won't compile Reviewed-by: lfoltan --- .../jtreg/runtime/classFileParserBug/Bad_NCDFE_Msg.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/classFileParserBug/Bad_NCDFE_Msg.java b/test/hotspot/jtreg/runtime/classFileParserBug/Bad_NCDFE_Msg.java index 1baf5bdc8a8..a69a3746192 100644 --- a/test/hotspot/jtreg/runtime/classFileParserBug/Bad_NCDFE_Msg.java +++ b/test/hotspot/jtreg/runtime/classFileParserBug/Bad_NCDFE_Msg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -31,6 +31,7 @@ * java.management * @compile C.java * @run driver Bad_NCDFE_Msg + */ import java.io.File; import jdk.test.lib.process.ProcessTools; From 7b66ba3a98729f379b39022a4b59e52cb1d99850 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Wed, 24 Jul 2024 22:39:49 +0000 Subject: [PATCH 12/12] 8336741: Optimize LocalTime.toString with StringBuilder.repeat Reviewed-by: liach, rriggs, naoto --- .../share/classes/java/time/LocalTime.java | 18 ++- .../jdk/internal/util/DecimalDigits.java | 28 +++- .../bench/java/time/ToStringBench.java | 124 ++++++++++++++++++ 3 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/time/ToStringBench.java diff --git a/src/java.base/share/classes/java/time/LocalTime.java b/src/java.base/share/classes/java/time/LocalTime.java index 499dca627e2..fd2c5ce4d45 100644 --- a/src/java.base/share/classes/java/time/LocalTime.java +++ b/src/java.base/share/classes/java/time/LocalTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024, 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,8 @@ import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A time without a time-zone in the ISO-8601 calendar system, * such as {@code 10:15:30}. @@ -1640,13 +1642,19 @@ public String toString() { buf.append(secondValue < 10 ? ":0" : ":").append(secondValue); if (nanoValue > 0) { buf.append('.'); - if (nanoValue % 1000_000 == 0) { - buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1)); + int zeros = 9 - DecimalDigits.stringSize(nanoValue); + if (zeros > 0) { + buf.repeat('0', zeros); + } + int digits; + if (nanoValue % 1_000_000 == 0) { + digits = nanoValue / 1_000_000; } else if (nanoValue % 1000 == 0) { - buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1)); + digits = nanoValue / 1000; } else { - buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1)); + digits = nanoValue; } + buf.append(digits); } } return buf.toString(); diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index bdcbe15ea84..ea1c9fec730 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -84,4 +84,30 @@ private DecimalDigits() { public static short digitPair(int i) { return DIGITS[i]; } + + /** + * Returns the string representation size for a given int value. + * + * @param x int value + * @return string size + * + * @implNote There are other ways to compute this: e.g. binary search, + * but values are biased heavily towards zero, and therefore linear search + * wins. The iteration results are also routinely inlined in the generated + * code after loop unrolling. + */ + public static int stringSize(int x) { + int d = 1; + if (x >= 0) { + d = 0; + x = -x; + } + int p = -10; + for (int i = 1; i < 10; i++) { + if (x > p) + return i + d; + p = 10 * p; + } + return 10 + d; + } } diff --git a/test/micro/org/openjdk/bench/java/time/ToStringBench.java b/test/micro/org/openjdk/bench/java/time/ToStringBench.java new file mode 100644 index 00000000000..3d62e21ba8f --- /dev/null +++ b/test/micro/org/openjdk/bench/java/time/ToStringBench.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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.bench.java.time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Thread) +public class ToStringBench { + private static final Instant[] INSTANTS; + private static final ZonedDateTime[] ZONED_DATE_TIMES; + private static final LocalDateTime[] LOCAL_DATE_TIMES; + private static final LocalDate[] LOCAL_DATES; + private static final LocalTime[] LOCAL_TIMES; + + static { + Instant loInstant = Instant.EPOCH.plus(Duration.ofDays(365*50)); // 2020-01-01 + Instant hiInstant = loInstant.plus(Duration.ofDays(1)); + long maxOffsetNanos = Duration.between(loInstant, hiInstant).toNanos(); + Random random = new Random(0); + INSTANTS = IntStream + .range(0, 1_000) + .mapToObj(ignored -> { + final long offsetNanos = (long) Math.floor(random.nextDouble() * maxOffsetNanos); + return loInstant.plus(offsetNanos, ChronoUnit.NANOS); + }) + .toArray(Instant[]::new); + + ZONED_DATE_TIMES = Stream.of(INSTANTS) + .map(instant -> ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)) + .toArray(ZonedDateTime[]::new); + + LOCAL_DATE_TIMES = Stream.of(ZONED_DATE_TIMES) + .map(zdt -> zdt.toLocalDateTime()) + .toArray(LocalDateTime[]::new); + + LOCAL_DATES = Stream.of(LOCAL_DATE_TIMES) + .map(ldt -> ldt.toLocalDate()) + .toArray(LocalDate[]::new); + + LOCAL_TIMES = Stream.of(LOCAL_DATE_TIMES) + .map(ldt -> ldt.toLocalTime()) + .toArray(LocalTime[]::new); + } + + @Benchmark + public void zonedDateTimeToString(Blackhole bh) { + for (final ZonedDateTime zonedDateTime : ZONED_DATE_TIMES) { + bh.consume(zonedDateTime.toString()); + } + } + + @Benchmark + public void localDateTimeToString(Blackhole bh) { + for (LocalDateTime localDateTime : LOCAL_DATE_TIMES) { + bh.consume(localDateTime.toString()); + } + } + + @Benchmark + public void localDateToString(Blackhole bh) { + for (LocalDate localDate : LOCAL_DATES) { + bh.consume(localDate.toString()); + } + } + + @Benchmark + public void localTimeToString(Blackhole bh) { + for (LocalTime localTime : LOCAL_TIMES) { + bh.consume(localTime.toString()); + } + } +} \ No newline at end of file