8315458: Implement JEP 463: Implicitly Declared Classes and Instance Main Method (Second Preview)

Reviewed-by: jlahoda, mcimadamore, vromero, rriggs, alanb, mchung
This commit is contained in:
Jim Laskey 2023-11-30 12:49:49 +00:00
parent 03759e892d
commit 04ad98ed32
48 changed files with 612 additions and 664 deletions

View file

@ -190,30 +190,21 @@ import sun.reflect.misc.ReflectUtil;
* a class or interface is hidden has no bearing on the characteristics * a class or interface is hidden has no bearing on the characteristics
* exposed by the methods of class {@code Class}. * exposed by the methods of class {@code Class}.
* *
* <h2><a id=unnamedClasses>Unnamed Classes</a></h2> * <h2><a id=implicitClasses>Implicit Classes</a></h2>
*
* A {@code class} file representing an {@linkplain #isUnnamedClass unnamed class}
* is generated by a Java compiler from a source file for an unnamed class.
* The {@code Class} object representing an unnamed class is top-level,
* {@linkplain #isSynthetic synthetic}, and {@code final}. While an
* unnamed class does <em>not</em> have a name in its Java source
* form, several of the name-related methods of {@code java.lang.Class}
* do return non-null and non-empty results for the {@code Class}
* object representing an unnamed class.
* *
* Conventionally, a Java compiler, starting from a source file for an * Conventionally, a Java compiler, starting from a source file for an
* unnamed class, say {@code HelloWorld.java}, creates a * implicit class, say {@code HelloWorld.java}, creates a
* similarly-named {@code class} file, {@code HelloWorld.class}, where * similarly-named {@code class} file, {@code HelloWorld.class}, where
* the class stored in that {@code class} file is named {@code * the class stored in that {@code class} file is named {@code
* "HelloWorld"}, matching the base names of the source and {@code * "HelloWorld"}, matching the base names of the source and {@code
* class} files. * class} files.
* *
* For the {@code Class} object of an unnamed class {@code * For the {@code Class} object of an implicit class {@code
* HelloWorld}, the methods to get the {@linkplain #getName name} and * HelloWorld}, the methods to get the {@linkplain #getName name} and
* {@linkplain #getTypeName type name} return results * {@linkplain #getTypeName type name} return results
* equal to {@code "HelloWorld"}. The {@linkplain #getSimpleName * equal to {@code "HelloWorld"}. The {@linkplain #getSimpleName
* simple name} of such an unnamed class is the empty string and the * simple name} of such an implicit class is {@code "HelloWorld"} and the
* {@linkplain #getCanonicalName canonical name} is {@code null}. * {@linkplain #getCanonicalName canonical name} is {@code "HelloWorld"}.
* *
* @param <T> the type of the class modeled by this {@code Class} * @param <T> the type of the class modeled by this {@code Class}
* object. For example, the type of {@code String.class} is {@code * object. For example, the type of {@code String.class} is {@code
@ -1809,7 +1800,7 @@ public final class Class<T> implements java.io.Serializable,
/** /**
* Returns the simple name of the underlying class as given in the * Returns the simple name of the underlying class as given in the
* source code. An empty string is returned if the underlying class is * source code. An empty string is returned if the underlying class is
* {@linkplain #isAnonymousClass() anonymous} or {@linkplain #isUnnamedClass() unnamed}. * {@linkplain #isAnonymousClass() anonymous}.
* A {@linkplain #isSynthetic() synthetic class}, one not present * A {@linkplain #isSynthetic() synthetic class}, one not present
* in source code, can have a non-empty name including special * in source code, can have a non-empty name including special
* characters, such as "{@code $}". * characters, such as "{@code $}".
@ -1822,9 +1813,6 @@ public final class Class<T> implements java.io.Serializable,
* @since 1.5 * @since 1.5
*/ */
public String getSimpleName() { public String getSimpleName() {
if (isUnnamedClass()) {
return "";
}
ReflectionData<T> rd = reflectionData(); ReflectionData<T> rd = reflectionData();
String simpleName = rd.simpleName; String simpleName = rd.simpleName;
if (simpleName == null) { if (simpleName == null) {
@ -1874,7 +1862,6 @@ public final class Class<T> implements java.io.Serializable,
* <ul> * <ul>
* <li>a {@linkplain #isLocalClass() local class} * <li>a {@linkplain #isLocalClass() local class}
* <li>a {@linkplain #isAnonymousClass() anonymous class} * <li>a {@linkplain #isAnonymousClass() anonymous class}
* <li>an {@linkplain #isUnnamedClass() unnamed class}
* <li>a {@linkplain #isHidden() hidden class} * <li>a {@linkplain #isHidden() hidden class}
* <li>an array whose component type does not have a canonical name</li> * <li>an array whose component type does not have a canonical name</li>
* </ul> * </ul>
@ -1894,9 +1881,6 @@ public final class Class<T> implements java.io.Serializable,
* @since 1.5 * @since 1.5
*/ */
public String getCanonicalName() { public String getCanonicalName() {
if (isUnnamedClass()) {
return null;
}
ReflectionData<T> rd = reflectionData(); ReflectionData<T> rd = reflectionData();
String canonicalName = rd.canonicalName; String canonicalName = rd.canonicalName;
if (canonicalName == null) { if (canonicalName == null) {
@ -1931,33 +1915,12 @@ public final class Class<T> implements java.io.Serializable,
} }
} }
/**
* {@return {@code true} if and only if the underlying class
* is an unnamed class}
*
* @apiNote
* An unnamed class is not an {@linkplain #isAnonymousClass anonymous class}.
*
* @since 21
*
* @jls 7.3 Compilation Units
*/
@PreviewFeature(feature=PreviewFeature.Feature.UNNAMED_CLASSES,
reflective=true)
public boolean isUnnamedClass() {
return PreviewFeatures.isEnabled() && isSynthetic()
&& isTopLevelClass()
&& Modifier.isFinal(getModifiers());
}
/** /**
* Returns {@code true} if and only if the underlying class * Returns {@code true} if and only if the underlying class
* is an anonymous class. * is an anonymous class.
* *
* @apiNote * @apiNote
* An anonymous class is not a {@linkplain #isHidden() hidden class}. * An anonymous class is not a {@linkplain #isHidden() hidden class}.
* An anonymous class is not an {@linkplain #isUnnamedClass() unnamed class}.
* *
* @return {@code true} if and only if this class is an anonymous class. * @return {@code true} if and only if this class is an anonymous class.
* @since 1.5 * @since 1.5
@ -2922,6 +2885,21 @@ public final class Class<T> implements java.io.Serializable,
return result; return result;
} }
/**
* Returns the most specific {@code Method} object of this class, super class or
* interface that have the specified method name and parameter types.
*
* @param publicOnly true if only public methods are examined, otherwise all methods
* @param name the name of the method
* @param parameterTypes the parameter array
* @return the {@code Method} object for the method found from this class matching
* the specified name and parameters, or null if not found
*/
Method findMethod(boolean publicOnly, String name, Class<?>... parameterTypes) {
PublicMethods.MethodList res = getMethodsRecursive(name, parameterTypes, true, publicOnly);
return res == null ? null : getReflectionFactory().copyMethod(res.getMostSpecific());
}
/** /**
* Returns a {@code Constructor} object that reflects the specified * Returns a {@code Constructor} object that reflects the specified
* constructor of the class represented by this * constructor of the class represented by this
@ -3750,7 +3728,7 @@ public final class Class<T> implements java.io.Serializable,
PublicMethods.MethodList res = getMethodsRecursive( PublicMethods.MethodList res = getMethodsRecursive(
name, name,
parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes, parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
/* includeStatic */ true); /* includeStatic */ true, /* publicOnly */ true);
return res == null ? null : res.getMostSpecific(); return res == null ? null : res.getMostSpecific();
} }
@ -3759,9 +3737,10 @@ public final class Class<T> implements java.io.Serializable,
// via ReflectionFactory.copyMethod. // via ReflectionFactory.copyMethod.
private PublicMethods.MethodList getMethodsRecursive(String name, private PublicMethods.MethodList getMethodsRecursive(String name,
Class<?>[] parameterTypes, Class<?>[] parameterTypes,
boolean includeStatic) { boolean includeStatic,
// 1st check declared public methods boolean publicOnly) {
Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true); // 1st check declared methods
Method[] methods = privateGetDeclaredMethods(publicOnly);
PublicMethods.MethodList res = PublicMethods.MethodList PublicMethods.MethodList res = PublicMethods.MethodList
.filter(methods, name, parameterTypes, includeStatic); .filter(methods, name, parameterTypes, includeStatic);
// if there is at least one match among declared methods, we need not // if there is at least one match among declared methods, we need not
@ -3775,15 +3754,14 @@ public final class Class<T> implements java.io.Serializable,
// we must consult the superclass (if any) recursively... // we must consult the superclass (if any) recursively...
Class<?> sc = getSuperclass(); Class<?> sc = getSuperclass();
if (sc != null) { if (sc != null) {
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic); res = sc.getMethodsRecursive(name, parameterTypes, includeStatic, publicOnly);
} }
// ...and coalesce the superclass methods with methods obtained // ...and coalesce the superclass methods with methods obtained
// from directly implemented interfaces excluding static methods... // from directly implemented interfaces excluding static methods...
for (Class<?> intf : getInterfaces(/* cloneArray */ false)) { for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
res = PublicMethods.MethodList.merge( res = PublicMethods.MethodList.merge(
res, intf.getMethodsRecursive(name, parameterTypes, res, intf.getMethodsRecursive(name, parameterTypes, /* includeStatic */ false, publicOnly));
/* includeStatic */ false));
} }
return res; return res;

View file

@ -2345,6 +2345,9 @@ public final class System {
public List<Method> getDeclaredPublicMethods(Class<?> klass, String name, Class<?>... parameterTypes) { public List<Method> getDeclaredPublicMethods(Class<?> klass, String name, Class<?>... parameterTypes) {
return klass.getDeclaredPublicMethods(name, parameterTypes); return klass.getDeclaredPublicMethods(name, parameterTypes);
} }
public Method findMethod(Class<?> klass, boolean publicOnly, String name, Class<?>... parameterTypes) {
return klass.findMethod(publicOnly, name, parameterTypes);
}
public jdk.internal.reflect.ConstantPool getConstantPool(Class<?> klass) { public jdk.internal.reflect.ConstantPool getConstantPool(Class<?> klass) {
return klass.getConstantPool(); return klass.getConstantPool();
} }

View file

@ -65,6 +65,11 @@ public interface JavaLangAccess {
*/ */
List<Method> getDeclaredPublicMethods(Class<?> klass, String name, Class<?>... parameterTypes); List<Method> getDeclaredPublicMethods(Class<?> klass, String name, Class<?>... parameterTypes);
/**
* Return most specific method that matches name and parameterTypes.
*/
Method findMethod(Class<?> klass, boolean publicOnly, String name, Class<?>... parameterTypes);
/** /**
* Return the constant pool for a class. * Return the constant pool for a class.
*/ */

View file

@ -69,8 +69,10 @@ public @interface PreviewFeature {
FOREIGN, FOREIGN,
@JEP(number=459, title="String Templates", status="Second Preview") @JEP(number=459, title="String Templates", status="Second Preview")
STRING_TEMPLATES, STRING_TEMPLATES,
@JEP(number=445, title="Unnamed Classes and Instance Main Methods") @JEP(number=445, title="Unnamed Classes and Instance Main Methods", status="Deprecated")
UNNAMED_CLASSES, UNNAMED_CLASSES,
@JEP(number=463, title="Implicit Classes and Instance Main Methods", status="Preview")
IMPLICIT_CLASSES,
@JEP(number=446, title="Scoped Values", status="Preview") @JEP(number=446, title="Scoped Values", status="Preview")
SCOPED_VALUES, SCOPED_VALUES,
@JEP(number=453, title="Structured Concurrency", status="Preview") @JEP(number=453, title="Structured Concurrency", status="Preview")

View file

@ -1,169 +0,0 @@
/*
* Copyright (c) 2023, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.internal.misc;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class MainMethodFinder {
private static boolean correctArgs(Method method) {
int argc = method.getParameterCount();
return argc == 0 || argc == 1 && method.getParameterTypes()[0] == String[].class;
}
/**
* Gather all the "main" methods in the class hierarchy.
*
* @param refc the main class or super class
* @param mains accumulated main methods
* @param isMainClass the class is the main class and not a super class
*/
private static void gatherMains(Class<?> refc, List<Method> mains, boolean isMainClass) {
if (refc != null && refc != Object.class) {
for (Method method : refc.getDeclaredMethods()) {
int mods = method.getModifiers();
// Must be named "main", public|protected|package-private, not synthetic (bridge) and either
// no arguments or one string array argument. Only statics in the Main class are acceptable.
if ("main".equals(method.getName()) &&
!method.isSynthetic() &&
!Modifier.isPrivate(mods) &&
correctArgs(method) &&
(isMainClass || !Modifier.isStatic(mods)))
{
mains.add(method);
}
}
gatherMains(refc.getSuperclass(), mains, false);
}
}
/**
* Comparator for two methods.
* Priority order is;
* sub-class < super-class.
* static < non-static,
* string arg < no arg and
*
* @param a first method
* @param b second method
*
* @return -1, 0 or 1 to represent higher priority. equals priority or lesser priority.
*/
private static int compareMethods(Method a, Method b) {
Class<?> aClass = a.getDeclaringClass();
Class<?> bClass = b.getDeclaringClass();
if (aClass != bClass) {
if (bClass.isAssignableFrom(aClass)) {
return -1;
} else {
return 1;
}
}
int aMods = a.getModifiers();
int bMods = b.getModifiers();
boolean aIsStatic = Modifier.isStatic(aMods);
boolean bIsStatic = Modifier.isStatic(bMods);
if (aIsStatic && !bIsStatic) {
return -1;
} else if (!aIsStatic && bIsStatic) {
return 1;
}
int aCount = a.getParameterCount();
int bCount = b.getParameterCount();
if (bCount < aCount) {
return -1;
} else if (aCount < bCount) {
return 1;
}
return 0;
}
/**
* Return the traditional main method or null if not found.
*
* @param mainClass main class
*
* @return main method or null
*/
private static Method getTraditionalMain(Class<?> mainClass) {
try {
Method traditionalMain = mainClass.getMethod("main", String[].class);
int mods = traditionalMain.getModifiers();
if (Modifier.isStatic(mods) && Modifier.isPublic(mods) && traditionalMain.getReturnType() == void.class) {
return traditionalMain;
}
} catch (NoSuchMethodException ex) {
// not found
}
return null;
}
/**
* {@return priority main method if none found}
*
* @param mainClass main class
*
* @throws NoSuchMethodException when not preview and no method found
*/
public static Method findMainMethod(Class<?> mainClass) throws NoSuchMethodException {
boolean isTraditionMain = !PreviewFeatures.isEnabled();
if (isTraditionMain) {
return mainClass.getMethod("main", String[].class);
}
List<Method> mains = new ArrayList<>();
gatherMains(mainClass, mains, true);
if (mains.isEmpty()) {
throw new NoSuchMethodException("No main method found");
}
if (1 < mains.size()) {
mains.sort(MainMethodFinder::compareMethods);
}
Method mainMethod = mains.get(0);
Method traditionalMain = getTraditionalMain(mainClass);
if (traditionalMain != null && !traditionalMain.equals(mainMethod)) {
System.err.println("WARNING: \"" + mains.get(0) + "\" chosen over \"" + traditionalMain + "\"");
}
return mains.get(0);
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2023, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.internal.misc;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
/**
* A collection of static methods that return specific method objects of interest.
*/
public class MethodFinder {
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
private MethodFinder() {
throw new AssertionError("private constructor");
}
/**
* Return the first method that meets the requirements of an application main method
* {@jls 12.1.4}. The method must:
* <ul>
* <li>be declared in this class's hierarchy</li>
* <li>have the name "main"</li>
* <li>have a single argument of type {@code String[]}, {@code String...} or no argument</li>
* <li>have the return type of void</li>
* <li>be public, protected or package private</li>
* <li>not be abstract</li>
*</ul>
*
* The method returned would be used by a launcher to initiate the execution of an
* application.
*
* Searching continues until a main method is found or the search is exhausted. The
* primary search occurs in two phases, once for a main method with a {@code
* String[]} or {@code String...} argument and failing that, once for a main method
* with a no arguments. The search itself uses recursion to first look at methods
* in this class, then default methods in this class's interface hierarchy and
* then repeating these steps with the class's super class.
*
* @apiNote The method returned may be declared in this class, a super class
* or as a default method of an interface that the class or super class
* implements.
* <p>It is not possible to declare a static main method and instance main
* method with the same signature in the same class. {@jls 8.4.2} states that
* "It is a compile-time error to declare two methods with override-equivalent
* signatures in a class."
* <p>{@link SecurityException SecurityExceptions} can halt
* the search. In this case, a null is returned.
*
* @return the main method if a method found or null if no method is found
*
* @jls 8.2 Class Members
* @jls 8.4 Method Declarations
* @jls 8.4.2 Method Signature
* @jls 12.1.4 Invoke a main method
*/
public static Method findMainMethod(Class<?> cls) {
boolean isPreview = PreviewFeatures.isEnabled();
Method mainMethod = JLA.findMethod(cls, !isPreview, "main", String[].class);
if (isPreview && mainMethod == null) {
mainMethod = JLA.findMethod(cls, false, "main");
}
if (mainMethod == null) {
return null;
}
int mods = mainMethod.getModifiers();
if (Modifier.isAbstract(mods) ||
mainMethod.getReturnType() != void.class ||
(isPreview && Modifier.isPrivate(mods)) ||
(!isPreview && !Modifier.isStatic(mods))) {
return null;
}
return mainMethod;
}
}

View file

@ -71,7 +71,7 @@ import java.util.jar.Manifest;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.internal.misc.MainMethodFinder; import jdk.internal.misc.MethodFinder;
import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.PreviewFeatures;
import jdk.internal.misc.VM; import jdk.internal.misc.VM;
import jdk.internal.module.ModuleBootstrap; import jdk.internal.module.ModuleBootstrap;
@ -709,7 +709,7 @@ public final class LauncherHelper {
/** /**
* This method: * This method:
* 1. Loads the main class from the module or class path * 1. Loads the main class from the module or class path
* 2. Checks the public static void main method. * 2. Checks for a valid main method.
* 3. If the main class extends FX Application then call on FXHelper to * 3. If the main class extends FX Application then call on FXHelper to
* perform the launch. * perform the launch.
* *
@ -753,7 +753,7 @@ public final class LauncherHelper {
mainClass = FXHelper.class; mainClass = FXHelper.class;
} }
validateMainClass(mainClass); validateMainMethod(mainClass);
return mainClass; return mainClass;
} }
@ -859,7 +859,7 @@ public final class LauncherHelper {
} }
} }
} catch (LinkageError le) { } catch (LinkageError le) {
abort(le, "java.launcher.cls.error6", cn, abort(le, "java.launcher.cls.error4", cn,
le.getClass().getName() + ": " + le.getLocalizedMessage()); le.getClass().getName() + ": " + le.getLocalizedMessage());
} }
return mainClass; return mainClass;
@ -890,54 +890,28 @@ public final class LauncherHelper {
return false; return false;
} }
/* // Check the existence and signature of main and abort if incorrect.
* main type flags private static void validateMainMethod(Class<?> mainClass) {
*/
private static final int MAIN_WITHOUT_ARGS = 1;
private static final int MAIN_NONSTATIC = 2;
private static int mainType = 0;
/*
* Return type so that launcher invokes the correct main
*/
public static int getMainType() {
return mainType;
}
private static void setMainType(Method mainMethod) {
int mods = mainMethod.getModifiers();
boolean isStatic = Modifier.isStatic(mods);
boolean noArgs = mainMethod.getParameterCount() == 0;
mainType = (isStatic ? 0 : MAIN_NONSTATIC) | (noArgs ? MAIN_WITHOUT_ARGS : 0);
}
// Check the existence and signature of main and abort if incorrect
static void validateMainClass(Class<?> mainClass) {
Method mainMethod = null; Method mainMethod = null;
try { try {
mainMethod = MainMethodFinder.findMainMethod(mainClass); mainMethod = MethodFinder.findMainMethod(mainClass);
} catch (NoSuchMethodException nsme) {
if (mainMethod == null) {
// invalid main or not FX application, abort with an error // invalid main or not FX application, abort with an error
abort(null, "java.launcher.cls.error4", mainClass.getName(), abort(null, "java.launcher.cls.error2", mainClass.getName(),
JAVAFX_APPLICATION_CLASS_NAME); JAVAFX_APPLICATION_CLASS_NAME);
}
} catch (Throwable e) { } catch (Throwable e) {
if (mainClass.getModule().isNamed()) { if (mainClass.getModule().isNamed()) {
abort(e, "java.launcher.module.error5", abort(e, "java.launcher.module.error3",
mainClass.getName(), mainClass.getModule().getName(), mainClass.getName(), mainClass.getModule().getName(),
e.getClass().getName(), e.getLocalizedMessage()); e.getClass().getName(), e.getLocalizedMessage());
} else { } else {
abort(e, "java.launcher.cls.error7", mainClass.getName(), abort(e, "java.launcher.cls.error5", mainClass.getName(),
e.getClass().getName(), e.getLocalizedMessage()); e.getClass().getName(), e.getLocalizedMessage());
} }
} }
setMainType(mainMethod);
/*
* findMainMethod (above) will choose the correct method, based
* on its name and parameter type, however, we still have to
* ensure that the method is static (non-preview) and returns a void.
*/
int mods = mainMethod.getModifiers(); int mods = mainMethod.getModifiers();
boolean isStatic = Modifier.isStatic(mods); boolean isStatic = Modifier.isStatic(mods);
boolean isPublic = Modifier.isPublic(mods); boolean isPublic = Modifier.isPublic(mods);
@ -945,32 +919,26 @@ public final class LauncherHelper {
if (!PreviewFeatures.isEnabled()) { if (!PreviewFeatures.isEnabled()) {
if (!isStatic || !isPublic || noArgs) { if (!isStatic || !isPublic || noArgs) {
abort(null, "java.launcher.cls.error2", "static", abort(null, "java.launcher.cls.error2", mainClass.getName(),
mainMethod.getDeclaringClass().getName()); JAVAFX_APPLICATION_CLASS_NAME);
} }
return;
} }
if (!isStatic) { if (!isStatic) {
String className = mainMethod.getDeclaringClass().getName();
if (mainClass.isMemberClass() && !Modifier.isStatic(mainClass.getModifiers())) { if (mainClass.isMemberClass() && !Modifier.isStatic(mainClass.getModifiers())) {
abort(null, "java.launcher.cls.error9", abort(null, "java.launcher.cls.error7", className);
mainMethod.getDeclaringClass().getName());
} }
try { try {
Constructor<?> constructor = mainClass.getDeclaredConstructor(); Constructor<?> constructor = mainClass.getDeclaredConstructor();
if (Modifier.isPrivate(constructor.getModifiers())) { if (Modifier.isPrivate(constructor.getModifiers())) {
abort(null, "java.launcher.cls.error8", abort(null, "java.launcher.cls.error6", className);
mainMethod.getDeclaringClass().getName());
} }
} catch (Throwable ex) { } catch (Throwable ex) {
abort(null, "java.launcher.cls.error8", abort(null, "java.launcher.cls.error6", className);
mainMethod.getDeclaringClass().getName());
} }
} }
if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
abort(null, "java.launcher.cls.error3",
mainMethod.getDeclaringClass().getName());
}
} }
private static final String encprop = "sun.jnu.encoding"; private static final String encprop = "sun.jnu.encoding";
@ -1122,13 +1090,13 @@ public final class LauncherHelper {
// find the module with the FX launcher // find the module with the FX launcher
Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME); Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
if (om.isEmpty()) { if (om.isEmpty()) {
abort(null, "java.launcher.cls.error5"); abort(null, "java.launcher.cls.error3");
} }
// load the FX launcher class // load the FX launcher class
fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME); fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME);
if (fxLauncherClass == null) { if (fxLauncherClass == null) {
abort(null, "java.launcher.cls.error5"); abort(null, "java.launcher.cls.error3");
} }
try { try {
@ -1149,7 +1117,7 @@ public final class LauncherHelper {
abort(null, "java.launcher.javafx.error1"); abort(null, "java.launcher.javafx.error1");
} }
} catch (NoSuchMethodException ex) { } catch (NoSuchMethodException ex) {
abort(ex, "java.launcher.cls.error5", ex); abort(ex, "java.launcher.cls.error3", ex);
} }
fxLaunchName = what; fxLaunchName = what;

View file

@ -238,29 +238,22 @@ java.launcher.cls.error1=\
Error: Could not find or load main class {0}\n\ Error: Could not find or load main class {0}\n\
Caused by: {1}: {2} Caused by: {1}: {2}
java.launcher.cls.error2=\ java.launcher.cls.error2=\
Error: Main method is not {0} in class {1}, please define the main method as:\n\
\ public static void main(String[] args)
java.launcher.cls.error3=\
Error: Main method must return a value of type void in class {0}, please \n\
define the main method as:\n\
\ public static void main(String[] args)
java.launcher.cls.error4=\
Error: Main method not found in class {0}, please define the main method as:\n\ Error: Main method not found in class {0}, please define the main method as:\n\
\ public static void main(String[] args)\n\ \ public static void main(String[] args)\n\
or a JavaFX application class must extend {1} or a JavaFX application class must extend {1}
java.launcher.cls.error5=\ java.launcher.cls.error3=\
Error: JavaFX runtime components are missing, and are required to run this application Error: JavaFX runtime components are missing, and are required to run this application
java.launcher.cls.error6=\ java.launcher.cls.error4=\
Error: LinkageError occurred while loading main class {0}\n\ Error: LinkageError occurred while loading main class {0}\n\
\t{1} \t{1}
java.launcher.cls.error7=\ java.launcher.cls.error5=\
Error: Unable to initialize main class {0}\n\ Error: Unable to initialize main class {0}\n\
Caused by: {1}: {2} Caused by: {1}: {2}
java.launcher.cls.error8=\ java.launcher.cls.error6=\
Error: no non-private zero argument constructor found in class {0}\n\ Error: no non-private zero argument constructor found in class {0}\n\
remove private from existing constructor or define as:\n\ remove private from existing constructor or define as:\n\
\ public {0}() \ public {0}()
java.launcher.cls.error9=\ java.launcher.cls.error7=\
Error: non-static inner class {0} constructor can not be invoked \n\ Error: non-static inner class {0} constructor can not be invoked \n\
make inner class static or move inner class out to separate source file make inner class static or move inner class out to separate source file
java.launcher.jar.error1=\ java.launcher.jar.error1=\
@ -280,7 +273,7 @@ java.launcher.module.error2=\
Error: Could not find or load main class {0} in module {1} Error: Could not find or load main class {0} in module {1}
java.launcher.module.error3=\ java.launcher.module.error3=\
Error: Unable to load main class {0} in module {1}\n\ Error: Unable to load main class {0} in module {1}\n\
\t{2} Caused by: {2}
java.launcher.module.error4=\ java.launcher.module.error4=\
{0} not found {0} not found
java.launcher.module.error5=\ java.launcher.module.error5=\

View file

@ -387,6 +387,84 @@ JLI_Launch(int argc, char ** argv, /* main argc, argv */
} \ } \
} while (JNI_FALSE) } while (JNI_FALSE)
#define CHECK_EXCEPTION_FAIL() \
do { \
if ((*env)->ExceptionOccurred(env)) { \
(*env)->ExceptionClear(env); \
return 0; \
} \
} while (JNI_FALSE)
#define CHECK_EXCEPTION_NULL_FAIL(mainObject) \
do { \
if ((*env)->ExceptionOccurred(env)) { \
(*env)->ExceptionClear(env); \
return 0; \
} else if (mainObject == NULL) { \
return 0; \
} \
} while (JNI_FALSE)
/*
* Invoke a static main with arguments. Returns 1 (true) if successful otherwise
* processes the pending exception from GetStaticMethodID and returns 0 (false).
*/
int
invokeStaticMainWithArgs(JNIEnv *env, jclass mainClass, jobjectArray mainArgs) {
jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_FAIL();
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
return 1;
}
/*
* Invoke an instance main with arguments. Returns 1 (true) if successful otherwise
* processes the pending exception from GetMethodID and returns 0 (false).
*/
int
invokeInstanceMainWithArgs(JNIEnv *env, jclass mainClass, jobjectArray mainArgs) {
jmethodID constructor = (*env)->GetMethodID(env, mainClass, "<init>", "()V");
CHECK_EXCEPTION_FAIL();
jobject mainObject = (*env)->NewObject(env, mainClass, constructor);
CHECK_EXCEPTION_NULL_FAIL(mainObject);
jmethodID mainID = (*env)->GetMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_FAIL();
(*env)->CallVoidMethod(env, mainObject, mainID, mainArgs);
return 1;
}
/*
* Invoke a static main without arguments. Returns 1 (true) if successful otherwise
* processes the pending exception from GetStaticMethodID and returns 0 (false).
*/
int
invokeStaticMainWithoutArgs(JNIEnv *env, jclass mainClass) {
jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"()V");
CHECK_EXCEPTION_FAIL();
(*env)->CallStaticVoidMethod(env, mainClass, mainID);
return 1;
}
/*
* Invoke an instance main without arguments. Returns 1 (true) if successful otherwise
* processes the pending exception from GetMethodID and returns 0 (false).
*/
int
invokeInstanceMainWithoutArgs(JNIEnv *env, jclass mainClass) {
jmethodID constructor = (*env)->GetMethodID(env, mainClass, "<init>", "()V");
CHECK_EXCEPTION_FAIL();
jobject mainObject = (*env)->NewObject(env, mainClass, constructor);
CHECK_EXCEPTION_NULL_FAIL(mainObject);
jmethodID mainID = (*env)->GetMethodID(env, mainClass, "main",
"()V");
CHECK_EXCEPTION_FAIL();
(*env)->CallVoidMethod(env, mainObject, mainID);
return 1;
}
int int
JavaMain(void* _args) JavaMain(void* _args)
@ -403,9 +481,6 @@ JavaMain(void* _args)
jclass mainClass = NULL; jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched jclass appClass = NULL; // actual application class being launched
jobjectArray mainArgs; jobjectArray mainArgs;
jmethodID mainID;
jmethodID constructor;
jobject mainObject;
int ret = 0; int ret = 0;
jlong start = 0, end = 0; jlong start = 0, end = 0;
@ -491,9 +566,6 @@ JavaMain(void* _args)
ret = 1; ret = 1;
/* /*
* Get the application's main class. It also checks if the main
* method exists.
*
* See bugid 5030265. The Main-Class name has already been parsed * See bugid 5030265. The Main-Class name has already been parsed
* from the manifest, but not parsed properly for UTF-8 support. * from the manifest, but not parsed properly for UTF-8 support.
* Hence the code here ignores the value previously extracted and * Hence the code here ignores the value previously extracted and
@ -523,7 +595,7 @@ JavaMain(void* _args)
* consistent in the UI we need to track and report the application main class. * consistent in the UI we need to track and report the application main class.
*/ */
appClass = GetApplicationClass(env); appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1); CHECK_EXCEPTION_NULL_LEAVE(appClass);
/* Build platform specific argument array */ /* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc); mainArgs = CreateApplicationArgs(env, argv, argc);
@ -545,59 +617,15 @@ JavaMain(void* _args)
CHECK_EXCEPTION_LEAVE(1); CHECK_EXCEPTION_LEAVE(1);
/* /*
* The LoadMainClass not only loads the main class, it will also ensure * The main method is invoked here so that extraneous java stacks are not in
* that the main method's signature is correct, therefore further checking * the application stack trace.
* is not required. The main method is invoked here so that extraneous java
* stacks are not in the application stack trace.
*/ */
#define MAIN_WITHOUT_ARGS 1 if (!invokeStaticMainWithArgs(env, mainClass, mainArgs) &&
#define MAIN_NONSTATIC 2 !invokeInstanceMainWithArgs(env, mainClass, mainArgs) &&
!invokeStaticMainWithoutArgs(env, mainClass) &&
jclass helperClass = GetLauncherHelperClass(env); !invokeInstanceMainWithoutArgs(env, mainClass)) {
jmethodID getMainType = (*env)->GetStaticMethodID(env, helperClass, ret = 1;
"getMainType", LEAVE();
"()I");
CHECK_EXCEPTION_NULL_LEAVE(getMainType);
int mainType = (*env)->CallStaticIntMethod(env, helperClass, getMainType);
CHECK_EXCEPTION_LEAVE(mainType);
switch (mainType) {
case 0: {
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
break;
}
case MAIN_WITHOUT_ARGS: {
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"()V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallStaticVoidMethod(env, mainClass, mainID);
break;
}
case MAIN_NONSTATIC: {
constructor = (*env)->GetMethodID(env, mainClass, "<init>", "()V");
CHECK_EXCEPTION_NULL_LEAVE(constructor);
mainObject = (*env)->NewObject(env, mainClass, constructor);
CHECK_EXCEPTION_NULL_LEAVE(mainObject);
mainID = (*env)->GetMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallVoidMethod(env, mainObject, mainID, mainArgs);
break;
}
case MAIN_NONSTATIC | MAIN_WITHOUT_ARGS: {
constructor = (*env)->GetMethodID(env, mainClass, "<init>", "()V");
CHECK_EXCEPTION_NULL_LEAVE(constructor);
mainObject = (*env)->NewObject(env, mainClass, constructor);
CHECK_EXCEPTION_NULL_LEAVE(mainObject);
mainID = (*env)->GetMethodID(env, mainClass, "main",
"()V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallVoidMethod(env, mainObject, mainID);
break;
}
} }
/* /*
@ -1593,8 +1621,9 @@ NewPlatformStringArray(JNIEnv *env, char **strv, int strc)
} }
/* /*
* Loads a class and verifies that the main class is present and it is ok to * Calls LauncherHelper::checkAndLoadMain to verify that the main class
* call it for more details refer to the java implementation. * is present, it is ok to load the main class and then load the main class.
* For more details refer to the java implementation.
*/ */
static jclass static jclass
LoadMainClass(JNIEnv *env, int mode, char *name) LoadMainClass(JNIEnv *env, int mode, char *name)

View file

@ -177,12 +177,11 @@ public interface Filer {
* <p>Creating a source file in or for an <em>unnamed</em> package in a <em>named</em> * <p>Creating a source file in or for an <em>unnamed</em> package in a <em>named</em>
* module is <em>not</em> supported. * module is <em>not</em> supported.
* *
* <p>If the environment is configured to support {@linkplain * <p>If the environment is configured to support implicitly declared
* TypeElement#isUnnamed unnamed classes}, the name argument is * classes, the name argument is used to provide the leading component of the
* used to provide the leading component of the name used for the * name used for the output file. For example {@code filer.createSourceFile("Foo")}
* output file. For example {@code filer.createSourceFile("Foo")} * to create an implicitly declared class hosted in {@code Foo.java}. All
* to create an unnamed class hosted in {@code Foo.java}. All * implicit classes must be in an unnamed package.
* unnamed classes must be in an unnamed package.
* *
* @apiNote To use a particular {@linkplain * @apiNote To use a particular {@linkplain
* java.nio.charset.Charset charset} to encode the contents of the * java.nio.charset.Charset charset} to encode the contents of the
@ -263,12 +262,11 @@ public interface Filer {
* <p>Creating a class file in or for an <em>unnamed</em> package in a <em>named</em> * <p>Creating a class file in or for an <em>unnamed</em> package in a <em>named</em>
* module is <em>not</em> supported. * module is <em>not</em> supported.
* *
* <p>If the environment is configured to support {@linkplain * <p>If the environment is configured to support implicitly declared
* TypeElement#isUnnamed unnamed classes}, the name argument is * classes, the name argument is used to provide the leading component of the
* used to provide the leading component of the name used for the * name used for the output file. For example {@code filer.createSourceFile("Foo")}
* output file. For example {@code filer.createClassFile("Foo")} to * to create an implicitly declared class hosted in {@code Foo.java}. All
* create an unnamed class hosted in {@code Foo.class}. All unnamed * implicit classes must be in an unnamed package.
* classes must be in an unnamed package.
* *
* @apiNote To avoid subsequent errors, the contents of the class * @apiNote To avoid subsequent errors, the contents of the class
* file should be compatible with the {@linkplain * file should be compatible with the {@linkplain

View file

@ -155,7 +155,7 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
/** /**
* Returns the fully qualified name of this class or interface * Returns the fully qualified name of this class or interface
* element. More precisely, it returns the <i>canonical</i> name. * element. More precisely, it returns the <i>canonical</i> name.
* For local, anonymous, and {@linkplain #isUnnamed() unnamed} classes, which do not have canonical * For local, and anonymous classes, which do not have canonical
* names, an {@linkplain Name##empty_name empty name} is * names, an {@linkplain Name##empty_name empty name} is
* returned. * returned.
* *
@ -171,7 +171,6 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
* *
* @see Elements#getBinaryName * @see Elements#getBinaryName
* @jls 6.7 Fully Qualified Names and Canonical Names * @jls 6.7 Fully Qualified Names and Canonical Names
* @jls 7.3 Compilation Units
*/ */
Name getQualifiedName(); Name getQualifiedName();
@ -181,10 +180,6 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
* For an anonymous class, an {@linkplain Name##empty_name empty * For an anonymous class, an {@linkplain Name##empty_name empty
* name} is returned. * name} is returned.
* *
* For an {@linkplain #isUnnamed() unnamed} class, a name matching
* the base name of the hosting file, minus any extension, is
* returned.
*
* @return the simple name of this class or interface, * @return the simple name of this class or interface,
* an empty name for an anonymous class * an empty name for an anonymous class
* *
@ -192,22 +187,6 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
@Override @Override
Name getSimpleName(); Name getSimpleName();
/**
* {@return {@code true} if this is an unnamed class and {@code
* false} otherwise}
*
* @implSpec
* The default implementation of this method returns {@code false}.
*
* @jls 7.3 Compilation Units
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.UNNAMED_CLASSES,
reflective=true)
default boolean isUnnamed() {
return false;
}
/** /**
* Returns the direct superclass of this class or interface element. * Returns the direct superclass of this class or interface element.
* If this class or interface element represents an interface or the class * If this class or interface element represents an interface or the class

View file

@ -444,9 +444,6 @@ public class ClassFinder {
if (c.members_field == null) { if (c.members_field == null) {
try { try {
c.complete(); c.complete();
if ((c.flags_field & UNNAMED_CLASS) != 0) {
syms.removeClass(ps.modle, flatname);
}
} catch (CompletionFailure ex) { } catch (CompletionFailure ex) {
if (absent) { if (absent) {
syms.removeClass(ps.modle, flatname); syms.removeClass(ps.modle, flatname);

View file

@ -123,9 +123,9 @@ public class Flags {
*/ */
public static final int HASINIT = 1<<18; public static final int HASINIT = 1<<18;
/** Class is a unnamed top level class. /** Class is a implicit top level class.
*/ */
public static final int UNNAMED_CLASS = 1<<19; public static final int IMPLICIT_CLASS = 1<<19;
/** Flag is set for compiler-generated anonymous method symbols /** Flag is set for compiler-generated anonymous method symbols
* that `own' an initializer block. * that `own' an initializer block.
@ -499,7 +499,7 @@ public class Flags {
ANNOTATION(Flags.ANNOTATION), ANNOTATION(Flags.ANNOTATION),
DEPRECATED(Flags.DEPRECATED), DEPRECATED(Flags.DEPRECATED),
HASINIT(Flags.HASINIT), HASINIT(Flags.HASINIT),
UNNAMED_CLASS(Flags.UNNAMED_CLASS), IMPLICIT_CLASS(Flags.IMPLICIT_CLASS),
BLOCK(Flags.BLOCK), BLOCK(Flags.BLOCK),
FROM_SOURCE(Flags.FROM_SOURCE), FROM_SOURCE(Flags.FROM_SOURCE),
ENUM(Flags.ENUM), ENUM(Flags.ENUM),

View file

@ -210,7 +210,7 @@ public class Preview {
public boolean isPreview(Feature feature) { public boolean isPreview(Feature feature) {
return switch (feature) { return switch (feature) {
case STRING_TEMPLATES -> true; case STRING_TEMPLATES -> true;
case UNNAMED_CLASSES -> true; case IMPLICIT_CLASSES -> true;
case SUPER_INIT -> true; case SUPER_INIT -> true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing). //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true' //When real preview features will be added, this method can be implemented to return 'true'

View file

@ -245,7 +245,7 @@ public enum Source {
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK21, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL), UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK21, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
RECORD_PATTERNS(JDK21, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL), RECORD_PATTERNS(JDK21, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL), STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL),
UNNAMED_CLASSES(JDK21, Fragments.FeatureUnnamedClasses, DiagKind.PLURAL), IMPLICIT_CLASSES(JDK21, Fragments.FeatureImplicitClasses, DiagKind.PLURAL),
WARN_ON_ILLEGAL_UTF8(MIN, JDK21), WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
UNNAMED_VARIABLES(JDK22, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL), UNNAMED_VARIABLES(JDK22, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL),
SUPER_INIT(JDK22, Fragments.FeatureSuperInit, DiagKind.NORMAL), SUPER_INIT(JDK22, Fragments.FeatureSuperInit, DiagKind.NORMAL),

View file

@ -435,6 +435,10 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
return (flags_field & FINAL) != 0; return (flags_field & FINAL) != 0;
} }
public boolean isImplicit() {
return (flags_field & IMPLICIT_CLASS) != 0;
}
/** Is this symbol declared (directly or indirectly) local /** Is this symbol declared (directly or indirectly) local
* to a method or variable initializer? * to a method or variable initializer?
* Also includes fields of inner classes which are in * Also includes fields of inner classes which are in
@ -1256,7 +1260,6 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
/** A class for class symbols /** A class for class symbols
*/ */
@SuppressWarnings("preview") // isUnnamed()
public static class ClassSymbol extends TypeSymbol implements TypeElement { public static class ClassSymbol extends TypeSymbol implements TypeElement {
/** a scope for all class members; variables, methods and inner classes /** a scope for all class members; variables, methods and inner classes
@ -1370,7 +1373,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
@Override @DefinedBy(Api.LANGUAGE_MODEL) @Override @DefinedBy(Api.LANGUAGE_MODEL)
public Name getQualifiedName() { public Name getQualifiedName() {
return isUnnamed() ? fullname.subName(0, 0) /* empty name */ : fullname; return fullname;
} }
@Override @DefinedBy(Api.LANGUAGE_MODEL) @Override @DefinedBy(Api.LANGUAGE_MODEL)
@ -1551,7 +1554,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
@DefinedBy(Api.LANGUAGE_MODEL) @DefinedBy(Api.LANGUAGE_MODEL)
public NestingKind getNestingKind() { public NestingKind getNestingKind() {
apiComplete(); apiComplete();
if (owner.kind == PCK) // Handles unnamed classes as well if (owner.kind == PCK) // Handles implicit classes as well
return NestingKind.TOP_LEVEL; return NestingKind.TOP_LEVEL;
else if (name.isEmpty()) else if (name.isEmpty())
return NestingKind.ANONYMOUS; return NestingKind.ANONYMOUS;
@ -1642,11 +1645,6 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
public List<Type> getPermittedSubclasses() { public List<Type> getPermittedSubclasses() {
return permitted.map(s -> s.type); return permitted.map(s -> s.type);
} }
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public boolean isUnnamed() {
return (flags_field & Flags.UNNAMED_CLASS) != 0 ;
}
} }

View file

@ -5507,7 +5507,7 @@ public class Attr extends JCTree.Visitor {
chk.checkFunctionalInterface((JCClassDecl) env.tree, c); chk.checkFunctionalInterface((JCClassDecl) env.tree, c);
chk.checkLeaksNotAccessible(env, (JCClassDecl) env.tree); chk.checkLeaksNotAccessible(env, (JCClassDecl) env.tree);
if ((c.flags_field & Flags.UNNAMED_CLASS) != 0) { if (c.isImplicit()) {
chk.checkHasMain(env.tree.pos(), c); chk.checkHasMain(env.tree.pos(), c);
} }
} finally { } finally {

View file

@ -452,7 +452,8 @@ public class Check {
} }
} }
for (Symbol sym = s.owner; sym != null; sym = sym.owner) { for (Symbol sym = s.owner; sym != null; sym = sym.owner) {
if (sym.kind == TYP && sym.name == name && sym.name != names.error) { if (sym.kind == TYP && sym.name == name && sym.name != names.error &&
!sym.isImplicit()) {
duplicateError(pos, sym); duplicateError(pos, sym);
return true; return true;
} }
@ -2276,7 +2277,7 @@ public class Check {
} }
if (!found) { if (!found) {
log.error(pos, Errors.UnnamedClassDoesNotHaveMainMethod); log.error(pos, Errors.ImplicitClassDoesNotHaveMainMethod);
} }
} }

View file

@ -442,7 +442,7 @@ public class Enter extends JCTree.Visitor {
log.error(tree.pos(), log.error(tree.pos(),
Errors.ClassPublicShouldBeInFile(topElement, tree.name)); Errors.ClassPublicShouldBeInFile(topElement, tree.name));
} }
if ((tree.mods.flags & UNNAMED_CLASS) != 0) { if ((tree.mods.flags & IMPLICIT_CLASS) != 0) {
syms.removeClass(env.toplevel.modle, tree.name); syms.removeClass(env.toplevel.modle, tree.name);
} }
} else { } else {

View file

@ -3047,14 +3047,6 @@ public class ClassReader {
signatureBuffer = new byte[ns]; signatureBuffer = new byte[ns];
} }
readClass(c); readClass(c);
if (previewClassFile) {
if ((c.flags_field & SYNTHETIC) != 0 &&
c.owner.kind == PCK &&
(c.flags_field & AUXILIARY) == 0 &&
(c.flags_field & FINAL) != 0) {
c.flags_field |= UNNAMED_CLASS;
}
}
} }
public void readClassFile(ClassSymbol c) { public void readClassFile(ClassSymbol c) {

View file

@ -85,7 +85,7 @@ import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.resources.LauncherProperties.Errors; import com.sun.tools.javac.resources.LauncherProperties.Errors;
import com.sun.tools.javac.util.JCDiagnostic.Error; import com.sun.tools.javac.util.JCDiagnostic.Error;
import jdk.internal.misc.MainMethodFinder; import jdk.internal.misc.MethodFinder;
import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.PreviewFeatures;
import jdk.internal.misc.VM; import jdk.internal.misc.VM;
@ -407,8 +407,8 @@ public class Main {
throw new Fault(Errors.NoClass); throw new Fault(Errors.NoClass);
} }
TypeElement mainClass = l.mainClass; TypeElement mainClass = l.mainClass;
String mainClassName = (mainClass.isUnnamed() ? mainClass.getSimpleName() String mainClassName = mainClass.getQualifiedName().toString();
: mainClass.getQualifiedName()).toString();
return mainClassName; return mainClassName;
} }
@ -434,26 +434,13 @@ public class Main {
throw new Fault(Errors.CantFindClass(mainClassName)); throw new Fault(Errors.CantFindClass(mainClassName));
} }
Method mainMethod; Method mainMethod = MethodFinder.findMainMethod(appClass);
try {
mainMethod = MainMethodFinder.findMainMethod(appClass); if (mainMethod == null) {
} catch (NoSuchMethodException e) {
throw new Fault(Errors.CantFindMainMethod(mainClassName)); throw new Fault(Errors.CantFindMainMethod(mainClassName));
} }
int mods = mainMethod.getModifiers(); boolean isStatic = Modifier.isStatic(mainMethod.getModifiers());
boolean isStatic = Modifier.isStatic(mods);
boolean isPublic = Modifier.isPublic(mods);
boolean noArgs = mainMethod.getParameterCount() == 0;
if (!PreviewFeatures.isEnabled() && (!isStatic || !isPublic)) {
throw new Fault(Errors.MainNotPublicStatic);
}
if (!mainMethod.getReturnType().equals(void.class)) {
throw new Fault(Errors.MainNotVoid);
}
Object instance = null; Object instance = null;
if (!isStatic) { if (!isStatic) {
@ -478,7 +465,7 @@ public class Main {
mainMethod.setAccessible(true); mainMethod.setAccessible(true);
Object receiver = isStatic ? appClass : instance; Object receiver = isStatic ? appClass : instance;
if (noArgs) { if (mainMethod.getParameterCount() == 0) {
mainMethod.invoke(receiver); mainMethod.invoke(receiver);
} else { } else {
mainMethod.invoke(receiver, (Object)mainArgs); mainMethod.invoke(receiver, (Object)mainArgs);

View file

@ -206,9 +206,9 @@ public class JavacParser implements Parser {
this.source = parser.source; this.source = parser.source;
this.preview = parser.preview; this.preview = parser.preview;
this.allowStringFolding = parser.allowStringFolding; this.allowStringFolding = parser.allowStringFolding;
this.keepDocComments = false; this.keepDocComments = parser.keepDocComments;
this.parseModuleInfo = false; this.parseModuleInfo = false;
this.docComments = null; this.docComments = parser.docComments;
this.errorTree = F.Erroneous(); this.errorTree = F.Erroneous();
this.endPosTable = newEndPosTable(false); this.endPosTable = newEndPosTable(false);
this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
@ -3916,8 +3916,8 @@ public class JavacParser implements Parser {
defs.append(pd); defs.append(pd);
} }
boolean firstTypeDecl = true; // have we see a class, enum, or interface declaration yet? boolean firstTypeDecl = true; // have we seen a class, enum, or interface declaration yet?
boolean isUnnamedClass = false; boolean isImplicitClass = false;
OUTER: while (token.kind != EOF) { OUTER: while (token.kind != EOF) {
if (token.pos <= endPosTable.errorEndPos) { if (token.pos <= endPosTable.errorEndPos) {
// error recovery // error recovery
@ -3984,13 +3984,13 @@ public class JavacParser implements Parser {
// this code speculatively tests to see if a top level method // this code speculatively tests to see if a top level method
// or field can parse. If the method or field can parse then // or field can parse. If the method or field can parse then
// it is parsed. Otherwise, parsing continues as though // it is parsed. Otherwise, parsing continues as though
// unnamed classes did not exist and error reporting // implicit classes did not exist and error reporting
// is the same as in the past. // is the same as in the past.
if (Feature.UNNAMED_CLASSES.allowedInSource(source) && !isDeclaration()) { if (Feature.IMPLICIT_CLASSES.allowedInSource(source) && !isDeclaration()) {
final JCModifiers finalMods = mods; final JCModifiers finalMods = mods;
JavacParser speculative = new VirtualParser(this); JavacParser speculative = new VirtualParser(this);
List<JCTree> speculativeResult = List<JCTree> speculativeResult =
speculative.topLevelMethodOrFieldDeclaration(finalMods); speculative.topLevelMethodOrFieldDeclaration(finalMods, null);
if (speculativeResult.head.hasTag(METHODDEF) || if (speculativeResult.head.hasTag(METHODDEF) ||
speculativeResult.head.hasTag(VARDEF)) { speculativeResult.head.hasTag(VARDEF)) {
isTopLevelMethodOrField = true; isTopLevelMethodOrField = true;
@ -3998,9 +3998,9 @@ public class JavacParser implements Parser {
} }
if (isTopLevelMethodOrField) { if (isTopLevelMethodOrField) {
checkSourceLevel(token.pos, Feature.UNNAMED_CLASSES); checkSourceLevel(token.pos, Feature.IMPLICIT_CLASSES);
defs.appendList(topLevelMethodOrFieldDeclaration(mods)); defs.appendList(topLevelMethodOrFieldDeclaration(mods, docComment));
isUnnamedClass = true; isImplicitClass = true;
} else { } else {
JCTree def = typeDeclaration(mods, docComment); JCTree def = typeDeclaration(mods, docComment);
if (def instanceof JCExpressionStatement statement) if (def instanceof JCExpressionStatement statement)
@ -4012,7 +4012,7 @@ public class JavacParser implements Parser {
firstTypeDecl = false; firstTypeDecl = false;
} }
} }
List<JCTree> topLevelDefs = isUnnamedClass ? constructUnnamedClass(defs.toList()) : defs.toList(); List<JCTree> topLevelDefs = isImplicitClass ? constructImplictClass(defs.toList()) : defs.toList();
JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs); JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs);
if (!consumedToplevelDoc) if (!consumedToplevelDoc)
attach(toplevel, firstToken.docComment()); attach(toplevel, firstToken.docComment());
@ -4027,14 +4027,14 @@ public class JavacParser implements Parser {
return toplevel; return toplevel;
} }
// Restructure top level to be an unnamed class. // Restructure top level to be an implicit class.
private List<JCTree> constructUnnamedClass(List<JCTree> origDefs) { private List<JCTree> constructImplictClass(List<JCTree> origDefs) {
ListBuffer<JCTree> topDefs = new ListBuffer<>(); ListBuffer<JCTree> topDefs = new ListBuffer<>();
ListBuffer<JCTree> defs = new ListBuffer<>(); ListBuffer<JCTree> defs = new ListBuffer<>();
for (JCTree def : origDefs) { for (JCTree def : origDefs) {
if (def.hasTag(Tag.PACKAGEDEF)) { if (def.hasTag(Tag.PACKAGEDEF)) {
log.error(def.pos(), Errors.UnnamedClassShouldNotHavePackageDeclaration); log.error(def.pos(), Errors.ImplicitClassShouldNotHavePackageDeclaration);
} else if (def.hasTag(Tag.IMPORT)) { } else if (def.hasTag(Tag.IMPORT)) {
topDefs.append(def); topDefs.append(def);
} else if (!def.hasTag(Tag.SKIP)) { } else if (!def.hasTag(Tag.SKIP)) {
@ -4053,12 +4053,12 @@ public class JavacParser implements Parser {
} }
Name name = names.fromString(simplename); Name name = names.fromString(simplename);
JCModifiers unnamedMods = F.at(Position.NOPOS) JCModifiers implicitMods = F.at(Position.NOPOS)
.Modifiers(Flags.FINAL|Flags.SYNTHETIC|Flags.UNNAMED_CLASS, List.nil()); .Modifiers(Flags.FINAL|Flags.IMPLICIT_CLASS, List.nil());
JCClassDecl unnamed = F.at(primaryPos).ClassDef( JCClassDecl implicit = F.at(primaryPos).ClassDef(
unnamedMods, name, List.nil(), null, List.nil(), List.nil(), implicitMods, name, List.nil(), null, List.nil(), List.nil(),
defs.toList()); defs.toList());
topDefs.append(unnamed); topDefs.append(implicit);
return topDefs.toList(); return topDefs.toList();
} }
@ -4735,10 +4735,9 @@ public class JavacParser implements Parser {
return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN))); return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN)));
} }
private List<JCTree> topLevelMethodOrFieldDeclaration(JCModifiers mods) throws AssertionError { private List<JCTree> topLevelMethodOrFieldDeclaration(JCModifiers mods, Comment dc) throws AssertionError {
int topPos = token.pos;
int pos = token.pos; int pos = token.pos;
Comment dc = token.docComment(); dc = dc == null ? token.docComment() : dc;
List<JCTypeParameter> typarams = typeParametersOpt(); List<JCTypeParameter> typarams = typeParametersOpt();
// if there are type parameters but no modifiers, save the start // if there are type parameters but no modifiers, save the start

View file

@ -126,7 +126,7 @@ public class PrintingProcessor extends AbstractProcessor {
kind != INSTANCE_INIT) { kind != INSTANCE_INIT) {
Element enclosing = e.getEnclosingElement(); Element enclosing = e.getEnclosingElement();
// Don't print out the constructor of an anonymous or unnamed class // Don't print out the constructor of an anonymous class
if (kind == CONSTRUCTOR && if (kind == CONSTRUCTOR &&
enclosing != null && enclosing != null &&
(NestingKind.ANONYMOUS == (NestingKind.ANONYMOUS ==
@ -136,13 +136,6 @@ public class PrintingProcessor extends AbstractProcessor {
public NestingKind visitType(TypeElement e, Void p) { public NestingKind visitType(TypeElement e, Void p) {
return e.getNestingKind(); return e.getNestingKind();
} }
}).visit(enclosing)
|| // Don't print the constructor of an unnamed class
(new SimpleElementVisitor14<Boolean, Void>(false) {
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Boolean visitType(TypeElement e, Void p) {
return e.isUnnamed();
}
}).visit(enclosing)) ) { }).visit(enclosing)) ) {
return this; return this;
} }
@ -222,14 +215,6 @@ public class PrintingProcessor extends AbstractProcessor {
printParameters(constructors.get(0)); printParameters(constructors.get(0));
} }
writer.print(")"); writer.print(")");
} else if (e.isUnnamed()) {
writer.println("// Unnamed class in file whose name starts with " + e.getSimpleName());
for(Element element : e.getEnclosedElements()) {
this.visit(element);
}
return this;
} else { } else {
if (nestingKind == TOP_LEVEL) { if (nestingKind == TOP_LEVEL) {
PackageElement pkg = elementUtils.getPackageOf(e); PackageElement pkg = elementUtils.getPackageOf(e);

View file

@ -524,11 +524,11 @@ compiler.err.duplicate.class=\
compiler.err.bad.file.name=\ compiler.err.bad.file.name=\
bad file name: {0} bad file name: {0}
compiler.err.unnamed.class.should.not.have.package.declaration=\ compiler.err.implicit.class.should.not.have.package.declaration=\
unnamed class should not have package declaration implicit class should not have package declaration
compiler.err.unnamed.class.does.not.have.main.method=\ compiler.err.implicit.class.does.not.have.main.method=\
unnamed class does not have main method in the form of void main() or void main(String[] args) implicit class does not have main method in the form of void main() or void main(String[] args)
# 0: name, 1: name # 0: name, 1: name
compiler.err.same.binary.name=\ compiler.err.same.binary.name=\
@ -3218,8 +3218,8 @@ compiler.misc.feature.string.templates=\
compiler.misc.feature.unconditional.patterns.in.instanceof=\ compiler.misc.feature.unconditional.patterns.in.instanceof=\
unconditional patterns in instanceof unconditional patterns in instanceof
compiler.misc.feature.unnamed.classes=\ compiler.misc.feature.implicit.classes=\
unnamed classes implicit classes
compiler.misc.feature.super.init=\ compiler.misc.feature.super.init=\
statements before super() statements before super()

View file

@ -1161,7 +1161,7 @@ public class TreeMaker implements JCTree.Factory {
!it.hasNext(); !it.hasNext();
} }
} }
return sym.kind == TYP && (sym.flags_field & Flags.UNNAMED_CLASS) != 0; return sym.kind == TYP && sym.isImplicit();
} }
/** The name of synthetic parameter number `i'. /** The name of synthetic parameter number `i'.

View file

@ -986,7 +986,7 @@ public class ElementsTable {
* @return true if the element is visible * @return true if the element is visible
*/ */
public boolean isSelected(Element e) { public boolean isSelected(Element e) {
if (toolEnv.isSynthetic((Symbol) e) && !toolEnv.isUnnamed((Symbol) e)) { if (toolEnv.isSynthetic((Symbol) e)) {
return false; return false;
} }
if (visibleElementVisitor == null) { if (visibleElementVisitor == null) {

View file

@ -174,10 +174,6 @@ public class ToolEnvironment {
return (sym.flags() & Flags.SYNTHETIC) != 0; return (sym.flags() & Flags.SYNTHETIC) != 0;
} }
boolean isUnnamed(Symbol sym) {
return (sym.flags() & Flags.UNNAMED_CLASS) != 0;
}
void setElementToTreePath(Element e, TreePath tree) { void setElementToTreePath(Element e, TreePath tree) {
if (e == null || tree == null) if (e == null || tree == null)
return; return;

View file

@ -1,93 +0,0 @@
/*
* Copyright (c) 2023, 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 8306112
* @enablePreview
* @build Unnamed TestUnnamedClass
* @run main TestUnnamedClass
* @summary Basic checks of java.lang.Class support for unnamed classes
*/
import java.lang.reflect.Modifier;
public class TestUnnamedClass {
public static void main(String... args) throws Exception {
Class<?> unnamedClass = Class.forName("Unnamed");
/*
* From JEP 445 JLS changes:
*
* "An unnamed class compilation unit implicitly declares a class that satisfies the following
* properties:
* It is always a top level class.
* It is always an unnamed class (it has no canonical or fully qualified name (6.7)).
* It is never abstract (8.1.1.1).
* It is always final (8.1.1.2).
* It is always a member of an unnamed package (7.4.2) and has package access.
* Its direct superclass type is always Object (8.1.4).
* It never has any direct superinterface types (8.1.5).
*/
check(unnamedClass.isUnnamedClass(), "Unnamed class was isUnnamedClass() false");
check(!(unnamedClass.isInterface() ||
unnamedClass.isEnum() ||
unnamedClass.isRecord() ||
unnamedClass.isHidden()),
"Unnamed class was not a normal class");
check(!(unnamedClass.isLocalClass() ||
unnamedClass.isMemberClass() ||
unnamedClass.isPrimitive() ||
unnamedClass.isArray()),
"Unnamed class was not top-level");
check("Unnamed".equals(unnamedClass.getName()), "Unexpected Class.getName result");
check("Unnamed".equals(unnamedClass.getTypeName()), "Unexpected Class.getTypeName result");
check("".equals(unnamedClass.getSimpleName()), "Unexpected Class.getSimpleName result");
check(unnamedClass.getCanonicalName() == null, "Unexpected non-null Class.getCanonicalName");
int modifiers = unnamedClass.getModifiers();
check((modifiers & Modifier.ABSTRACT) == 0, "Unnamed class was abstract");
check((modifiers & Modifier.FINAL) != 0, "Unnamed class was not final");
check((modifiers & (Modifier.PUBLIC |
Modifier.PRIVATE |
Modifier.PROTECTED)) == 0, "Unnamed class was not package access");
check(unnamedClass.isSynthetic(), "Unnamed class was not synthetic");
check("".equals(unnamedClass.getPackage().getName()), "Unnamed class not in an unnamed package");
check(unnamedClass.getSuperclass() == Object.class, "Superclass was not java.lang.Object");
check(unnamedClass.getInterfaces().length == 0, "Unnamed class had super interfaces");
}
private static void check(boolean predicate, String message) {
if (!predicate) {
throw new RuntimeException(message);
}
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -541,12 +541,12 @@ public class Arrrghs extends TestHelper {
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
"public static int main(String[] args){return 1;}"); "public static int main(String[] args){return 1;}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Main method must return a value of type void in class Foo"); tr.contains("Error: Main method not found in class Foo");
if (!tr.testStatus) if (!tr.testStatus)
System.out.println(tr); System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("Error: Main method must return a value of type void in class Foo"); tr.contains("Error: Main method not found in class Foo");
if (!tr.testStatus) if (!tr.testStatus)
System.out.println(tr); System.out.println(tr);
@ -567,12 +567,12 @@ public class Arrrghs extends TestHelper {
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
"public void main(String[] args){}"); "public void main(String[] args){}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Main method is not static in class Foo"); tr.contains("Error: Main method not found in class Foo");
if (!tr.testStatus) if (!tr.testStatus)
System.out.println(tr); System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("Error: Main method is not static in class Foo"); tr.contains("Error: Main method not found in class Foo");
if (!tr.testStatus) if (!tr.testStatus)
System.out.println(tr); System.out.println(tr);

View file

@ -42,13 +42,13 @@ public class InstanceMainTest extends TestHelper {
} }
""", """,
// static dominating instance // instance dominating static
""" """
class MainClass { class MainClass {
void main(String[] args) { void main(String[] args) {
throw new AssertionError();
} }
static void main() { static void main() {
throw new AssertionError();
} }
} }
""", """,
@ -81,12 +81,12 @@ public class InstanceMainTest extends TestHelper {
} }
""", """,
// unnamed class static dominating instance // main with args dominating main without args
""" """
void main(String[] args) { void main(String[] args) {
throw new AssertionError();
} }
static void main() { static void main() {
throw new AssertionError();
} }
""", """,
@ -105,15 +105,15 @@ public class InstanceMainTest extends TestHelper {
} }
""", """,
// instance main dominating super static // instance main with args dominating super
""" """
class MainClass extends SuperClass { class MainClass extends SuperClass {
void main() { void main() {
throw new AssertionError();
} }
} }
class SuperClass { class SuperClass {
void main(String[] args) { void main(String[] args) {
throw new AssertionError();
} }
} }
""", """,

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -92,9 +92,10 @@ public class LauncherErrors {
ProcessTools.executeTestJava("-Djava.security.manager", "--module-path", dir, "--module", mid) ProcessTools.executeTestJava("-Djava.security.manager", "--module-path", dir, "--module", mid)
.outputTo(System.out) .outputTo(System.out)
.errorTo(System.out) .errorTo(System.out)
.shouldContain("Error: Unable to initialize main class " + MAIN_CLASS + " in module " + TEST_MODULE) .shouldContain("Error: Unable to load main class " + MAIN_CLASS + " in module " + TEST_MODULE)
.shouldContain("Caused by: java.security.AccessControlException: access denied") .shouldContain("Caused by: java.security.AccessControlException")
.shouldNotHaveExitValue(0); .shouldNotHaveExitValue(0);
} }
} }

View file

@ -24,11 +24,11 @@
/* /*
* @test * @test
* @bug 8309595 * @bug 8309595
* @summary Allow javadoc to process unnamed classes * @summary Allow javadoc to process implicit classes
* @library /tools/lib ../../lib * @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool * @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.* * @build toolbox.ToolBox javadoc.tester.*
* @run main TestUnnamed * @run main TestImplicit
*/ */
import java.io.File; import java.io.File;
@ -39,18 +39,18 @@ import java.nio.file.Path;
import javadoc.tester.JavadocTester; import javadoc.tester.JavadocTester;
import toolbox.ToolBox; import toolbox.ToolBox;
public class TestUnnamed extends JavadocTester { public class TestImplicit extends JavadocTester {
private static final String thisVersion = System.getProperty("java.specification.version"); private static final String thisVersion = System.getProperty("java.specification.version");
private static final ToolBox tb = new ToolBox(); private static final ToolBox tb = new ToolBox();
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
new TestUnnamed().runTests(); new TestImplicit().runTests();
} }
@Test @Test
public void testUnnamed(Path base) throws IOException { public void testImplicit(Path base) throws IOException {
String className = "Sample"; String className = "Sample";
Files.createDirectories(base); Files.createDirectories(base);
Path out = base.resolve("out"); Path out = base.resolve("out");

View file

@ -23,8 +23,8 @@
/** /**
* Exemplar unnamed class. * Exemplar implicit class.
*/ */
public void main() { void main() {
System.out.println("Hello, world."); System.out.println("Hello, world.");
} }

View file

@ -1,6 +1,6 @@
/** /**
* @test /nodynamiccopyright/ * @test /nodynamiccopyright/
* @compile/fail/ref=UnnamedClassRecovery.out -XDrawDiagnostics --enable-preview --source ${jdk.version} UnnamedClassRecovery.java * @compile/fail/ref=ImplicitClassRecovery.out -XDrawDiagnostics --enable-preview --source ${jdk.version} ImplicitClassRecovery.java
*/ */
public void main() { public void main() {
//the following is intentionally missing a semicolon: //the following is intentionally missing a semicolon:

View file

@ -0,0 +1,4 @@
ImplicitClassRecovery.java:7:33: compiler.err.expected: ';'
- compiler.note.preview.filename: ImplicitClassRecovery.java, DEFAULT
- compiler.note.preview.recompile
1 error

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2023, 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 8315458
* @enablePreview
* @summary Make sure nesting classes don't create symbol conflicts with implicit name.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build toolbox.ToolBox toolbox.JavacTask
* @run main NestedClasses
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import toolbox.ToolBox;
import toolbox.JavaTask;
import toolbox.JavacTask;
import toolbox.Task;
public class NestedClasses {
private static ToolBox TOOLBOX = new ToolBox();
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
public static void main(String... arg) throws IOException {
compPass("A.java", """
void main() {}
class A {} // okay
""");
compPass("A.java", """
void main() {}
class B {
class A { } // okay
}
""");
compFail("A.java", """
void main() {}
class B {
class B { } //error
}
""");
}
/*
* Test source for successful compile.
*/
static void compPass(String fileName, String code) throws IOException {
Path path = Path.of(fileName);
Files.writeString(path, code);
String output = new JavacTask(TOOLBOX)
.files(List.of(path))
.classpath(".")
.options("-encoding", "utf8", "--enable-preview", "-source", JAVA_VERSION)
.run()
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
if (output.contains("compiler.err")) {
throw new RuntimeException("Error detected");
}
}
/*
* Test source for unsuccessful compile and specific error.
*/
static void compFail(String fileName, String code) throws IOException {
Path path = Path.of(fileName);
Files.writeString(path, code);
String output = new JavacTask(TOOLBOX)
.files(List.of(path))
.classpath(".")
.options("-XDrawDiagnostics", "-encoding", "utf8", "--enable-preview", "-source", JAVA_VERSION)
.run(Task.Expect.FAIL)
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
if (!output.contains("compiler.err")) {
throw new RuntimeException("No error detected");
}
}
}

View file

@ -1,7 +1,7 @@
/** /**
* @test /nodynamiccopyright/ * @test /nodynamiccopyright/
* @bug 8310314 * @bug 8310314
* @summary Ensure proper error position for the "unnamed classes not supported" error * @summary Ensure proper error position for the "implicit classes not supported" error
* @compile/fail/ref=SourceLevelErrorPosition.out -XDrawDiagnostics SourceLevelErrorPosition.java * @compile/fail/ref=SourceLevelErrorPosition.out -XDrawDiagnostics SourceLevelErrorPosition.java
*/ */
class Nested {} class Nested {}

View file

@ -1,2 +1,2 @@
SourceLevelErrorPosition.java:8:1: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.unnamed.classes) SourceLevelErrorPosition.java:8:1: compiler.err.preview.feature.disabled.plural: (compiler.misc.feature.implicit.classes)
1 error 1 error

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2023, 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 8315458
* @enablePreview
* @build Implicit TestImplicitClass
* @run main TestImplicitClass
* @summary Basic checks of java.lang.Class support for implicit classes
*/
import java.lang.reflect.Modifier;
public class TestImplicitClass {
public static void main(String... args) throws Exception {
Class<?> implicitClass = Class.forName("Implicit");
/*
* From JEP 463 JLS changes:
*
* "An implicit class compilation unit implicitly declares a class that satisfies the following
* properties:
* It is always a top level class.
* It is never abstract (8.1.1.1).
* It is never synthetic (13.1.11)
* It is always final (8.1.1.2).
* It is always a member of unnamed package (7.4.2) and has package access.
* Its direct superclass type is always Object (8.1.4).
* It never has any direct superinterface types (8.1.5).
*/
check(!(implicitClass.isInterface() ||
implicitClass.isEnum() ||
implicitClass.isRecord() ||
implicitClass.isHidden()),
"Implicit class was not a normal class");
check(!(implicitClass.isLocalClass() ||
implicitClass.isMemberClass() ||
implicitClass.isPrimitive() ||
implicitClass.isArray()),
"Implicit class was not top-level");
check("Implicit".equals(implicitClass.getName()), "Unexpected Class.getName result");
check("Implicit".equals(implicitClass.getTypeName()), "Unexpected Class.getTypeName result");
check("Implicit".equals(implicitClass.getSimpleName()), "Unexpected Class.getSimpleName result");
check("Implicit".equals(implicitClass.getCanonicalName()), "Unexpected Class.getCanonicalName result");
int modifiers = implicitClass.getModifiers();
check((modifiers & Modifier.ABSTRACT) == 0, "Implicit class was abstract");
check((modifiers & Modifier.FINAL) != 0, "Implicit class was not final");
check((modifiers & (Modifier.PUBLIC |
Modifier.PRIVATE |
Modifier.PROTECTED)) == 0, "Implicit class was not package access");
check(!implicitClass.isSynthetic(), "Implicit class was synthetic");
check("".equals(implicitClass.getPackage().getName()), "Implicit class not in an implicit package");
check(implicitClass.getSuperclass() == Object.class, "Superclass was not java.lang.Object");
check(implicitClass.getInterfaces().length == 0, "Implicit class had super interfaces");
}
private static void check(boolean predicate, String message) {
if (!predicate) {
throw new RuntimeException(message);
}
}
}

View file

@ -21,7 +21,7 @@
* questions. * questions.
*/ */
// key: compiler.misc.feature.unnamed.classes // key: compiler.misc.feature.implicit.classes
// key: compiler.warn.preview.feature.use.plural // key: compiler.warn.preview.feature.use.plural
// options: -source ${jdk.version} --enable-preview -Xlint:preview // options: -source ${jdk.version} --enable-preview -Xlint:preview

View file

@ -21,12 +21,12 @@
* questions. * questions.
*/ */
// key: compiler.err.unnamed.class.should.not.have.package.declaration // key: compiler.err.implicit.class.should.not.have.package.declaration
// key: compiler.note.preview.filename // key: compiler.note.preview.filename
// key: compiler.note.preview.recompile // key: compiler.note.preview.recompile
// options: -source ${jdk.version} --enable-preview // options: -source ${jdk.version} --enable-preview
package unnamed.classes; package implicit.classes;
public static void main(String... args) { public static void main(String... args) {
} }

View file

@ -21,7 +21,7 @@
* questions. * questions.
*/ */
// key: compiler.err.unnamed.class.does.not.have.main.method // key: compiler.err.implicit.class.does.not.have.main.method
// key: compiler.note.preview.filename // key: compiler.note.preview.filename
// key: compiler.note.preview.recompile // key: compiler.note.preview.recompile
// options: -source ${jdk.version} --enable-preview // options: -source ${jdk.version} --enable-preview

View file

@ -564,7 +564,7 @@ public class SourceLauncherTest extends TestRunner {
tb.writeJavaFiles(base, tb.writeJavaFiles(base,
"class NotStatic { public void main(String... args) { } }"); "class NotStatic { public void main(String... args) { } }");
testError(base.resolve("NotStatic.java"), "", testError(base.resolve("NotStatic.java"), "",
"error: 'main' method is not declared 'public static'"); "error: can't find main(String[]) method in class: NotStatic");
} }
@Test @Test
@ -572,7 +572,7 @@ public class SourceLauncherTest extends TestRunner {
tb.writeJavaFiles(base, tb.writeJavaFiles(base,
"class NotVoid { public static int main(String... args) { return 0; } }"); "class NotVoid { public static int main(String... args) { return 0; } }");
testError(base.resolve("NotVoid.java"), "", testError(base.resolve("NotVoid.java"), "",
"error: 'main' method is not declared with a return type of 'void'"); "error: can't find main(String[]) method in class: NotVoid");
} }
@Test @Test

View file

@ -2472,7 +2472,7 @@ public class JavacParserTest extends TestCase {
result, result,
""" """
\n\ \n\
/*synthetic*/ final class Test { final class Test {
\n\ \n\
void main() { void main() {
(ERROR); (ERROR);

View file

@ -24,19 +24,19 @@
/* /*
* @test * @test
* @bug 8306112 8309568 * @bug 8306112 8309568
* @summary Test basic processing of unnamed classes. * @summary Test basic processing of implicitly declared classes.
* @library /tools/javac/lib * @library /tools/javac/lib
* @modules java.compiler * @modules java.compiler
* jdk.compiler * jdk.compiler
* @build JavacTestingAbstractProcessor TestUnnamedClass * @build JavacTestingAbstractProcessor TestImplicitClass
* @compile -processor TestUnnamedClass --enable-preview --release ${jdk.version} Anonymous.java * @compile -processor TestImplicitClass --enable-preview --release ${jdk.version} Anonymous.java
* @clean Nameless.java * @clean Nameless.java
* @compile/process -processor TestUnnamedClass -proc:only --enable-preview --release ${jdk.version} -Xprefer:newer -AclassOnly Anonymous Nameless * @compile/process -processor TestImplicitClass -proc:only --enable-preview --release ${jdk.version} -Xprefer:newer -AclassOnly Anonymous Nameless
*/ */
// The first @compile line processes Anonymous.java and a // The first @compile line processes Anonymous.java and a
// Nameless.java class generated using the Filer. Both of those // Nameless.java class generated using the Filer. Both of those implicitly
// unnamed classes are then compiled down to class files. The second // declared classes are then compiled down to class files. The second
// @compile line, as directed by -Xprefer:newer, builds and checks the // @compile line, as directed by -Xprefer:newer, builds and checks the
// language model objects constructed from those class files, ignoring // language model objects constructed from those class files, ignoring
// any source files for those types. // any source files for those types.
@ -52,12 +52,12 @@ import static javax.lang.model.util.ElementFilter.*;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
/** /**
* Test annotation processing representation of unnamed classes * Test annotation processing representation of implicitly classes
* constructed from either a source file or a class file. * constructed from either a source file or a class file.
*/ */
@SuppressWarnings("preview") @SuppressWarnings("preview")
@SupportedOptions("classOnly") @SupportedOptions("classOnly")
public class TestUnnamedClass extends JavacTestingAbstractProcessor { public class TestImplicitClass extends JavacTestingAbstractProcessor {
private static int round = 0; private static int round = 0;
private static int checkedClassesCount = 0; private static int checkedClassesCount = 0;
@ -72,14 +72,14 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
// Don't generate any files if testing pre-existing class files // Don't generate any files if testing pre-existing class files
if (!classOnly) { if (!classOnly) {
generateUnnamed(); generateImplicitClass();
} }
} else { } else {
if (!roundEnv.processingOver()) { // Test generated file(s) if (!roundEnv.processingOver()) { // Test generated file(s)
checkRoots(roundEnv); checkRoots(roundEnv);
} else { // Should have checked at least one class before processing is over } else { // Should have checked at least one class before processing is over
if (checkedClassesCount == 0) { if (checkedClassesCount == 0) {
messager.printError("No unnamed classes checked."); messager.printError("No implicitly declared classes checked.");
} }
} }
} }
@ -95,11 +95,11 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
checkUnnamedClassProperties(type); checkUnnamedClassProperties(type);
} }
if (checks == 0) { if (checks == 0) {
messager.printError("No checking done of any candidate unnamed classes."); messager.printError("No checking done of any candidate implicitly declared classes.");
} }
} }
private void generateUnnamed() { private void generateImplicitClass() {
try { try {
String unnamedSource = """ String unnamedSource = """
void main() { void main() {
@ -117,12 +117,11 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
} }
/* /*
* From JEP 445 JLS changes: * From JEP 463 JLS changes:
* *
* "An unnamed class compilation unit implicitly declares a class that satisfies the following * "An implicitly declared class compilation unit implicitly declares a class that
* properties: * satisfies the following properties:
* It is always a top level class. * It is always a top level class.
* It is always an unnamed class (it has no canonical or fully qualified name (6.7)).
* It is never abstract (8.1.1.1). * It is never abstract (8.1.1.1).
* It is always final (8.1.1.2). * It is always final (8.1.1.2).
* It is always a member of an unnamed package (7.4.2) and has package access. * It is always a member of an unnamed package (7.4.2) and has package access.
@ -130,8 +129,8 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
* It never has any direct superinterface types (8.1.5). * It never has any direct superinterface types (8.1.5).
* *
* The body of the class contains every ClassMemberDeclaration * The body of the class contains every ClassMemberDeclaration
* from the unnamed class compilation unit. It is not possible for * from the implicitly declared class compilation unit. It is not possible for
* an unnamed class compilation unit to declare an instance * an implicitly declared class compilation unit to declare an instance
* initializer, static initializer, or constructor. * initializer, static initializer, or constructor.
* *
* It has an implicitly declared default constructor (8.8.9). * It has an implicitly declared default constructor (8.8.9).
@ -141,63 +140,55 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
* *
* It is a compile-time error if this class does not declare a candidate main method (12.1.4). * It is a compile-time error if this class does not declare a candidate main method (12.1.4).
*/ */
void checkUnnamedClassProperties(TypeElement unnamedClass) { void checkUnnamedClassProperties(TypeElement implicitClass) {
checkedClassesCount++; checkedClassesCount++;
Name expectedName = unnamedClass.getSimpleName(); Name expectedName = implicitClass.getSimpleName();
System.out.println("Checking " + expectedName); System.out.println("Checking " + expectedName);
if (unnamedClass.getNestingKind() != NestingKind.TOP_LEVEL) { if (implicitClass.getNestingKind() != NestingKind.TOP_LEVEL) {
messager.printError("Unnamed class is not top-level.", unnamedClass); messager.printError("Implicitly declared class is not top-level.", implicitClass);
} }
if (!unnamedClass.isUnnamed()) { if (!implicitClass.getQualifiedName().equals(expectedName)) {
messager.printError("Unnamed class is _not_ indicated as such.", unnamedClass); messager.printError("Implicitly declared class qualified name does not match simple name.", implicitClass);
} }
if (unnamedClass.getSimpleName().isEmpty()) { Name binaryName = elements.getBinaryName(implicitClass);
messager.printError("Unnamed class has an empty simple name.", unnamedClass);
}
if (!unnamedClass.getQualifiedName().isEmpty()) {
messager.printError("Unnamed class does _not_ have an empty qualified name.", unnamedClass);
}
Name binaryName = elements.getBinaryName(unnamedClass);
if (!expectedName.equals(binaryName)) { if (!expectedName.equals(binaryName)) {
messager.printError("Unnamed has unexpected binary name" + binaryName + ".", unnamedClass); messager.printError("Implicitly declared class has unexpected binary name" + binaryName + ".", implicitClass);
} }
if (unnamedClass.getModifiers().contains(Modifier.ABSTRACT)) { if (implicitClass.getModifiers().contains(Modifier.ABSTRACT)) {
messager.printError("Unnamed class is abstract.", unnamedClass); messager.printError("Implicitly declared class is abstract.", implicitClass);
} }
if (!unnamedClass.getModifiers().contains(Modifier.FINAL)) { if (!implicitClass.getModifiers().contains(Modifier.FINAL)) {
messager.printError("Unnamed class is _not_ final.", unnamedClass); messager.printError("Implicitly declared class is _not_ final.", implicitClass);
} }
if (!elements.getPackageOf(unnamedClass).isUnnamed()) { if (!elements.getPackageOf(implicitClass).isUnnamed()) {
messager.printError("Unnamed class is _not_ in an unnamed package.", unnamedClass); messager.printError("Implicitly declared class is _not_ in an unnamed package.", implicitClass);
} }
if (unnamedClass.getModifiers().contains(Modifier.PUBLIC) || if (implicitClass.getModifiers().contains(Modifier.PUBLIC) ||
unnamedClass.getModifiers().contains(Modifier.PRIVATE) || implicitClass.getModifiers().contains(Modifier.PRIVATE) ||
unnamedClass.getModifiers().contains(Modifier.PROTECTED)) { implicitClass.getModifiers().contains(Modifier.PROTECTED)) {
messager.printError("Unnamed class does _not_ have package access.", unnamedClass); messager.printError("Implicitly declared class does _not_ have package access.", implicitClass);
} }
if ( !types.isSameType(unnamedClass.getSuperclass(), if ( !types.isSameType(implicitClass.getSuperclass(),
elements.getTypeElement("java.lang.Object").asType())) { elements.getTypeElement("java.lang.Object").asType())) {
messager.printError("Unnamed class does _not_ have java.lang.Object as a superclass.", unnamedClass); messager.printError("Implicitly declared class does _not_ have java.lang.Object as a superclass.", implicitClass);
} }
if (!unnamedClass.getInterfaces().isEmpty()) { if (!implicitClass.getInterfaces().isEmpty()) {
messager.printError("Unnamed class has superinterfaces.", unnamedClass); messager.printError("Implicitly declared class has superinterfaces.", implicitClass);
} }
List<ExecutableElement> ctors = constructorsIn(unnamedClass.getEnclosedElements()); List<ExecutableElement> ctors = constructorsIn(implicitClass.getEnclosedElements());
if (ctors.size() != 1 ) { if (ctors.size() != 1 ) {
messager.printError("Did not find exactly one constructor", unnamedClass); messager.printError("Did not find exactly one constructor", implicitClass);
} }
if (!classOnly) { if (!classOnly) {
@ -208,7 +199,7 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
} }
} }
List<ExecutableElement> methods = methodsIn(unnamedClass.getEnclosedElements()); List<ExecutableElement> methods = methodsIn(implicitClass.getEnclosedElements());
// Just look for a method named "main"; don't check the other details. // Just look for a method named "main"; don't check the other details.
boolean mainFound = false; boolean mainFound = false;
Name mainName = elements.getName("main"); Name mainName = elements.getName("main");
@ -220,7 +211,7 @@ public class TestUnnamedClass extends JavacTestingAbstractProcessor {
} }
if (!mainFound) { if (!mainFound) {
messager.printError("No main mehtod found", unnamedClass); messager.printError("No main method found", implicitClass);
} }
} }
} }

View file

@ -1,4 +0,0 @@
UnnamedClassRecovery.java:7:33: compiler.err.expected: ';'
- compiler.note.preview.filename: UnnamedClassRecovery.java, DEFAULT
- compiler.note.preview.recompile
1 error