From 765ad0e40bc522de4b2821ccc60b9139faf7376f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= <hannesw@openjdk.org>
Date: Wed, 5 Jun 2024 12:39:56 +0000
Subject: [PATCH] 8331947: Preview creates checkbox for JEP-less preview
 feature

Reviewed-by: liach, prappo
---
 .../internal/doclets/toolkit/WorkArounds.java | 15 ++---
 .../util/DeprecatedAPIListBuilder.java        |  9 ++-
 .../doclets/toolkit/util/NewAPIBuilder.java   |  9 +--
 .../toolkit/util/PreviewAPIListBuilder.java   | 57 +++++++++++--------
 .../util/RestrictedAPIListBuilder.java        |  9 ++-
 .../toolkit/util/SummaryAPIListBuilder.java   | 28 +++++----
 .../doclet/testPreview/TestPreview.java       |  4 ++
 .../testPreview/api/preview/NoPreview.java    |  6 +-
 8 files changed, 86 insertions(+), 51 deletions(-)

diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java
index 3f4bd418b05..daf0aabab27 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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,9 +31,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.ModuleElement;
@@ -48,15 +48,12 @@ import javax.tools.JavaFileManager.Location;
 
 import com.sun.source.util.TreePath;
 import com.sun.tools.javac.code.Flags;
-import com.sun.tools.javac.code.Scope;
 import com.sun.tools.javac.code.Symbol;
 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 import com.sun.tools.javac.code.Symbol.MethodSymbol;
 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
 import com.sun.tools.javac.code.Symbol.PackageSymbol;
 import com.sun.tools.javac.code.Symbol.VarSymbol;
-import com.sun.tools.javac.comp.AttrContext;
-import com.sun.tools.javac.comp.Env;
 import com.sun.tools.javac.util.Names;
 import com.sun.tools.javac.util.Options;
 
