/*
 * Copyright (c) 2014, 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.
 */

/*
 * @test
 * @bug 8042261 8298405
 * @summary Checking what attribute is generated by annotation Deprecated
 *          or javadoc deprecated for field, method, class(inner/local), interface.
 * @library /tools/lib /tools/javac/lib ../lib
 * @modules jdk.compiler/com.sun.tools.javac.api
 *          jdk.compiler/com.sun.tools.javac.main
 *          jdk.compiler/com.sun.tools.javac.util
 *          java.base/jdk.internal.classfile.impl
 * @build toolbox.ToolBox InMemoryFileManager TestResult TestBase
 * @run main DeprecatedTest
 */

import java.lang.classfile.*;
import java.lang.classfile.attribute.*;
import jdk.internal.classfile.impl.BoundAttribute;

import javax.tools.JavaFileObject;
import java.io.IOException;
import java.util.Map;

public class DeprecatedTest extends TestResult {

    private static final String[] sources = new String[]{
            "@Deprecated public class deprecated {\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated public void deprecated() {}\n"
            + "@Deprecated public int deprecated;\n"
            + "public void notDeprecated() {}\n"
            + "public int notDeprecated;\n"
            + "public void f() {\n"
            + "    @Deprecated class deprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }\n"
            + "    class notDeprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }}\n"
            + "}",
            "@Deprecated public interface deprecated {\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated void deprecated01();\n"
            + "void notDeprecated01();\n"
            + "@Deprecated default void deprecated02() {}\n"
            + "default void notDeprecated02() {}\n"
            + "@Deprecated int deprecated = 0;\n"
            + "int notDeprecated = 0;\n"
            + "}",
            "@Deprecated public enum deprecated {\n"
            + "@Deprecated deprecated, notDeprecated;\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated public void deprecated() {}\n"
            + "public void notDeprecated() {}\n"
            + "public void f() {\n"
            + "    @Deprecated class deprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }\n"
            + "    class notDeprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }}\n"
            + "}",
            "@Deprecated public @interface deprecated {\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated int deprecated() default 0;\n"
            + "int notDeprecated() default 0;\n"
            + "@Deprecated int deprecated = 0;\n"
            + "int notDeprecated = 0;\n"
            + "}",
            "public class notDeprecated {\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated public void deprecated() {}\n"
            + "@Deprecated public int deprecated;\n"
            + "public void notDeprecated() {}\n"
            + "public int notDeprecated;\n"
            + "public void f() {\n"
            + "    @Deprecated class deprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }\n"
            + "    class notDeprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }}\n"
            + "}",
            "public interface notDeprecated {\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated void deprecated01();\n"
            + "void notDeprecated01();\n"
            + "@Deprecated default void deprecated02() {}\n"
            + "default void notDeprecated02() {}\n"
            + "@Deprecated int deprecated = 0;\n"
            + "int notDeprecated = 0;\n"
            + "}",
            "public enum notDeprecated {\n"
            + "@Deprecated deprecated, notDeprecated;\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated public void deprecated() {}\n"
            + "public void notDeprecated() {}\n"
            + "public void f() {\n"
            + "    @Deprecated class deprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }\n"
            + "    class notDeprecatedLocal {\n"
            + "        @Deprecated int deprecated;\n"
            + "        @Deprecated void deprecated() {}\n"
            + "        int notDeprecated;\n"
            + "        void notDeprecated(){}\n"
            + "    }}\n"
            + "}",
            "public @interface notDeprecated {\n"
            + "@Deprecated class deprecatedInner01 {}\n"
            + "@Deprecated interface deprecatedInner02 {}\n"
            + "@Deprecated enum deprecatedInner03 {}\n"
            + "@Deprecated @interface deprecatedInner04 {}\n"
            + "class notDeprecatedInner01 {}\n"
            + "interface notDeprecatedInner02 {}\n"
            + "enum notDeprecatedInner03 {}\n"
            + "@interface notDeprecatedInner04 {}\n"
            + "@Deprecated int deprecated() default 0;\n"
            + "int notDeprecated() default 0;\n"
            + "@Deprecated int deprecated = 0;\n"
            + "int notDeprecated = 0;\n"
            + "}"};

