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()); + } + +}