@@ -408,7 +405,7 @@ public class WorkArounds {
      * @param feature the name of the PreviewFeature.Feature enum value
      * @return the map of PreviewFeature.JEP annotation element values, or an empty map
      */
-    public Map<? extends ExecutableElement, ? extends AnnotationValue> getJepInfo(String feature) {
+    public Map<String, Object> getJepInfo(String feature) {
         TypeElement featureType = elementUtils.getTypeElement("jdk.internal.javac.PreviewFeature.Feature");
         TypeElement jepType = elementUtils.getTypeElement("jdk.internal.javac.PreviewFeature.JEP");
         var featureVar = featureType.getEnclosedElements().stream()
@@ -416,7 +413,11 @@ public class WorkArounds {
         if (featureVar.isPresent()) {
             for (AnnotationMirror anno : featureVar.get().getAnnotationMirrors()) {
                 if (anno.getAnnotationType().asElement().equals(jepType)) {
-                    return anno.getElementValues();
+                    return anno.getElementValues().entrySet()
+                            .stream()
+                            .collect(Collectors.toMap(
+                                    e -> e.getKey().getSimpleName().toString(),
+                                    e -> e.getValue().getValue()));
                 }
             }
         }
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java
index d6e8d127abf..781131234c6 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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,7 +47,7 @@ public class DeprecatedAPIListBuilder extends SummaryAPIListBuilder {
      * @param since list of releases passed via <code>--since</code> option
      */
     public DeprecatedAPIListBuilder(BaseConfiguration configuration, List<String> since) {
-        super(configuration, configuration.utils::isDeprecated);
+        super(configuration);
         this.foundReleases = new HashSet<>();
         buildSummaryAPIInfo();
         // The releases list is set to the intersection of releases defined via `--since` option
@@ -73,6 +73,11 @@ public class DeprecatedAPIListBuilder extends SummaryAPIListBuilder {
         return forRemoval;
     }
 
+    @Override
+    protected boolean belongsToSummary(Element element) {
+        return utils.isDeprecated(element);
+    }
+
     @Override
     protected void handleElement(Element e) {
         foundReleases.add(utils.getDeprecatedSince(e));
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/NewAPIBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/NewAPIBuilder.java
index 0220f6155a3..962ad84bd03 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/NewAPIBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/NewAPIBuilder.java
@@ -46,16 +46,17 @@ public class NewAPIBuilder extends SummaryAPIListBuilder {
     public final List<String> releases;
 
     public NewAPIBuilder(BaseConfiguration configuration, List<String> releases) {
-        super(configuration, element -> isNewAPI(element, configuration.utils, releases));
+        super(configuration);
         this.releases = releases;
         buildSummaryAPIInfo();
     }
 
-    private static boolean isNewAPI(Element e, Utils utils, List<String> releases) {
-        if (!utils.hasDocCommentTree(e)) {
+    @Override
+    protected boolean belongsToSummary(Element element) {
+        if (!utils.hasDocCommentTree(element)) {
             return false;
         }
-        var sinceTrees = utils.getBlockTags(e, SINCE, SinceTree.class);
+        var sinceTrees = utils.getBlockTags(element, SINCE, SinceTree.class);
         if (sinceTrees.isEmpty()) {
             return false;
         }
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/PreviewAPIListBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/PreviewAPIListBuilder.java
index 65d0a50a4b6..e9e92c13e8f 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/PreviewAPIListBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/PreviewAPIListBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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
@@ -27,14 +27,13 @@ package jdk.javadoc.internal.doclets.toolkit.util;
 
 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
 
-import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 /**
  * Build list of all the preview packages, classes, constructors, fields and methods.
@@ -43,6 +42,7 @@ public class PreviewAPIListBuilder extends SummaryAPIListBuilder {
 
     private final Map<Element, JEP> elementJeps = new HashMap<>();
     private final Map<String, JEP> jeps = new HashMap<>();
+    private static final JEP NULL_SENTINEL = new JEP(0, "", "");
 
     /**
      * The JEP for a preview feature in this release.
@@ -60,40 +60,51 @@ public class PreviewAPIListBuilder extends SummaryAPIListBuilder {
      * @param configuration the current configuration of the doclet
      */
     public PreviewAPIListBuilder(BaseConfiguration configuration) {
-        super(configuration, configuration.utils::isPreviewAPI);
+        super(configuration);
         buildSummaryAPIInfo();
     }
 
     @Override
-    protected void handleElement(Element e) {
-        String feature = Objects.requireNonNull(utils.getPreviewFeature(e),
+    protected boolean belongsToSummary(Element element) {
+        if (!utils.isPreviewAPI(element)) {
+            return false;
+        }
+        String feature = Objects.requireNonNull(utils.getPreviewFeature(element),
                 "Preview feature not specified").toString();
-        JEP jep = jeps.computeIfAbsent(feature, (featureName) -> {
-            Map<? extends ExecutableElement, ? extends AnnotationValue> anno = configuration.workArounds.getJepInfo(featureName);
-            int number = 0;
-            String title = "";
-            String status = "Preview"; // Default value is not returned by the method we use above.
-            for (var entry : anno.entrySet()) {
-                if ("number".equals(entry.getKey().getSimpleName().toString())) {
-                    number = (int) entry.getValue().getValue();
-                } else if ("title".equals(entry.getKey().getSimpleName().toString())) {
-                    title = (String) entry.getValue().getValue();
-                } else if ("status".equals(entry.getKey().getSimpleName().toString())) {
-                    status = (String) entry.getValue().getValue();
-                } else {
-                    throw new IllegalArgumentException(entry.getKey().getSimpleName().toString());
+        JEP jep = jeps.computeIfAbsent(feature, featureName -> {
+            Map<String, Object> jepInfo = configuration.workArounds.getJepInfo(featureName);
+            if (!jepInfo.isEmpty()) {
+                int number = 0;
+                String title = "";
+                String status = "Preview"; // Default value is not returned by the method we used above.
+                for (var entry : jepInfo.entrySet()) {
+                    switch (entry.getKey()) {
+                        case "number" -> number = (int) entry.getValue();
+                        case "title" -> title = (String) entry.getValue();
+                        case "status" -> status = (String) entry.getValue();
+                        default -> throw new IllegalArgumentException(entry.getKey());
+                    }
                 }
+                return new JEP(number, title, status);
             }
-            return new JEP(number, title, status);
+            return NULL_SENTINEL;
         });
-        elementJeps.put(e, jep);
+        if (jep != NULL_SENTINEL) {
+            elementJeps.put(element, jep);
+            return true;
+        }
+        // Preview features without JEP are not included.
+        return false;
     }
 
     /**
      * {@return a sorted set of preview feature JEPs in this release}
      */
     public Set<JEP> getJEPs() {
-        return new TreeSet<>(jeps.values());
+        return jeps.values()
+                .stream()
+                .filter(jep -> jep != NULL_SENTINEL)
+                .collect(Collectors.toCollection(TreeSet::new));
     }
 
     /**
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/RestrictedAPIListBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/RestrictedAPIListBuilder.java
index caf89cabebe..05ff0143298 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/RestrictedAPIListBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/RestrictedAPIListBuilder.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
@@ -41,7 +41,12 @@ public class RestrictedAPIListBuilder extends SummaryAPIListBuilder {
      * @param configuration the current configuration of the doclet
      */
     public RestrictedAPIListBuilder(BaseConfiguration configuration) {
-        super(configuration, configuration.utils::isRestrictedAPI);
+        super(configuration);
         buildSummaryAPIInfo();
     }
+
+    @Override
+    protected boolean belongsToSummary(Element element) {
+        return utils.isRestrictedAPI(element);
+    }
 }
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/SummaryAPIListBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/SummaryAPIListBuilder.java
index 5125656c821..74f9fe97d7a 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/SummaryAPIListBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/SummaryAPIListBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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
@@ -26,7 +26,6 @@
 package jdk.javadoc.internal.doclets.toolkit.util;
 
 import java.util.*;
-import java.util.function.Predicate;
 
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ModuleElement;
@@ -39,14 +38,13 @@ import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
 /**
  * Build list of all the summary packages, classes, constructors, fields and methods.
  */
-public class SummaryAPIListBuilder {
+public abstract class SummaryAPIListBuilder {
     /**
      * List of summary type Lists.
      */
     private final Map<SummaryElementKind, SortedSet<Element>> summaryMap;
     protected final BaseConfiguration configuration;
     protected final Utils utils;
-    private final Predicate<Element> belongsToSummary;
 
     public enum SummaryElementKind {
         MODULE,
@@ -69,11 +67,9 @@ public class SummaryAPIListBuilder {
      *
      * @param configuration the current configuration of the doclet
      */
-    public SummaryAPIListBuilder(BaseConfiguration configuration,
-                                 Predicate<Element> belongsToSummary) {
+    protected SummaryAPIListBuilder(BaseConfiguration configuration) {
         this.configuration = configuration;
         this.utils = configuration.utils;
-        this.belongsToSummary = belongsToSummary;
         summaryMap = new EnumMap<>(SummaryElementKind.class);
         for (SummaryElementKind kind : SummaryElementKind.values()) {
             summaryMap.put(kind, createSummarySet());
@@ -93,7 +89,7 @@ public class SummaryAPIListBuilder {
         SortedSet<ModuleElement> modules = configuration.modules;
         SortedSet<Element> mset = summaryMap.get(SummaryElementKind.MODULE);
         for (Element me : modules) {
-            if (belongsToSummary.test(me)) {
+            if (belongsToSummary(me)) {
                 mset.add(me);
                 handleElement(me);
             }
@@ -101,14 +97,14 @@ public class SummaryAPIListBuilder {
         SortedSet<PackageElement> packages = configuration.packages;
         SortedSet<Element> pset = summaryMap.get(SummaryElementKind.PACKAGE);
         for (Element pe : packages) {
-            if (belongsToSummary.test(pe)) {
+            if (belongsToSummary(pe)) {
                 pset.add(pe);
                 handleElement(pe);
             }
         }
         for (TypeElement te : configuration.getIncludedTypeElements()) {
             SortedSet<Element> eset;
-            if (belongsToSummary.test(te)) {
+            if (belongsToSummary(te)) {
                 switch (te.getKind()) {
                     case ANNOTATION_TYPE -> {
                         eset = summaryMap.get(SummaryElementKind.ANNOTATION_TYPE);
@@ -149,7 +145,7 @@ public class SummaryAPIListBuilder {
             }
             if (utils.isRecord(te)) {
                 for (RecordComponentElement component : te.getRecordComponents()) {
-                    if (belongsToSummary.test(component)) {
+                    if (belongsToSummary(component)) {
                         throw new AssertionError("record components not supported in summary builders: " +
                                                  "component: " + component.getSimpleName() +
                                                  " of record: " + te.getQualifiedName());
@@ -164,6 +160,14 @@ public class SummaryAPIListBuilder {
         }
     }
 
+    /**
+     * This method decides whether Element {@code element} should be included in this summary list.
+     *
+     * @param element an element
+     * @return true if the element should be included
+     */
+    protected abstract boolean belongsToSummary(Element element);
+
     /**
      * Add the members into a single list of summary members.
      *
@@ -172,7 +176,7 @@ public class SummaryAPIListBuilder {
      */
     private void composeSummaryList(SortedSet<Element> sset, List<? extends Element> members) {
         for (Element member : members) {
-            if (belongsToSummary.test(member)) {
+            if (belongsToSummary(member)) {
                 sset.add(member);
                 handleElement(member);
             }
diff --git a/test/langtools/jdk/javadoc/doclet/testPreview/TestPreview.java b/test/langtools/jdk/javadoc/doclet/testPreview/TestPreview.java
index d5107f7598f..a59e68d25a0 100644
--- a/test/langtools/jdk/javadoc/doclet/testPreview/TestPreview.java
+++ b/test/langtools/jdk/javadoc/doclet/testPreview/TestPreview.java
@@ -24,6 +24,7 @@
 /*
  * @test
  * @bug      8250768 8261976 8277300 8282452 8287597 8325325 8325874 8297879
+ *           8331947
  * @summary  test generated docs for items declared using preview
  * @library  ../../lib
  * @modules jdk.javadoc/jdk.javadoc.internal.tool
@@ -156,6 +157,9 @@ public class TestPreview extends JavadocTester {
                     <li><a href="package-summary.html">preview</a></li>
                     <li><a href="Core.html" class="current-selection">Core</a></li>
                     </ol>""");
+
+        // 8331947: Support preview features without JEP should not be included in Preview API page
+        checkOutput("preview-list.html", false, "supportMethod");
     }
 
     @Test
diff --git a/test/langtools/jdk/javadoc/doclet/testPreview/api/preview/NoPreview.java b/test/langtools/jdk/javadoc/doclet/testPreview/api/preview/NoPreview.java
index 951b557ce6d..b4ff5f0da00 100644
--- a/test/langtools/jdk/javadoc/doclet/testPreview/api/preview/NoPreview.java
+++ b/test/langtools/jdk/javadoc/doclet/testPreview/api/preview/NoPreview.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
@@ -34,4 +34,8 @@ public class NoPreview {
 
     @PreviewFeature(feature=Feature.TEST)
     public static class T {}
+
+    // Preview support feature without JEP should not be listed
+    @PreviewFeature(feature=Feature.LANGUAGE_MODEL)
+    public void supportMethod() {}
 }