From 069e673f4bfe26663d0a4f290ca712b568b342df Mon Sep 17 00:00:00 2001 From: Michael Bien Date: Mon, 2 Mar 2026 14:57:21 +0100 Subject: [PATCH 1/3] Provide access to LazyConstant via internal fallback module - introduces "JDK Fallbacks" module for internal use only - module delegates to JDK implementations if available and uses fallbacks if not - the accessors are meant to be temporary friend API with expiration date, only to bridge the gap between lower and upper runtime requirements --- .github/workflows/main.yml | 3 + nbbuild/cluster.properties | 1 + platform/o.n.jdk.fallback/build.xml | 25 ++++ platform/o.n.jdk.fallback/manifest.mf | 6 + .../nbproject/project.properties | 23 ++++ .../o.n.jdk.fallback/nbproject/project.xml | 48 +++++++ .../netbeans/jdk/fallback/Bundle.properties | 21 +++ .../jdk/fallback/lang/NBLazyConstant.java | 125 ++++++++++++++++++ .../jdk/fallback/lang/NBLazyConstantTest.java | 117 ++++++++++++++++ 9 files changed, 369 insertions(+) create mode 100644 platform/o.n.jdk.fallback/build.xml create mode 100644 platform/o.n.jdk.fallback/manifest.mf create mode 100644 platform/o.n.jdk.fallback/nbproject/project.properties create mode 100644 platform/o.n.jdk.fallback/nbproject/project.xml create mode 100644 platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties create mode 100644 platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java create mode 100644 platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a7b5eeaad60c..01962b867d6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -284,6 +284,9 @@ jobs: if: ${{ matrix.java == '21' }} run: .github/retry.sh ant $OPTS -f platform/masterfs test + - name: platform/o.n.jdk.fallback + run: ant $OPTS -f platform/o.n.jdk.fallback test + - name: Commit Validation tests run: .github/retry.sh ant $OPTS -Dcluster.config=$CLUSTER_CONFIG commit-validation diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties index bbd52bc8c877..a1f133f3891a 100644 --- a/nbbuild/cluster.properties +++ b/nbbuild/cluster.properties @@ -215,6 +215,7 @@ nb.cluster.platform=\ o.n.html.ko4j,\ o.n.html.presenters.spi,\ o.n.html.xhr4j,\ + o.n.jdk.fallback,\ o.n.swing.laf.dark,\ o.n.swing.laf.flatlaf,\ o.n.swing.outline,\ diff --git a/platform/o.n.jdk.fallback/build.xml b/platform/o.n.jdk.fallback/build.xml new file mode 100644 index 000000000000..9fcdc9bcee3f --- /dev/null +++ b/platform/o.n.jdk.fallback/build.xml @@ -0,0 +1,25 @@ + + + + Builds, tests, and runs the project org.netbeans.jdk.fallback + + diff --git a/platform/o.n.jdk.fallback/manifest.mf b/platform/o.n.jdk.fallback/manifest.mf new file mode 100644 index 000000000000..32b7d9726894 --- /dev/null +++ b/platform/o.n.jdk.fallback/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: false +AutoUpdate-Essential-Module: true +OpenIDE-Module: o.n.jdk.fallback/0 +OpenIDE-Module-Localizing-Bundle: org/netbeans/jdk/fallback/Bundle.properties +OpenIDE-Module-Specification-Version: 0.1 diff --git a/platform/o.n.jdk.fallback/nbproject/project.properties b/platform/o.n.jdk.fallback/nbproject/project.properties new file mode 100644 index 000000000000..e75020298da1 --- /dev/null +++ b/platform/o.n.jdk.fallback/nbproject/project.properties @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +javac.compilerargs=-Xlint +javac.release=17 + +# javadoc.arch=${basedir}/arch.xml +# javadoc.apichanges=${basedir}/apichanges.xml +# javadoc.arch=${basedir}/arch.xml diff --git a/platform/o.n.jdk.fallback/nbproject/project.xml b/platform/o.n.jdk.fallback/nbproject/project.xml new file mode 100644 index 000000000000..d4647ac0d2d3 --- /dev/null +++ b/platform/o.n.jdk.fallback/nbproject/project.xml @@ -0,0 +1,48 @@ + + + + org.netbeans.modules.apisupport.project + + + o.n.jdk.fallback + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + + + org.netbeans.modules.maven.indexer + org.netbeans.jdk.fallback.lang + + + + diff --git a/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties new file mode 100644 index 000000000000..fe8a6095209e --- /dev/null +++ b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +OpenIDE-Module-Name=Simple JDK Fallbacks +OpenIDE-Module-Display-Category=Libraries +OpenIDE-Module-Short-Description=Provides internal, non-permanent and possibly incomplete fallbacks for JDK APIs\ + which are useful for NetBeans but not covered by the minimal run requirements yet. diff --git a/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java new file mode 100644 index 000000000000..34528cc071eb --- /dev/null +++ b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.jdk.fallback.lang; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Delegates to JDK's LazyConstant and provides a fallback implementation if not available. + * + * Internal API, may be removed when no longer needed. + * + * @author mbien + */ +public final class NBLazyConstant { + + private static final MethodHandle lazyConstantFactory; + + static { + Logger log = Logger.getLogger(NBLazyConstant.class.getName()); + MethodHandle mh = null; + try { + if (Boolean.getBoolean("nb.jdk.LazyConstant.usefallback")) { + mh = null; + log.log(Level.INFO, "using fallback"); + } else if (Runtime.version().feature() >= 26) { + Class entryPoint = Class.forName("java.lang.LazyConstant"); + mh = MethodHandles.lookup().findStatic(entryPoint, "of", MethodType.methodType(entryPoint, Supplier.class)) + .asType(MethodType.methodType(Supplier.class, Supplier.class)); + } else if (Runtime.version().feature() == 25) { + Class entryPoint = Class.forName("java.lang.StableValue"); + mh = MethodHandles.lookup().findStatic(entryPoint, "supplier", MethodType.methodType(Supplier.class, Supplier.class)); + } + // dryrun - just to be sure + if (mh != null) { + Supplier probe = () -> true; + ((Supplier)mh.invokeExact(probe)).get(); + } + } catch (Throwable ex) { + mh = null; + log.log(Level.FINE, "using fallback", ex); + } + lazyConstantFactory = mh; + log.log(Level.FINE, () -> "impl=" + String.valueOf(lazyConstantFactory)); + } + + private NBLazyConstant() {} + + /** + * Create a {@link Supplier} for a lazily initializing constant. + * @param computingFunction Factory to create the constant, only called once. + * @return Returns the constant, never null. + */ + @SuppressWarnings("unchecked") + public static Supplier of(Supplier computingFunction) { + Objects.requireNonNull(computingFunction); + if (computingFunction instanceof DoubleCheckedFallback lc) { + return (Supplier) lc; + } + if (lazyConstantFactory != null) { + try { + return (Supplier) lazyConstantFactory.invokeExact(computingFunction); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable ex) { + throw new RuntimeException(ex); // shouldn't be reachable under regular circumstances + } + } else { + return new DoubleCheckedFallback<>(computingFunction); + } + } + + private static class DoubleCheckedFallback implements Supplier { + + private volatile T constant; + private Supplier factory; + + private DoubleCheckedFallback(Supplier factory) { + this.factory = factory; + } + + @Override + public T get() { + T c = constant; + if (c == null) { + synchronized (this) { + c = constant; + if (c == null) { + c = factory.get(); + Objects.requireNonNull(c); + constant = c; + factory = null; + } + } + } + return c; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{factory=" + factory + ", constant=" + constant + '}'; + } + } +} diff --git a/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java b/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java new file mode 100644 index 000000000000..19efa289a302 --- /dev/null +++ b/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.jdk.fallback.lang; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +public class NBLazyConstantTest { + + @Test + public void testFactory() { + + Supplier lazy = NBLazyConstant.of(() -> true); + assertNotNull(lazy); + assertTrue(lazy.get()); + + String impl; + if (Runtime.version().feature() >= 26) { + impl = "LazyConstant"; + } else if (Runtime.version().feature() == 25) { + impl = "StableSupplier"; + } else { + impl = "DoubleCheckedFallback"; + } + assertTrue(impl + " expected but got " + lazy.getClass(), lazy.getClass().getSimpleName().contains(impl)); + } + + @Test + public void testConstant() { + Supplier lazy = NBLazyConstant.of(() -> Math.random()); + assertNotNull(lazy); + assertEquals(lazy.get(), lazy.get()); + assertEquals(lazy.get(), lazy.get()); + assertEquals(lazy.get(), lazy.get()); + } + + @Test + public void testRequireNPEOnNullResult() { + + // StableValue allows null, we use the LazyConstant spec + assumeTrue(Runtime.version().feature() != 25); + + AtomicReference value = new AtomicReference<>(); + + Supplier lazy = NBLazyConstant.of(() -> value.get()); + assertNotNull(lazy); + + try { + lazy.get(); + fail(); + } catch (NullPointerException good) {} + + try { + lazy.get(); + fail(); + } catch (NullPointerException stillGood) {} + + value.set("good"); + + // constant can be computed from now on + assertEquals("good", lazy.get()); + assertEquals("good", lazy.get()); + } + + @Test + public void testExceptionDuringCompute() { + + AtomicBoolean fail = new AtomicBoolean(true); + + Supplier lazy = NBLazyConstant.of(() -> { + if (fail.get()) { + throw new RuntimeException("can't compute"); + } else { + return "good"; + } + }); + assertNotNull(lazy); + + try { + lazy.get(); + fail(); + } catch (RuntimeException good) {} + + try { + lazy.get(); + fail(); + } catch (RuntimeException stillGood) {} + + fail.set(false); + + // constant can be computed from now on + assertEquals("good", lazy.get()); + assertEquals("good", lazy.get()); + } + +} From 22e001df97f9f54c93551277169c660a9bc3f91f Mon Sep 17 00:00:00 2001 From: Michael Bien Date: Wed, 18 Mar 2026 20:46:38 +0100 Subject: [PATCH 2/3] Use NBLazyConstant in NexusRepositoryQueries as proof of concept simple usage; removes a synchronized block --- java/maven.indexer/nbproject/project.xml | 9 ++++++++ .../maven/indexer/NexusRepositoryQueries.java | 21 +++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/java/maven.indexer/nbproject/project.xml b/java/maven.indexer/nbproject/project.xml index 78c76d997eb2..e86263eb2839 100644 --- a/java/maven.indexer/nbproject/project.xml +++ b/java/maven.indexer/nbproject/project.xml @@ -25,6 +25,15 @@ org.netbeans.modules.maven.indexer + + o.n.jdk.fallback + + + + 0-1 + 0.1 + + com.google.gson diff --git a/java/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryQueries.java b/java/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryQueries.java index 8d3944ecc177..e8938b79b589 100644 --- a/java/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryQueries.java +++ b/java/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryQueries.java @@ -30,6 +30,7 @@ import java.util.Optional; import java.util.Set; import java.util.TreeSet; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -61,6 +62,7 @@ import org.apache.maven.search.backend.smo.SmoSearchBackendFactory; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.jdk.fallback.lang.NBLazyConstant; import org.netbeans.modules.maven.indexer.api.NBArtifactInfo; import org.netbeans.modules.maven.indexer.api.NBGroupInfo; import org.netbeans.modules.maven.indexer.api.NBVersionInfo; @@ -88,7 +90,12 @@ final class NexusRepositoryQueries implements private static final Logger LOGGER = Logger.getLogger(NexusRepositoryQueries.class.getName()); - private SmoSearchBackend smo; + private final Supplier smo = NBLazyConstant.of(() -> SmoSearchBackendFactory.create( + SmoSearchBackendFactory.DEFAULT_BACKEND_ID, + SmoSearchBackendFactory.DEFAULT_REPOSITORY_ID, + SmoSearchBackendFactory.DEFAULT_SMO_URI, + new Java11HttpClientTransport(SMORequestResult.REQUEST_TIMEOUT)) + ); private final NexusRepositoryIndexManager indexer; NexusRepositoryQueries(NexusRepositoryIndexManager indexer) { @@ -887,16 +894,8 @@ public List getLoaded(final List repos) { return toRet; } - synchronized SmoSearchBackend getSMO() { - if (smo == null) { - smo = SmoSearchBackendFactory.create( - SmoSearchBackendFactory.DEFAULT_BACKEND_ID, - SmoSearchBackendFactory.DEFAULT_REPOSITORY_ID, - SmoSearchBackendFactory.DEFAULT_SMO_URI, - new Java11HttpClientTransport(SMORequestResult.REQUEST_TIMEOUT) - ); - } - return smo; + SmoSearchBackend getSMO() { + return smo.get(); } private interface RepoAction { From fb37b336425d90f5f76a6e31ffba60d25eef33cd Mon Sep 17 00:00:00 2001 From: Michael Bien Date: Thu, 19 Mar 2026 22:19:57 +0100 Subject: [PATCH 3/3] updated for JDK 27 third preview spec --- .../netbeans/jdk/fallback/Bundle.properties | 2 +- .../jdk/fallback/lang/NBLazyConstant.java | 21 +++-- .../jdk/fallback/lang/NBLazyConstantTest.java | 85 ++++++++++++++++--- 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties index fe8a6095209e..5c23532244ef 100644 --- a/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties +++ b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/Bundle.properties @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -OpenIDE-Module-Name=Simple JDK Fallbacks +OpenIDE-Module-Name=Internal JDK Fallbacks OpenIDE-Module-Display-Category=Libraries OpenIDE-Module-Short-Description=Provides internal, non-permanent and possibly incomplete fallbacks for JDK APIs\ which are useful for NetBeans but not covered by the minimal run requirements yet. diff --git a/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java index 34528cc071eb..5c1bfc671073 100644 --- a/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java +++ b/platform/o.n.jdk.fallback/src/org/netbeans/jdk/fallback/lang/NBLazyConstant.java @@ -21,6 +21,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.Supplier; import java.util.logging.Level; @@ -29,6 +30,8 @@ /** * Delegates to JDK's LazyConstant and provides a fallback implementation if not available. * + * The fallback implementation simulates behavior of the JDk 27 LazyConstant spec. + * * Internal API, may be removed when no longer needed. * * @author mbien @@ -75,9 +78,6 @@ private NBLazyConstant() {} @SuppressWarnings("unchecked") public static Supplier of(Supplier computingFunction) { Objects.requireNonNull(computingFunction); - if (computingFunction instanceof DoubleCheckedFallback lc) { - return (Supplier) lc; - } if (lazyConstantFactory != null) { try { return (Supplier) lazyConstantFactory.invokeExact(computingFunction); @@ -91,6 +91,8 @@ public static Supplier of(Supplier computingFunction) { } } + /// simmulates JDK 27 spec, 25 and 26 are more permissive + /// and may allow null or error recovery private static class DoubleCheckedFallback implements Supplier { private volatile T constant; @@ -105,12 +107,19 @@ public T get() { T c = constant; if (c == null) { synchronized (this) { + if (factory == null) { // error state due to past supplier failure + throw new NoSuchElementException("Unable to access the constant because an Exception was thrown at initial computation"); + } c = constant; if (c == null) { - c = factory.get(); - Objects.requireNonNull(c); + try { + c = factory.get(); + Objects.requireNonNull(c); + } catch (Throwable t) { + factory = null; + throw new NoSuchElementException(t); + } constant = c; - factory = null; } } } diff --git a/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java b/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java index 19efa289a302..d8539517693c 100644 --- a/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java +++ b/platform/o.n.jdk.fallback/test/unit/src/org/netbeans/jdk/fallback/lang/NBLazyConstantTest.java @@ -18,7 +18,9 @@ */ package org.netbeans.jdk.fallback.lang; +import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.junit.Test; @@ -28,6 +30,9 @@ public class NBLazyConstantTest { + // true if fallback or JDK 27 impl active + private static final boolean JDK27_SPEC = Runtime.version().feature() >= 27 || Runtime.version().feature() < 25; + @Test public void testFactory() { @@ -58,29 +63,60 @@ public void testConstant() { @Test public void testRequireNPEOnNullResult() { - // StableValue allows null, we use the LazyConstant spec - assumeTrue(Runtime.version().feature() != 25); + // StableValue allows null, we use the LazyConstant JDK 27 spec + // since its the least permissive + assumeTrue("jdk 25 impl supports null", Runtime.version().feature() != 25); AtomicReference value = new AtomicReference<>(); + AtomicInteger calls = new AtomicInteger(); - Supplier lazy = NBLazyConstant.of(() -> value.get()); + Supplier lazy = NBLazyConstant.of(() -> { + calls.incrementAndGet(); + return value.get(); + }); assertNotNull(lazy); try { lazy.get(); fail(); - } catch (NullPointerException good) {} + } catch (NoSuchElementException good) { + assertTrue(JDK27_SPEC); + assertEquals(NullPointerException.class, good.getCause().getClass()); + } catch (NullPointerException good) { + assertFalse(JDK27_SPEC); + } + assertEquals(1, calls.get()); try { lazy.get(); fail(); - } catch (NullPointerException stillGood) {} + } catch (NoSuchElementException stillGood) { + assertTrue(JDK27_SPEC); + // no cause anymore + } catch (NullPointerException stillGood) { + assertFalse(JDK27_SPEC); + } + + // JDK 25-26 spec has retries, we don't test those since fallback mimics JDK 27 + if (Runtime.version().feature() == 26) { + return; + } + + assertEquals(1, calls.get()); value.set("good"); - // constant can be computed from now on - assertEquals("good", lazy.get()); - assertEquals("good", lazy.get()); + // JDK 27+ spec -> no recovery from error state + try { + lazy.get(); + fail(); + } catch (NoSuchElementException stillGood) { + assertTrue(JDK27_SPEC); + } catch (NullPointerException stillGood) { + assertFalse(JDK27_SPEC); + } + assertEquals(1, calls.get()); + } @Test @@ -100,18 +136,41 @@ public void testExceptionDuringCompute() { try { lazy.get(); fail(); - } catch (RuntimeException good) {} + } catch (NoSuchElementException good) { + assertTrue(JDK27_SPEC); + assertEquals(RuntimeException.class, good.getCause().getClass()); + } catch (RuntimeException good) { + assertFalse(JDK27_SPEC); + assertEquals(RuntimeException.class, good.getClass()); + } try { lazy.get(); fail(); - } catch (RuntimeException stillGood) {} + } catch (NoSuchElementException good) { + assertTrue(JDK27_SPEC); + // no cause anymore + } catch (RuntimeException good) { + assertFalse(JDK27_SPEC); + assertEquals(RuntimeException.class, good.getClass()); + } fail.set(false); - // constant can be computed from now on - assertEquals("good", lazy.get()); - assertEquals("good", lazy.get()); + // we don't test error recovery of 25 and 26 + if (!JDK27_SPEC) { +// assertEquals("good", lazy.get()); +// assertEquals("good", lazy.get()); + return; + } + + try { + lazy.get(); + fail(); + } catch (RuntimeException stillInFailureState) { + assertEquals(NoSuchElementException.class, stillInFailureState.getClass()); + } + } }