/**
 * @test
 * @bug 8358341
 * @summary Verify that the Class-Path manifest attribute is reflected for processorpath
 * @library /tools/lib
 * @modules jdk.compiler/com.sun.tools.javac.api
 * jdk.compiler/com.sun.tools.javac.code
 * jdk.compiler/com.sun.tools.javac.main
 * @build toolbox.ToolBox FollowClassPathAttributeInProcessorPath
 * @run junit ProcessorPathTest
 */

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import javax.annotation.processing.Processor;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import toolbox.JavacTask;
import toolbox.JarTask;
import toolbox.Task;
import toolbox.ToolBox;

public class ProcessorPathTest {
    static ToolBox tb = new ToolBox();

    static Path base = Paths.get(".");

    static Path annotation = base.resolve("annotation");
    static Path annotationSrc = annotation.resolve("src");
    static Path annotationClasses = annotation.resolve("classes");

    static Path dep = base.resolve("dep");
    static Path depSrc = dep.resolve("src");
    static Path depClasses = dep.resolve("classes");

    static Path[] dependencies = { annotationClasses, depClasses };

    static Path processorA = base.resolve("processorA");
    static Path processorASrc = processorA.resolve("src");
    static Path processorAClasses = processorA.resolve("classes");
    static Path processorAJar = base.resolve("processorA.jar");

    static Path processorB = base.resolve("processorB");
    static Path processorBSrc = processorB.resolve("src");
    static Path processorBClasses = processorB.resolve("classes");
    static Path processorBJar = base.resolve("processorB.jar");

    static Path[] processorJar = { processorAJar, processorBJar};

    static Path targetA = base.resolve("targetA");
    static Path targetASrc = targetA.resolve("src");
    static Path targetAClasses = targetA.resolve("classes");
    static Path targetAGenerated = targetA.resolve("generated");

    static Path targetB = base.resolve("targetB");
    static Path targetBSrc = targetB.resolve("src");
    static Path targetBClasses = targetB.resolve("classes");
    static Path targetBGenerated = targetB.resolve("generated");

    static Path[] targetSrc = { targetASrc, targetBSrc };
    static Path[] targetClasses = { targetAClasses, targetBClasses };
    static Path[] targetGenerated = { targetAGenerated, targetBGenerated };

    static String expected = "Note: Called!";

    @BeforeAll
    public static void setup() throws IOException {

        tb.writeJavaFiles(annotationSrc,
                          """
                          package annotation;

                          public @interface MyAnnotation {}
                          """);

        Files.createDirectories(annotationClasses);

        new JavacTask(tb, Task.Mode.EXEC)
                .outdir(annotationClasses)
                .files(tb.findJavaFiles(annotationSrc))
                .run()
                .writeAll();

        tb.writeJavaFiles(depSrc,
                          """
                          package dep;

                          public class Dependency {
                              public static String get() {
                                  return "OK";
                              }
                          }
                          """);

        Files.createDirectories(depClasses);

        new JavacTask(tb, Task.Mode.EXEC)
                .outdir(depClasses)
                .files(tb.findJavaFiles(depSrc))
                .run()
                .writeAll();

        tb.writeJavaFiles(processorASrc,
                          """
                          package proc;

                          import java.util.Set;
                          import javax.annotation.processing.AbstractProcessor;
                          import javax.annotation.processing.RoundEnvironment;
                          import javax.annotation.processing.SupportedAnnotationTypes;
                          import javax.lang.model.SourceVersion;
                          import javax.lang.model.element.TypeElement;

                          import annotation.MyAnnotation;

                          public class ProcessorImpl extends AbstractProcessor {
                              public boolean process(Set<? extends TypeElement> annotations,
                                                     RoundEnvironment roundEnv) {
                                  if (roundEnv.processingOver()) {
                                      processingEnv.getMessager().printNote("Called!");
                                  }
                                  return false;
                              }
                              public SourceVersion getSupportedSourceVersion() {
                                  return SourceVersion.latest();
                              }
                              public Set<String> getSupportedAnnotationTypes() {
                                  return Set.of(MyAnnotation.class.getName());
                              }
                          }
                          """);

        Files.createDirectories(processorAClasses);

        new JavacTask(tb, Task.Mode.EXEC)
                .outdir(processorAClasses)
                .files(tb.findJavaFiles(processorASrc))
                .classpath(annotationClasses.toString())
                .run()
                .writeAll();

        tb.writeFile(processorAClasses.resolve("META-INF/services").resolve(Processor.class.getName()),
                     """
                     proc.ProcessorImpl
                     """);

        new JarTask(tb, processorAJar)
                .baseDir(processorAClasses)
                .files("META-INF/services/" + Processor.class.getName(),
                       "proc/ProcessorImpl.class")
                .classpath(annotationClasses.toString())
                .run();

        tb.writeJavaFiles(processorBSrc,
                          """
                          package proc;

                          import java.util.Set;
                          import javax.annotation.processing.AbstractProcessor;
                          import javax.annotation.processing.RoundEnvironment;
                          import javax.annotation.processing.SupportedAnnotationTypes;
                          import javax.lang.model.SourceVersion;
                          import javax.lang.model.element.TypeElement;

                          import dep.Dependency;

                          @SupportedAnnotationTypes("*")
                          public class ProcessorImpl extends AbstractProcessor {
                              public boolean process(Set<? extends TypeElement> annotations,
                                                     RoundEnvironment roundEnv) {
                                  if (roundEnv.processingOver() && "OK".equals(Dependency.get())) {
                                      processingEnv.getMessager().printNote("Called!");
                                  }
                                  return false;
                              }
                              public SourceVersion getSupportedSourceVersion() {
                                  return SourceVersion.latest();
                              }
                          }
                          """);

        Files.createDirectories(processorBClasses);

        new JavacTask(tb, Task.Mode.EXEC)
                .outdir(processorBClasses)
                .files(tb.findJavaFiles(processorBSrc))
                .classpath(depClasses.toString())
                .run()
                .writeAll();

        tb.writeFile(processorBClasses.resolve("META-INF/services").resolve(Processor.class.getName()),
                     """
                     proc.ProcessorImpl
                     """);

        new JarTask(tb, processorBJar)
                .baseDir(processorBClasses)
                .files("META-INF/services/" + Processor.class.getName(),
                       "proc/ProcessorImpl.class")
                .classpath(depClasses.toString())
                .run();

        tb.writeJavaFiles(targetASrc,
                          """
                          package target;

                          import annotation.MyAnnotation;

                          @MyAnnotation
                          public class MyClass {}
                          """);

        Files.createDirectories(targetAClasses);
        Files.createDirectories(targetAGenerated);

        tb.writeJavaFiles(targetBSrc,
                          """
                          package target;

                          public class MyClass {}
                          """);

        Files.createDirectories(targetBClasses);
        Files.createDirectories(targetBGenerated);
    }