    public static void main(String[] args) throws TestFailedException {
        new DeprecatedTest().test();
    }

    public void test() throws TestFailedException {
        try {
            for (String src : sources) {
                test(src);
                test(src.replaceAll("@Deprecated", "/** @deprecated */"));
                test(src.replaceAll("deprecated", "notDeprecated2") // change element name
                        .replaceAll("@Deprecated", "/// @deprecated\n"));
            }
        } catch (Exception e) {
            addFailure(e);
        } finally {
            checkStatus();
        }
    }

    private void test(String src) {
        addTestCase(src);
        printf("Testing test case :\n%s\n", src);
        try {
            Map<String, ? extends JavaFileObject> classes = compile(src).getClasses();
            String outerClassName = classes.keySet().stream()
                    .filter(n -> !n.contains("$"))
                    .findFirst().orElse(null);
            echo("Testing outer class : " + outerClassName);
            ClassModel cf = readClassFile(classes.get(outerClassName));
            DeprecatedAttribute attr = cf.findAttribute(Attributes.deprecated()).orElse(null);
            testAttribute(outerClassName, attr, cf);
            testInnerClasses(cf, classes);
            testMethods(cf);
            testFields(cf);
        } catch (Exception e) {
            addFailure(e);
        }
    }

    private void testInnerClasses(ClassModel cf, Map<String, ? extends JavaFileObject> classes)
            throws IOException {
        InnerClassesAttribute innerAttr = cf.findAttribute(Attributes.innerClasses()).orElse(null);
        assert innerAttr != null;
        for (InnerClassInfo innerClass : innerAttr.classes()) {
            String innerClassName = innerClass.innerClass().name().stringValue();
            echo("Testing inner class : " + innerClassName);
            ClassModel innerCf = readClassFile(classes.get(innerClassName));
            DeprecatedAttribute attr = innerCf.findAttribute(Attributes.deprecated()).orElse(null);
            assert innerClass.innerName().isPresent();
            String innerClassSimpleName = innerClass.innerName().get().stringValue();
            testAttribute(innerClassSimpleName, attr, innerCf);
            if (innerClassName.contains("Local")) {
                testMethods(innerCf);
                testFields(innerCf);
            }
        }
    }

    private void testMethods(ClassModel cf) {
        for (MethodModel m : cf.methods()) {
            String methodName = m.methodName().stringValue();
            echo("Testing method : " + methodName);
            DeprecatedAttribute attr = m.findAttribute(Attributes.deprecated()).orElse(null);
            testAttribute(methodName, attr, cf);
        }
    }

    private void testFields(ClassModel cm) {
        for (FieldModel f : cm.fields()) {
            String fieldName = f.fieldName().stringValue();
            echo("Testing field : " + fieldName);
            DeprecatedAttribute attr = f.findAttribute(Attributes.deprecated()).orElse(null);
            testAttribute(fieldName, attr, cm);
        }
    }

    private void testAttribute(String name, DeprecatedAttribute attr, ClassModel cf) {
        if (name.contains("deprecated")) {
            testDeprecatedAttribute(name, attr, cf);
        } else {
            checkNull(attr, name + " should not have deprecated attribute");
        }
    }

    private void testDeprecatedAttribute(String name, DeprecatedAttribute attr, ClassModel cm) {
        if (checkNotNull(attr, name + " must have deprecated attribute")) {
            checkEquals(0, ((BoundAttribute<?>)attr).payloadLen(),
                    "attribute_length should equal to 0");
            checkEquals("Deprecated", attr.attributeName().stringValue(),
                    name + " attribute_name_index");
        }
    }
}
