diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java index 5b746e5ac4e..f804174c711 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.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 @@ -34,9 +34,6 @@ import com.sun.tools.javac.resources.LauncherProperties.Errors; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Context.Factory; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.NestingKind; -import javax.lang.model.element.TypeElement; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; @@ -112,10 +109,9 @@ final class MemoryContext { * Any messages generated during compilation will be written to the stream * provided when this object was created. * - * @return the list of top-level types defined in the source file * @throws Fault if any compilation errors occur, or if no class was found */ - List compileProgram() throws Fault { + void compileProgram() throws Fault { var units = new ArrayList(); units.add(descriptor.fileObject()); if (descriptor.isModular()) { @@ -126,35 +122,10 @@ final class MemoryContext { var context = new Context(); MemoryPreview.registerInstance(context); var task = compiler.getTask(out, memoryFileManager, null, opts, null, units, context); - var fileUri = descriptor.fileObject().toUri(); - var names = new ArrayList(); - task.addTaskListener(new TaskListener() { - @Override - public void started(TaskEvent event) { - if (event.getKind() != TaskEvent.Kind.ANALYZE) return; - TypeElement element = event.getTypeElement(); - if (element.getNestingKind() != NestingKind.TOP_LEVEL) return; - JavaFileObject source = event.getSourceFile(); - if (source == null) return; - if (!source.toUri().equals(fileUri)) return; - ElementKind kind = element.getKind(); - if (kind != ElementKind.CLASS - && kind != ElementKind.ENUM - && kind != ElementKind.INTERFACE - && kind != ElementKind.RECORD) - return; - var name = element.getQualifiedName().toString(); - names.add(name); - } - }); var ok = task.call(); if (!ok) { throw new Fault(Errors.CompilationFailed); } - if (names.isEmpty()) { - throw new Fault(Errors.NoClass); - } - return List.copyOf(names); } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java index 9c9831ecedf..087b1708acd 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.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 @@ -25,6 +25,7 @@ package com.sun.tools.javac.launcher; +import com.sun.source.tree.ClassTree; import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.resources.LauncherProperties.Errors; @@ -33,6 +34,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -46,30 +48,46 @@ import java.util.TreeSet; * risk. This code and its internal interfaces are subject to change * or deletion without notice.

*/ -public record ProgramDescriptor(ProgramFileObject fileObject, Optional packageName, Path sourceRootPath) { +public record ProgramDescriptor( + ProgramFileObject fileObject, + Optional packageName, + List qualifiedTypeNames, + Path sourceRootPath) { static ProgramDescriptor of(ProgramFileObject fileObject) throws Fault { var file = fileObject.getFile(); + var packageName = ""; // empty string will be converted into an empty optional + var packageNameAndDot = ""; // empty string or packageName + '.' + var qualifiedTypeNames = new ArrayList(); try { var compiler = JavacTool.create(); var standardFileManager = compiler.getStandardFileManager(null, null, null); var units = List.of(fileObject); var task = compiler.getTask(null, standardFileManager, diagnostic -> {}, null, null, units); - for (var tree : task.parse()) { - var packageTree = tree.getPackage(); - if (packageTree != null) { - var packageName = packageTree.getPackageName().toString(); - var root = computeSourceRootPath(file, packageName); - return new ProgramDescriptor(fileObject, Optional.of(packageName), root); + var tree = task.parse().iterator().next(); // single compilation unit + var packageTree = tree.getPackage(); + if (packageTree != null) { + packageName = packageTree.getPackageName().toString(); + packageNameAndDot = packageName + '.'; + } + for (var type : tree.getTypeDecls()) { + if (type instanceof ClassTree classType) { + qualifiedTypeNames.add(packageNameAndDot + classType.getSimpleName()); } } } catch (IOException ignore) { // fall through to let actual compilation determine the error message } - var root = computeSourceRootPath(file, ""); - return new ProgramDescriptor(fileObject, Optional.empty(), root); + if (qualifiedTypeNames.isEmpty()) { + throw new Fault(Errors.NoClass); + } + return new ProgramDescriptor( + fileObject, + packageName.isEmpty() ? Optional.empty() : Optional.of(packageName), + List.copyOf(qualifiedTypeNames), + computeSourceRootPath(file, packageName)); } - public static Path computeSourceRootPath(Path program, String packageName) { + public static Path computeSourceRootPath(Path program, String packageName) throws Fault { var absolute = program.normalize().toAbsolutePath(); var absoluteRoot = absolute.getRoot(); assert absoluteRoot != null; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java index 75dc779cb33..4ae6b841542 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.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 @@ -147,10 +147,10 @@ public final class SourceLauncher { ProgramDescriptor program = ProgramDescriptor.of(ProgramFileObject.of(file)); RelevantJavacOptions options = RelevantJavacOptions.of(program, runtimeArgs); MemoryContext context = new MemoryContext(out, program, options); - List names = context.compileProgram(); + context.compileProgram(); String[] mainArgs = Arrays.copyOfRange(args, 1, args.length); - var appClass = execute(names, mainArgs, context); + var appClass = execute(context, mainArgs); return new Result(appClass, context.getNamesOfCompiledClasses()); } @@ -184,20 +184,20 @@ public final class SourceLauncher { * Invokes the {@code main} method of a program class, using a class loader that * will load recently compiled classes from memory. * - * @param topLevelClassNames the names of classes in the program compilation unit * @param mainArgs the arguments for the {@code main} method * @param context the context for the class to be executed * @throws Fault if there is a problem finding or invoking the {@code main} method * @throws InvocationTargetException if the {@code main} method throws an exception */ - private Class execute(List topLevelClassNames, String[] mainArgs, MemoryContext context) + private Class execute(MemoryContext context, String[] mainArgs) throws Fault, InvocationTargetException { System.setProperty("jdk.launcher.sourcefile", context.getSourceFileAsString()); ClassLoader parentLoader = ClassLoader.getSystemClassLoader(); + ProgramDescriptor program = context.getProgramDescriptor(); // 1. Find a main method in the first class and if there is one - invoke it Class firstClass; - String firstClassName = topLevelClassNames.getFirst(); + String firstClassName = program.qualifiedTypeNames().getFirst(); try { ClassLoader loader = context.newClassLoaderFor(parentLoader, firstClassName); firstClass = Class.forName(firstClassName, false, loader); @@ -208,10 +208,14 @@ public final class SourceLauncher { Method mainMethod = MethodFinder.findMainMethod(firstClass); if (mainMethod == null) { // 2. If the first class doesn't have a main method, look for a class with a matching name - var compilationUnitName = context.getProgramDescriptor().fileObject().getFile().getFileName().toString(); + var compilationUnitName = program.fileObject().getFile().getFileName().toString(); assert compilationUnitName.endsWith(".java"); - var expectedName = compilationUnitName.substring(0, compilationUnitName.length() - 5); - var actualName = topLevelClassNames.stream() + var expectedSimpleName = compilationUnitName.substring(0, compilationUnitName.length() - 5); + var expectedPackageName = program.packageName().orElse(""); + var expectedName = expectedPackageName.isEmpty() + ? expectedSimpleName + : expectedPackageName + '.' + expectedSimpleName; + var actualName = program.qualifiedTypeNames().stream() .filter(name -> name.equals(expectedName)) .findFirst() .orElseThrow(() -> new Fault(Errors.CantFindClass(expectedName))); diff --git a/test/langtools/tools/javac/launcher/SourceLauncherTest.java b/test/langtools/tools/javac/launcher/SourceLauncherTest.java index cfa6c8ec9e5..6534f9f4dd0 100644 --- a/test/langtools/tools/javac/launcher/SourceLauncherTest.java +++ b/test/langtools/tools/javac/launcher/SourceLauncherTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8192920 8204588 8246774 8248843 8268869 8235876 + * @bug 8192920 8204588 8246774 8248843 8268869 8235876 8328339 * @summary Test source launcher * @library /tools/lib * @enablePreview @@ -110,6 +110,27 @@ public class SourceLauncherTest extends TestRunner { testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n"); } + @Test + public void testHelloWorldInPackageWithStaticImport(Path base) throws IOException { + tb.writeJavaFiles(base, + """ + package hello; + import static hello.Helper.*; + import java.util.Arrays; + class World { + public static void main(String... args) { + m(args); + } + } + class Helper { + static void m(String... args) { + System.out.println("Hello World! " + Arrays.toString(args)); + } + } + """); + testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n"); + } + @Test public void testHelloWorldWithAux(Path base) throws IOException { tb.writeJavaFiles(base, @@ -300,7 +321,7 @@ public class SourceLauncherTest extends TestRunner { public void testMismatchOfPathAndPackage(Path base) throws IOException { Files.createDirectories(base); Path file = base.resolve("MismatchOfPathAndPackage.java"); - Files.write(file, List.of("package p;")); + Files.write(file, List.of("package p; class MismatchOfPathAndPackage {}")); testError(file, "", "error: end of path to source file does not match its package name p: " + file); }