    @ParameterizedTest
    @ValueSource(ints = { 0, 1 })
    public void processorOnClassPath(int index) throws IOException {

        List<String> out = new JavacTask(tb, Task.Mode.EXEC)
                .options("-proc:only", "-XprintProcessorInfo", "-XprintRounds",
                         "-s", targetGenerated[index].toString(),
                         "--class-path", processorJar[index].toString())
                .outdir(targetClasses[index])
                .files(tb.findJavaFiles(targetSrc[index]))
                .run()
                .writeAll()
                .getOutputLines(Task.OutputKind.STDERR);

        if (!Objects.equals(expected, out.getLast())) {
            throw new AssertionError("Unexpected result: " + out +
                                     ", expected: " + expected);
        }
    }

    @ParameterizedTest
    @ValueSource(ints = { 0, 1 })
    public void processorOnProcessorPath(int index) throws IOException {

        List<String> out = new JavacTask(tb, Task.Mode.EXEC)
                .options("-proc:only", "-XprintProcessorInfo", "-XprintRounds",
                         "-s", targetGenerated[index].toString(),
                         "--processor-path", processorJar[index].toString())
                .outdir(targetClasses[index])
                .files(tb.findJavaFiles(targetSrc[index]))
                .run()
                .writeAll()
                .getOutputLines(Task.OutputKind.STDERR);

        if (!Objects.equals(expected, out.getLast())) {
            throw new AssertionError("Unexpected result: " + out +
                                     ", expected: " + expected);
        }
    }

    @ParameterizedTest
    @ValueSource(ints = { 0, 1 })
    public void processorOnExtendedProcessorPath(int index) throws IOException {

        List<String> out = new JavacTask(tb, Task.Mode.EXEC)
                .options("-proc:only", "-XprintProcessorInfo", "-XprintRounds",
                         "-s", targetGenerated[index].toString(),
                         "--processor-path", processorJar[index].toString() + ":" + dependencies[index].toString())
                .outdir(targetClasses[index])
                .files(tb.findJavaFiles(targetSrc[index]))
                .run()
                .writeAll()
                .getOutputLines(Task.OutputKind.STDERR);

        if (!Objects.equals(expected, out.getLast())) {
            throw new AssertionError("Unexpected result: " + out +
                                     ", expected: " + expected);
        }
    }

    @ParameterizedTest
    @ValueSource(ints = { 0, 1 })
    public void processorOnProcessorPathWithClasspath(int index) throws IOException {

        List<String> out = new JavacTask(tb, Task.Mode.EXEC)
                .options("-proc:only", "-XprintProcessorInfo", "-XprintRounds",
                         "-s", targetGenerated[index].toString(),
                         "--processor-path", processorJar[index].toString())
                .outdir(targetClasses[index])
                .files(tb.findJavaFiles(targetSrc[index]))
                .classpath(dependencies[index].toString())
                .run()
                .writeAll()
                .getOutputLines(Task.OutputKind.STDERR);

        if (!Objects.equals(expected, out.getLast())) {
            throw new AssertionError("Unexpected result: " + out +
                                     ", expected: " + expected);
        }
    }

    @ParameterizedTest
    @ValueSource(ints = { 0, 1 })
    public void processorOnExtendedProcessorPathWithClasspath(int index) throws IOException {

        List<String> out = new JavacTask(tb, Task.Mode.EXEC)
                .options("-proc:only", "-XprintProcessorInfo", "-XprintRounds",
                         "-s", targetGenerated[index].toString(),
                         "--processor-path", processorJar[index].toString() + ":" + dependencies[index].toString())
                .outdir(targetClasses[index])
                .files(tb.findJavaFiles(targetSrc[index]))
                .classpath(dependencies[index].toString())
                .run()
                .writeAll()
                .getOutputLines(Task.OutputKind.STDERR);

        if (!Objects.equals(expected, out.getLast())) {
            throw new AssertionError("Unexpected result: " + out +
                                     ", expected: " + expected);
        }
    }
}
