8306112: Implementation of JEP 445: Unnamed Classes and Instance Main Methods (Preview)
8308613: javax.lang.model updates for JEP 445 (preview) 8308913: Update core reflection for JEP 445 (preview) Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org> Co-authored-by: Joe Darcy <darcy@openjdk.org> Co-authored-by: Jan Lahoda <jlahoda@openjdk.org> Co-authored-by: Jim Laskey <jlaskey@openjdk.org> Co-authored-by: Adam Sotona <asotona@openjdk.org> Reviewed-by: mcimadamore, vromero, darcy
This commit is contained in:
parent
e970ddbc60
commit
98b53c06cf
@ -98,6 +98,7 @@ define SetupInterimModule
|
||||
EXCLUDES := sun javax/tools/snippet-files, \
|
||||
EXCLUDE_FILES := $(TOPDIR)/src/$1/share/classes/module-info.java \
|
||||
$(TOPDIR)/src/$1/share/classes/javax/tools/ToolProvider.java \
|
||||
$(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/Main.java \
|
||||
Standard.java, \
|
||||
EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java \
|
||||
$($1.interim_EXTRA_FILES), \
|
||||
|
@ -70,8 +70,10 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.loader.BootLoader;
|
||||
import jdk.internal.loader.BuiltinClassLoader;
|
||||
import jdk.internal.misc.PreviewFeatures;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.module.Resources;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
@ -81,6 +83,7 @@ import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.reflect.ReflectionFactory;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||
|
||||
import sun.invoke.util.Wrapper;
|
||||
import sun.reflect.generics.factory.CoreReflectionFactory;
|
||||
import sun.reflect.generics.factory.GenericsFactory;
|
||||
@ -157,7 +160,8 @@ import sun.reflect.misc.ReflectUtil;
|
||||
* other members are the classes and interfaces whose declarations are
|
||||
* enclosed within the top-level class declaration.
|
||||
*
|
||||
* <p> A class or interface created by the invocation of
|
||||
* <h2><a id=hiddenClasses>Hidden Classes</a></h2>
|
||||
* A class or interface created by the invocation of
|
||||
* {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...)
|
||||
* Lookup::defineHiddenClass} is a {@linkplain Class#isHidden() <em>hidden</em>}
|
||||
* class or interface.
|
||||
@ -185,6 +189,31 @@ import sun.reflect.misc.ReflectUtil;
|
||||
* a class or interface is hidden has no bearing on the characteristics
|
||||
* exposed by the methods of class {@code Class}.
|
||||
*
|
||||
* <h2><a id=unnamedClasses>Unnamed 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
|
||||
* unnamed class, say {@code HelloWorld.java}, creates a
|
||||
* similarly-named {@code class} file, {@code HelloWorld.class}, where
|
||||
* the class stored in that {@code class} file is named {@code
|
||||
* "HelloWorld"}, matching the base names of the source and {@code
|
||||
* class} files.
|
||||
*
|
||||
* For the {@code Class} object of an unnamed class {@code
|
||||
* HelloWorld}, the methods to get the {@linkplain #getName name} and
|
||||
* {@linkplain #getTypeName type name} return results
|
||||
* equal to {@code "HelloWorld"}. The {@linkplain #getSimpleName
|
||||
* simple name} of such an unnamed class is the empty string and the
|
||||
* {@linkplain #getCanonicalName canonical name} is {@code null}.
|
||||
*
|
||||
* @param <T> the type of the class modeled by this {@code Class}
|
||||
* object. For example, the type of {@code String.class} is {@code
|
||||
* Class<String>}. Use {@code Class<?>} if the class being modeled is
|
||||
@ -1717,7 +1746,7 @@ public final class Class<T> implements java.io.Serializable,
|
||||
/**
|
||||
* Returns the simple name of the underlying class as given in the
|
||||
* source code. An empty string is returned if the underlying class is
|
||||
* {@linkplain #isAnonymousClass() anonymous}.
|
||||
* {@linkplain #isAnonymousClass() anonymous} or {@linkplain #isUnnamedClass() unnamed}.
|
||||
* A {@linkplain #isSynthetic() synthetic class}, one not present
|
||||
* in source code, can have a non-empty name including special
|
||||
* characters, such as "{@code $}".
|
||||
@ -1730,6 +1759,9 @@ public final class Class<T> implements java.io.Serializable,
|
||||
* @since 1.5
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
if (isUnnamedClass()) {
|
||||
return "";
|
||||
}
|
||||
ReflectionData<T> rd = reflectionData();
|
||||
String simpleName = rd.simpleName;
|
||||
if (simpleName == null) {
|
||||
@ -1779,6 +1811,7 @@ public final class Class<T> implements java.io.Serializable,
|
||||
* <ul>
|
||||
* <li>a {@linkplain #isLocalClass() local class}
|
||||
* <li>a {@linkplain #isAnonymousClass() anonymous class}
|
||||
* <li>an {@linkplain #isUnnamedClass() unnamed class}
|
||||
* <li>a {@linkplain #isHidden() hidden class}
|
||||
* <li>an array whose component type does not have a canonical name</li>
|
||||
* </ul>
|
||||
@ -1798,6 +1831,9 @@ public final class Class<T> implements java.io.Serializable,
|
||||
* @since 1.5
|
||||
*/
|
||||
public String getCanonicalName() {
|
||||
if (isUnnamedClass()) {
|
||||
return null;
|
||||
}
|
||||
ReflectionData<T> rd = reflectionData();
|
||||
String canonicalName = rd.canonicalName;
|
||||
if (canonicalName == null) {
|
||||
@ -1832,12 +1868,33 @@ 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
|
||||
* is an anonymous class.
|
||||
*
|
||||
* @apiNote
|
||||
* 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.
|
||||
* @since 1.5
|
||||
|
@ -72,6 +72,8 @@ public @interface PreviewFeature {
|
||||
STRING_TEMPLATES,
|
||||
@JEP(number=443, title="Unnamed Patterns and Variables")
|
||||
UNNAMED,
|
||||
@JEP(number=445, title="Unnamed Classes and Instance Main Methods")
|
||||
UNNAMED_CLASSES,
|
||||
/**
|
||||
* A key for testing.
|
||||
*/
|
||||
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.lang.module.ResolvedModule;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.math.BigDecimal;
|
||||
@ -78,6 +79,8 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.internal.misc.MainMethodFinder;
|
||||
import jdk.internal.misc.PreviewFeatures;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.module.ModuleBootstrap;
|
||||
import jdk.internal.module.Modules;
|
||||
@ -843,11 +846,32 @@ public final class LauncherHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* main type flags
|
||||
*/
|
||||
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;
|
||||
try {
|
||||
mainMethod = mainClass.getMethod("main", String[].class);
|
||||
mainMethod = MainMethodFinder.findMainMethod(mainClass);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// invalid main or not FX application, abort with an error
|
||||
abort(null, "java.launcher.cls.error4", mainClass.getName(),
|
||||
@ -863,16 +887,42 @@ public final class LauncherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
setMainType(mainMethod);
|
||||
|
||||
/*
|
||||
* getMethod (above) will choose the correct method, based
|
||||
* 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 and returns a void.
|
||||
* ensure that the method is static (non-preview) and returns a void.
|
||||
*/
|
||||
int mod = mainMethod.getModifiers();
|
||||
if (!Modifier.isStatic(mod)) {
|
||||
abort(null, "java.launcher.cls.error2", "static",
|
||||
mainMethod.getDeclaringClass().getName());
|
||||
int mods = mainMethod.getModifiers();
|
||||
boolean isStatic = Modifier.isStatic(mods);
|
||||
boolean isPublic = Modifier.isPublic(mods);
|
||||
boolean noArgs = mainMethod.getParameterCount() == 0;
|
||||
|
||||
if (!PreviewFeatures.isEnabled()) {
|
||||
if (!isStatic || !isPublic || noArgs) {
|
||||
abort(null, "java.launcher.cls.error2", "static",
|
||||
mainMethod.getDeclaringClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStatic) {
|
||||
if (mainClass.isMemberClass() && !Modifier.isStatic(mainClass.getModifiers())) {
|
||||
abort(null, "java.launcher.cls.error9",
|
||||
mainMethod.getDeclaringClass().getName());
|
||||
}
|
||||
try {
|
||||
Constructor<?> constructor = mainClass.getDeclaredConstructor();
|
||||
if (Modifier.isPrivate(constructor.getModifiers())) {
|
||||
abort(null, "java.launcher.cls.error8",
|
||||
mainMethod.getDeclaringClass().getName());
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
abort(null, "java.launcher.cls.error8",
|
||||
mainMethod.getDeclaringClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
|
||||
abort(null, "java.launcher.cls.error3",
|
||||
mainMethod.getDeclaringClass().getName());
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2007, 2021, 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.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -236,6 +236,13 @@ java.launcher.cls.error6=\
|
||||
java.launcher.cls.error7=\
|
||||
Error: Unable to initialize main class {0}\n\
|
||||
Caused by: {1}: {2}
|
||||
java.launcher.cls.error8=\
|
||||
Error: no non-private zero argument constructor found in class {0}\n\
|
||||
remove private from existing constructor or define as:\n\
|
||||
\ public {0}()
|
||||
java.launcher.cls.error9=\
|
||||
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
|
||||
java.launcher.jar.error1=\
|
||||
Error: An unexpected error occurred while trying to open file {0}
|
||||
java.launcher.jar.error2=manifest not found in {0}
|
||||
|
@ -401,8 +401,10 @@ JavaMain(void* _args)
|
||||
JNIEnv *env = 0;
|
||||
jclass mainClass = NULL;
|
||||
jclass appClass = NULL; // actual application class being launched
|
||||
jmethodID mainID;
|
||||
jobjectArray mainArgs;
|
||||
jmethodID mainID;
|
||||
jmethodID constructor;
|
||||
jobject mainObject;
|
||||
int ret = 0;
|
||||
jlong start = 0, end = 0;
|
||||
|
||||
@ -539,12 +541,55 @@ JavaMain(void* _args)
|
||||
* is not required. The main method is invoked here so that extraneous java
|
||||
* stacks are not in the application stack trace.
|
||||
*/
|
||||
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
|
||||
"([Ljava/lang/String;)V");
|
||||
CHECK_EXCEPTION_NULL_LEAVE(mainID);
|
||||
#define MAIN_WITHOUT_ARGS 1
|
||||
#define MAIN_NONSTATIC 2
|
||||
|
||||
/* Invoke main method. */
|
||||
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
|
||||
jclass helperClass = GetLauncherHelperClass(env);
|
||||
jmethodID getMainType = (*env)->GetStaticMethodID(env, helperClass,
|
||||
"getMainType",
|
||||
"()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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The launcher's exit code (in the absence of calls to
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 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
|
||||
@ -28,6 +28,7 @@ package javax.annotation.processing;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.*;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import java.io.IOException;
|
||||
|
||||
@ -176,6 +177,13 @@ public interface Filer {
|
||||
* <p>Creating a source file in or for an <em>unnamed</em> package in a <em>named</em>
|
||||
* module is <em>not</em> supported.
|
||||
*
|
||||
* <p>If the environment is configured to support {@linkplain
|
||||
* TypeElement#isUnnamed unnamed classes}, the name argument is
|
||||
* used to provide the leading component of the name used for the
|
||||
* output file. For example {@code filer.createSourceFile("Foo")}
|
||||
* to create an unnamed class hosted in {@code Foo.java}. All
|
||||
* unnamed classes must be in an unnamed package.
|
||||
*
|
||||
* @apiNote To use a particular {@linkplain
|
||||
* java.nio.charset.Charset charset} to encode the contents of the
|
||||
* file, an {@code OutputStreamWriter} with the chosen charset can
|
||||
@ -255,6 +263,13 @@ public interface Filer {
|
||||
* <p>Creating a class file in or for an <em>unnamed</em> package in a <em>named</em>
|
||||
* module is <em>not</em> supported.
|
||||
*
|
||||
* <p>If the environment is configured to support {@linkplain
|
||||
* TypeElement#isUnnamed unnamed classes}, the name argument is
|
||||
* used to provide the leading component of the name used for the
|
||||
* output file. For example {@code filer.createClassFile("Foo")} to
|
||||
* create an unnamed class hosted in {@code Foo.class}. All unnamed
|
||||
* classes must be in an unnamed package.
|
||||
*
|
||||
* @apiNote To avoid subsequent errors, the contents of the class
|
||||
* file should be compatible with the {@linkplain
|
||||
* ProcessingEnvironment#getSourceVersion source version} being
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package javax.lang.model.element;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import java.util.List;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.*;
|
||||
@ -147,7 +149,7 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
|
||||
/**
|
||||
* Returns the fully qualified name of this class or interface
|
||||
* element. More precisely, it returns the <i>canonical</i> name.
|
||||
* For local and anonymous classes, which do not have canonical
|
||||
* For local, anonymous, and {@linkplain #isUnnamed() unnamed} classes, which do not have canonical
|
||||
* names, an {@linkplain Name##empty_name empty name} is
|
||||
* returned.
|
||||
*
|
||||
@ -163,6 +165,7 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
|
||||
*
|
||||
* @see Elements#getBinaryName
|
||||
* @jls 6.7 Fully Qualified Names and Canonical Names
|
||||
* @jls 7.3 Compilation Units
|
||||
*/
|
||||
Name getQualifiedName();
|
||||
|
||||
@ -172,6 +175,10 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
|
||||
* For an anonymous class, an {@linkplain Name##empty_name empty
|
||||
* 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,
|
||||
* an empty name for an anonymous class
|
||||
*
|
||||
@ -179,6 +186,22 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
|
||||
@Override
|
||||
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.
|
||||
* If this class or interface element represents an interface or the class
|
||||
|
@ -444,6 +444,9 @@ public class ClassFinder {
|
||||
if (c.members_field == null) {
|
||||
try {
|
||||
c.complete();
|
||||
if ((c.flags_field & UNNAMED_CLASS) != 0) {
|
||||
syms.removeClass(ps.modle, flatname);
|
||||
}
|
||||
} catch (CompletionFailure ex) {
|
||||
if (absent) {
|
||||
syms.removeClass(ps.modle, flatname);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 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
|
||||
@ -123,6 +123,10 @@ public class Flags {
|
||||
*/
|
||||
public static final int HASINIT = 1<<18;
|
||||
|
||||
/** Class is a unnamed top level class.
|
||||
*/
|
||||
public static final int UNNAMED_CLASS = 1<<19;
|
||||
|
||||
/** Flag is set for compiler-generated anonymous method symbols
|
||||
* that `own' an initializer block.
|
||||
*/
|
||||
@ -490,6 +494,7 @@ public class Flags {
|
||||
ANNOTATION(Flags.ANNOTATION),
|
||||
DEPRECATED(Flags.DEPRECATED),
|
||||
HASINIT(Flags.HASINIT),
|
||||
UNNAMED_CLASS(Flags.UNNAMED_CLASS),
|
||||
BLOCK(Flags.BLOCK),
|
||||
FROM_SOURCE(Flags.FROM_SOURCE),
|
||||
ENUM(Flags.ENUM),
|
||||
|
@ -210,6 +210,7 @@ public class Preview {
|
||||
public boolean isPreview(Feature feature) {
|
||||
return switch (feature) {
|
||||
case STRING_TEMPLATES -> true;
|
||||
case UNNAMED_CLASSES -> true;
|
||||
case UNNAMED_VARIABLES -> true;
|
||||
//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'
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 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
|
||||
@ -238,6 +238,7 @@ public enum Source {
|
||||
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK21, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
|
||||
RECORD_PATTERNS(JDK21, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
|
||||
STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL),
|
||||
UNNAMED_CLASSES(JDK21, Fragments.FeatureUnnamedClasses, DiagKind.PLURAL),
|
||||
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
|
||||
UNNAMED_VARIABLES(JDK21, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL),
|
||||
;
|
||||
|
@ -1256,6 +1256,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
|
||||
/** A class for class symbols
|
||||
*/
|
||||
@SuppressWarnings("preview") // isUnnamed()
|
||||
public static class ClassSymbol extends TypeSymbol implements TypeElement {
|
||||
|
||||
/** a scope for all class members; variables, methods and inner classes
|
||||
@ -1367,10 +1368,15 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
return fullname.toString();
|
||||
}
|
||||
|
||||
@DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public Name getQualifiedName() {
|
||||
return fullname;
|
||||
}
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public Name getQualifiedName() {
|
||||
return isUnnamed() ? fullname.subName(0, 0) /* empty name */ : fullname;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public Name getSimpleName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public List<Symbol> getEnclosedElements() {
|
||||
@ -1545,7 +1551,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
@DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public NestingKind getNestingKind() {
|
||||
apiComplete();
|
||||
if (owner.kind == PCK)
|
||||
if (owner.kind == PCK) // Handles unnamed classes as well
|
||||
return NestingKind.TOP_LEVEL;
|
||||
else if (name.isEmpty())
|
||||
return NestingKind.ANONYMOUS;
|
||||
@ -1636,6 +1642,11 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
public List<Type> getPermittedSubclasses() {
|
||||
return permitted.map(s -> s.type);
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public boolean isUnnamed() {
|
||||
return (flags_field & Flags.UNNAMED_CLASS) != 0 ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -5511,6 +5511,10 @@ public class Attr extends JCTree.Visitor {
|
||||
chk.checkClassOverrideEqualsAndHashIfNeeded(env.tree.pos(), c);
|
||||
chk.checkFunctionalInterface((JCClassDecl) env.tree, c);
|
||||
chk.checkLeaksNotAccessible(env, (JCClassDecl) env.tree);
|
||||
|
||||
if ((c.flags_field & Flags.UNNAMED_CLASS) != 0) {
|
||||
chk.checkHasMain(env.tree.pos(), c);
|
||||
}
|
||||
} finally {
|
||||
env.info.returnResult = prevReturnRes;
|
||||
log.useSource(prev);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 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
|
||||
@ -2250,6 +2250,36 @@ public class Check {
|
||||
}
|
||||
}
|
||||
|
||||
public void checkHasMain(DiagnosticPosition pos, ClassSymbol c) {
|
||||
boolean found = false;
|
||||
|
||||
for (Symbol sym : c.members().getSymbolsByName(names.main)) {
|
||||
if (sym.kind == MTH && (sym.flags() & PRIVATE) == 0) {
|
||||
MethodSymbol meth = (MethodSymbol)sym;
|
||||
if (!types.isSameType(meth.getReturnType(), syms.voidType)) {
|
||||
continue;
|
||||
}
|
||||
if (meth.params.isEmpty()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (meth.params.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
if (!types.isSameType(meth.params.head.type, types.makeArrayType(syms.stringType))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
log.error(pos, Errors.UnnamedClassDoesNotHaveMainMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkModuleName (JCModuleDecl tree) {
|
||||
Name moduleName = tree.sym.name;
|
||||
Assert.checkNonNull(moduleName);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 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
|
||||
@ -308,8 +308,6 @@ public class Enter extends JCTree.Visitor {
|
||||
|
||||
@Override
|
||||
public void visitTopLevel(JCCompilationUnit tree) {
|
||||
// Assert.checkNonNull(tree.modle, tree.sourcefile.toString());
|
||||
|
||||
JavaFileObject prev = log.useSource(tree.sourcefile);
|
||||
boolean addEnv = false;
|
||||
boolean isPkgInfo = tree.sourcefile.isNameCompatible("package-info",
|
||||
@ -441,6 +439,9 @@ public class Enter extends JCTree.Visitor {
|
||||
log.error(tree.pos(),
|
||||
Errors.ClassPublicShouldBeInFile(topElement, tree.name));
|
||||
}
|
||||
if ((tree.mods.flags & UNNAMED_CLASS) != 0) {
|
||||
syms.removeClass(env.toplevel.modle, tree.name);
|
||||
}
|
||||
} else {
|
||||
if (!tree.name.isEmpty() &&
|
||||
!chk.checkUniqueClassName(tree.pos(), tree.name, enclScope)) {
|
||||
|
@ -53,7 +53,6 @@ import com.sun.tools.javac.util.DefinedBy.Api;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.Warning;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -65,7 +64,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
@ -2432,7 +2430,6 @@ public class Resolve {
|
||||
|
||||
if (kind.contains(KindSelector.TYP)) {
|
||||
sym = findType(env, name);
|
||||
|
||||
if (sym.exists()) return sym;
|
||||
else bestSoFar = bestOf(bestSoFar, sym);
|
||||
}
|
||||
|
@ -2730,6 +2730,11 @@ public class ClassReader {
|
||||
signatureBuffer = new byte[ns];
|
||||
}
|
||||
readClass(c);
|
||||
if (previewClassFile) {
|
||||
if ((c.flags_field & SYNTHETIC) != 0 && c.isSubClass(syms.objectType.tsym, types)) {
|
||||
c.flags_field |= UNNAMED_CLASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readClassFile(ClassSymbol c) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 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
|
||||
@ -38,6 +38,7 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
@ -84,6 +85,8 @@ import com.sun.tools.javac.code.Source;
|
||||
import com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.Error;
|
||||
|
||||
import jdk.internal.misc.MainMethodFinder;
|
||||
import jdk.internal.misc.PreviewFeatures;
|
||||
import jdk.internal.misc.VM;
|
||||
|
||||
import static javax.tools.JavaFileObject.Kind.SOURCE;
|
||||
@ -201,8 +204,8 @@ public class Main {
|
||||
Context context = new Context(file.toAbsolutePath());
|
||||
String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
|
||||
|
||||
String[] appArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
execute(mainClassName, appArgs, context);
|
||||
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
execute(mainClassName, mainArgs, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -403,7 +406,9 @@ public class Main {
|
||||
if (l.mainClass == null) {
|
||||
throw new Fault(Errors.NoClass);
|
||||
}
|
||||
String mainClassName = l.mainClass.getQualifiedName().toString();
|
||||
TypeElement mainClass = l.mainClass;
|
||||
String mainClassName = (mainClass.isUnnamed() ? mainClass.getSimpleName()
|
||||
: mainClass.getQualifiedName()).toString();
|
||||
return mainClassName;
|
||||
}
|
||||
|
||||
@ -412,31 +417,72 @@ public class Main {
|
||||
* will load recently compiled classes from memory.
|
||||
*
|
||||
* @param mainClassName the class to be executed
|
||||
* @param appArgs the arguments for the {@code main} method
|
||||
* @param mainArgs the arguments for the {@code main} method
|
||||
* @param context the context for the class to be executed
|
||||
* @throws Fault if there is a problem finding or invoking the {@code main} method
|
||||
* @throws InvocationTargetException if the {@code main} method throws an exception
|
||||
*/
|
||||
private void execute(String mainClassName, String[] appArgs, Context context)
|
||||
private void execute(String mainClassName, String[] mainArgs, Context context)
|
||||
throws Fault, InvocationTargetException {
|
||||
System.setProperty("jdk.launcher.sourcefile", context.file.toString());
|
||||
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
|
||||
|
||||
Class<?> appClass;
|
||||
try {
|
||||
Class<?> appClass = Class.forName(mainClassName, true, cl);
|
||||
Method main = appClass.getDeclaredMethod("main", String[].class);
|
||||
int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
|
||||
if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
|
||||
throw new Fault(Errors.MainNotPublicStatic);
|
||||
}
|
||||
if (!main.getReturnType().equals(void.class)) {
|
||||
throw new Fault(Errors.MainNotVoid);
|
||||
}
|
||||
main.setAccessible(true);
|
||||
main.invoke(0, (Object) appArgs);
|
||||
appClass = Class.forName(mainClassName, true, cl);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new Fault(Errors.CantFindClass(mainClassName));
|
||||
}
|
||||
|
||||
Method mainMethod;
|
||||
try {
|
||||
mainMethod = MainMethodFinder.findMainMethod(appClass);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new Fault(Errors.CantFindMainMethod(mainClassName));
|
||||
}
|
||||
|
||||
int mods = 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;
|
||||
|
||||
if (!isStatic) {
|
||||
Constructor<?> constructor;
|
||||
try {
|
||||
constructor = appClass.getDeclaredConstructor();
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new Fault(Errors.CantFindConstructor(mainClassName));
|
||||
}
|
||||
|
||||
try {
|
||||
constructor.setAccessible(true);
|
||||
instance = constructor.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new Fault(Errors.CantAccessConstructor(mainClassName));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Similar to sun.launcher.LauncherHelper#executeMainClass
|
||||
// but duplicated here to prevent additional launcher frames
|
||||
mainMethod.setAccessible(true);
|
||||
Object receiver = isStatic ? appClass : instance;
|
||||
|
||||
if (noArgs) {
|
||||
mainMethod.invoke(receiver);
|
||||
} else {
|
||||
mainMethod.invoke(receiver, (Object)mainArgs);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new Fault(Errors.CantAccessMainMethod(mainClassName));
|
||||
} catch (InvocationTargetException e) {
|
||||
|
@ -30,12 +30,15 @@ import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
|
||||
import com.sun.source.tree.CaseTree;
|
||||
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
|
||||
import com.sun.source.tree.ModuleTree.ModuleKind;
|
||||
|
||||
import com.sun.tools.javac.code.*;
|
||||
import com.sun.tools.javac.code.Source.Feature;
|
||||
import com.sun.tools.javac.file.PathFileObject;
|
||||
import com.sun.tools.javac.parser.Tokens.*;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
|
||||
import com.sun.tools.javac.resources.CompilerProperties.Errors;
|
||||
@ -182,10 +185,33 @@ public class JavacParser implements Parser {
|
||||
this.allowStringFolding = fac.options.getBoolean("allowStringFolding", true);
|
||||
this.keepDocComments = keepDocComments;
|
||||
this.parseModuleInfo = parseModuleInfo;
|
||||
docComments = newDocCommentTable(keepDocComments, fac);
|
||||
this.docComments = newDocCommentTable(keepDocComments, fac);
|
||||
this.keepLineMap = keepLineMap;
|
||||
this.errorTree = F.Erroneous();
|
||||
endPosTable = newEndPosTable(keepEndPositions);
|
||||
this.endPosTable = newEndPosTable(keepEndPositions);
|
||||
this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
|
||||
this.allowRecords = Feature.RECORDS.allowedInSource(source);
|
||||
this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source);
|
||||
}
|
||||
|
||||
/** Construct a parser from an existing parser, with minimal overhead.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
protected JavacParser(JavacParser parser,
|
||||
Lexer S) {
|
||||
this.S = S;
|
||||
this.token = parser.token;
|
||||
this.F = parser.F;
|
||||
this.log = parser.log;
|
||||
this.names = parser.names;
|
||||
this.source = parser.source;
|
||||
this.preview = parser.preview;
|
||||
this.allowStringFolding = parser.allowStringFolding;
|
||||
this.keepDocComments = false;
|
||||
this.parseModuleInfo = false;
|
||||
this.docComments = null;
|
||||
this.errorTree = F.Erroneous();
|
||||
this.endPosTable = newEndPosTable(false);
|
||||
this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
|
||||
this.allowRecords = Feature.RECORDS.allowedInSource(source);
|
||||
this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source);
|
||||
@ -2794,10 +2820,7 @@ public class JavacParser implements Parser {
|
||||
case FINAL: {
|
||||
dc = token.comment(CommentStyle.JAVADOC);
|
||||
JCModifiers mods = modifiersOpt();
|
||||
if (token.kind == INTERFACE ||
|
||||
token.kind == CLASS ||
|
||||
token.kind == ENUM ||
|
||||
isRecordStart()) {
|
||||
if (isDeclaration()) {
|
||||
return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));
|
||||
} else {
|
||||
JCExpression t = parseType(true);
|
||||
@ -3891,6 +3914,7 @@ public class JavacParser implements Parser {
|
||||
}
|
||||
|
||||
boolean firstTypeDecl = true; // have we see a class, enum, or interface declaration yet?
|
||||
boolean isUnnamedClass = false;
|
||||
while (token.kind != EOF) {
|
||||
if (token.pos <= endPosTable.errorEndPos) {
|
||||
// error recovery
|
||||
@ -3949,16 +3973,43 @@ public class JavacParser implements Parser {
|
||||
reportSyntaxError(token.pos, Errors.ExpectedModule);
|
||||
}
|
||||
}
|
||||
|
||||
defs.appendList(semiList.toList());
|
||||
JCTree def = typeDeclaration(mods, docComment);
|
||||
if (def instanceof JCExpressionStatement statement)
|
||||
def = statement.expr;
|
||||
defs.append(def);
|
||||
boolean isTopLevelMethodOrField = false;
|
||||
|
||||
// Do to a significant number of existing negative tests
|
||||
// this code speculatively tests to see if a top level method
|
||||
// or field can parse. If the method or field can parse then
|
||||
// it is parsed. Otherwise, parsing continues as though
|
||||
// unnamed classes did not exist and error reporting
|
||||
// is the same as in the past.
|
||||
if (Feature.UNNAMED_CLASSES.allowedInSource(source) && !isDeclaration()) {
|
||||
final JCModifiers finalMods = mods;
|
||||
JavacParser speculative = new VirtualParser(this);
|
||||
List<JCTree> speculativeResult =
|
||||
speculative.topLevelMethodOrFieldDeclaration(finalMods);
|
||||
if (speculativeResult.head.hasTag(METHODDEF) ||
|
||||
speculativeResult.head.hasTag(VARDEF)) {
|
||||
isTopLevelMethodOrField = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTopLevelMethodOrField) {
|
||||
defs.appendList(topLevelMethodOrFieldDeclaration(mods));
|
||||
isUnnamedClass = true;
|
||||
} else {
|
||||
JCTree def = typeDeclaration(mods, docComment);
|
||||
if (def instanceof JCExpressionStatement statement)
|
||||
def = statement.expr;
|
||||
defs.append(def);
|
||||
}
|
||||
|
||||
mods = null;
|
||||
firstTypeDecl = false;
|
||||
}
|
||||
}
|
||||
JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(defs.toList());
|
||||
List<JCTree> topLevelDefs = isUnnamedClass ? constructUnnamedClass(defs.toList()) : defs.toList();
|
||||
JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs);
|
||||
if (!consumedToplevelDoc)
|
||||
attach(toplevel, firstToken.comment(CommentStyle.JAVADOC));
|
||||
if (defs.isEmpty())
|
||||
@ -3972,6 +4023,43 @@ public class JavacParser implements Parser {
|
||||
return toplevel;
|
||||
}
|
||||
|
||||
// Restructure top level to be an unnamed class.
|
||||
private List<JCTree> constructUnnamedClass(List<JCTree> origDefs) {
|
||||
checkSourceLevel(Feature.UNNAMED_CLASSES);
|
||||
|
||||
ListBuffer<JCTree> topDefs = new ListBuffer<>();
|
||||
ListBuffer<JCTree> defs = new ListBuffer<>();
|
||||
|
||||
for (JCTree def : origDefs) {
|
||||
if (def.hasTag(Tag.PACKAGEDEF)) {
|
||||
log.error(def.pos(), Errors.UnnamedClassShouldNotHavePackageDeclaration);
|
||||
} else if (def.hasTag(Tag.IMPORT)) {
|
||||
topDefs.append(def);
|
||||
} else if (!def.hasTag(Tag.SKIP)) {
|
||||
defs.append(def);
|
||||
}
|
||||
}
|
||||
|
||||
int primaryPos = defs.first().pos;
|
||||
String simplename = PathFileObject.getSimpleName(log.currentSourceFile());
|
||||
|
||||
if (simplename.endsWith(".java")) {
|
||||
simplename = simplename.substring(0, simplename.length() - ".java".length());
|
||||
}
|
||||
if (!SourceVersion.isIdentifier(simplename) || SourceVersion.isKeyword(simplename)) {
|
||||
log.error(primaryPos, Errors.BadFileName(simplename));
|
||||
}
|
||||
|
||||
Name name = names.fromString(simplename);
|
||||
JCModifiers anonMods = F.at(primaryPos)
|
||||
.Modifiers(Flags.FINAL|Flags.SYNTHETIC|Flags.UNNAMED_CLASS, List.nil());
|
||||
JCClassDecl anon = F.at(primaryPos).ClassDef(
|
||||
anonMods, name, List.nil(), null, List.nil(), List.nil(),
|
||||
defs.toList());
|
||||
topDefs.append(anon);
|
||||
return topDefs.toList();
|
||||
}
|
||||
|
||||
JCModuleDecl moduleDecl(JCModifiers mods, ModuleKind kind, Comment dc) {
|
||||
int pos = token.pos;
|
||||
checkSourceLevel(Feature.MODULES);
|
||||
@ -4132,17 +4220,17 @@ public class JavacParser implements Parser {
|
||||
} else {
|
||||
errs = List.of(mods);
|
||||
}
|
||||
final JCErroneous erroneousTree;
|
||||
|
||||
JCDiagnostic.Error error;
|
||||
if (parseModuleInfo) {
|
||||
erroneousTree = syntaxError(pos, errs, Errors.ExpectedModuleOrOpen);
|
||||
error = Errors.ExpectedModuleOrOpen;
|
||||
} else if (allowRecords) {
|
||||
error = Errors.Expected4(CLASS, INTERFACE, ENUM, "record");
|
||||
} else {
|
||||
if (allowRecords) {
|
||||
erroneousTree = syntaxError(pos, errs, Errors.Expected4(CLASS, INTERFACE, ENUM, "record"));
|
||||
} else {
|
||||
erroneousTree = syntaxError(pos, errs, Errors.Expected3(CLASS, INTERFACE, ENUM));
|
||||
}
|
||||
error = Errors.Expected3(CLASS, INTERFACE, ENUM);
|
||||
}
|
||||
return toP(F.Exec(erroneousTree));
|
||||
return toP(F.Exec(syntaxError(pos, errs, error)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -4363,7 +4451,7 @@ public class JavacParser implements Parser {
|
||||
hasStructuralErrors = true;
|
||||
}
|
||||
wasError = false;
|
||||
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(enumName,
|
||||
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(null, enumName,
|
||||
false, false));
|
||||
if (token.pos <= endPosTable.errorEndPos) {
|
||||
// error recovery
|
||||
@ -4469,11 +4557,11 @@ public class JavacParser implements Parser {
|
||||
}
|
||||
ListBuffer<JCTree> defs = new ListBuffer<>();
|
||||
while (token.kind != RBRACE && token.kind != EOF) {
|
||||
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(className, isInterface, isRecord));
|
||||
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(null, className, isInterface, isRecord));
|
||||
if (token.pos <= endPosTable.errorEndPos) {
|
||||
// error recovery
|
||||
skip(false, true, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
accept(RBRACE);
|
||||
return defs.toList();
|
||||
@ -4508,18 +4596,17 @@ public class JavacParser implements Parser {
|
||||
* )
|
||||
*
|
||||
*/
|
||||
protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(Name className, boolean isInterface, boolean isRecord) {
|
||||
protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(JCModifiers mods, Name className,
|
||||
boolean isInterface,
|
||||
boolean isRecord) {
|
||||
if (token.kind == SEMI) {
|
||||
nextToken();
|
||||
return List.nil();
|
||||
} else {
|
||||
Comment dc = token.comment(CommentStyle.JAVADOC);
|
||||
int pos = token.pos;
|
||||
JCModifiers mods = modifiersOpt();
|
||||
if (token.kind == CLASS ||
|
||||
allowRecords && isRecordStart() ||
|
||||
token.kind == INTERFACE ||
|
||||
token.kind == ENUM) {
|
||||
mods = modifiersOpt(mods);
|
||||
if (isDeclaration()) {
|
||||
return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));
|
||||
} else if (token.kind == LBRACE &&
|
||||
(mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 &&
|
||||
@ -4531,97 +4618,185 @@ public class JavacParser implements Parser {
|
||||
}
|
||||
return List.of(block(pos, mods.flags));
|
||||
} else {
|
||||
pos = token.pos;
|
||||
List<JCTypeParameter> typarams = typeParametersOpt();
|
||||
// if there are type parameters but no modifiers, save the start
|
||||
// position of the method in the modifiers.
|
||||
if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
|
||||
mods.pos = pos;
|
||||
storeEnd(mods, pos);
|
||||
}
|
||||
List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);
|
||||
|
||||
if (annosAfterParams.nonEmpty()) {
|
||||
mods.annotations = mods.annotations.appendList(annosAfterParams);
|
||||
if (mods.pos == Position.NOPOS)
|
||||
mods.pos = mods.annotations.head.pos;
|
||||
}
|
||||
|
||||
Token tk = token;
|
||||
pos = token.pos;
|
||||
JCExpression type;
|
||||
boolean isVoid = token.kind == VOID;
|
||||
if (isVoid) {
|
||||
type = to(F.at(pos).TypeIdent(TypeTag.VOID));
|
||||
nextToken();
|
||||
} else {
|
||||
// method returns types are un-annotated types
|
||||
type = unannotatedType(false);
|
||||
}
|
||||
if ((token.kind == LPAREN && !isInterface ||
|
||||
isRecord && token.kind == LBRACE) && type.hasTag(IDENT)) {
|
||||
if (isInterface || tk.name() != className)
|
||||
log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq);
|
||||
else if (annosAfterParams.nonEmpty())
|
||||
illegal(annosAfterParams.head.pos);
|
||||
if (isRecord && token.kind == LBRACE) {
|
||||
mods.flags |= Flags.COMPACT_RECORD_CONSTRUCTOR;
|
||||
}
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, null, names.init, typarams,
|
||||
isInterface, true, isRecord, dc));
|
||||
} else if (isRecord && type.hasTag(IDENT) && token.kind == THROWS) {
|
||||
// trying to define a compact constructor with a throws clause
|
||||
log.error(DiagnosticFlag.SYNTAX, token.pos,
|
||||
Errors.InvalidCanonicalConstructorInRecord(
|
||||
Fragments.Compact,
|
||||
className,
|
||||
Fragments.ThrowsClauseNotAllowedForCanonicalConstructor(Fragments.Compact)));
|
||||
skip(false, true, false, false);
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, null, names.init, typarams,
|
||||
isInterface, true, isRecord, dc));
|
||||
} else {
|
||||
pos = token.pos;
|
||||
Name name = ident();
|
||||
if (token.kind == LPAREN) {
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, type, name, typarams,
|
||||
isInterface, isVoid, false, dc));
|
||||
} else if (!isVoid && typarams.isEmpty()) {
|
||||
if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) {
|
||||
List<JCTree> defs =
|
||||
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
|
||||
new ListBuffer<JCTree>(), false).toList();
|
||||
accept(SEMI);
|
||||
storeEnd(defs.last(), S.prevToken().endPos);
|
||||
return defs;
|
||||
} else {
|
||||
int errPos = pos;
|
||||
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
|
||||
new ListBuffer<JCTree>(), false).toList();
|
||||
accept(SEMI);
|
||||
return List.of(syntaxError(errPos, null, Errors.RecordCannotDeclareInstanceFields));
|
||||
}
|
||||
} else {
|
||||
pos = token.pos;
|
||||
List<JCTree> err;
|
||||
if (isVoid || typarams.nonEmpty()) {
|
||||
JCMethodDecl m =
|
||||
toP(F.at(pos).MethodDef(mods, name, type, typarams,
|
||||
List.nil(), List.nil(), null, null));
|
||||
attach(m, dc);
|
||||
err = List.of(m);
|
||||
} else {
|
||||
err = List.nil();
|
||||
}
|
||||
return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN)));
|
||||
}
|
||||
}
|
||||
return constructorOrMethodOrFieldDeclaration(mods, className, isInterface, isRecord, dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<JCTree> constructorOrMethodOrFieldDeclaration(JCModifiers mods, Name className,
|
||||
boolean isInterface,
|
||||
boolean isRecord, Comment dc) {
|
||||
int pos;
|
||||
pos = token.pos;
|
||||
List<JCTypeParameter> typarams = typeParametersOpt();
|
||||
// if there are type parameters but no modifiers, save the start
|
||||
// position of the method in the modifiers.
|
||||
if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
|
||||
mods.pos = pos;
|
||||
storeEnd(mods, pos);
|
||||
}
|
||||
List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);
|
||||
|
||||
if (annosAfterParams.nonEmpty()) {
|
||||
mods.annotations = mods.annotations.appendList(annosAfterParams);
|
||||
if (mods.pos == Position.NOPOS)
|
||||
mods.pos = mods.annotations.head.pos;
|
||||
}
|
||||
|
||||
Token tk = token;
|
||||
pos = token.pos;
|
||||
JCExpression type;
|
||||
boolean isVoid = token.kind == VOID;
|
||||
|
||||
if (isVoid) {
|
||||
type = to(F.at(pos).TypeIdent(TypeTag.VOID));
|
||||
nextToken();
|
||||
} else {
|
||||
// method returns types are un-annotated types
|
||||
type = unannotatedType(false);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
if ((token.kind == LPAREN && !isInterface ||
|
||||
isRecord && token.kind == LBRACE) && type.hasTag(IDENT)) {
|
||||
if (isInterface || tk.name() != className) {
|
||||
log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq);
|
||||
} else if (annosAfterParams.nonEmpty()) {
|
||||
illegal(annosAfterParams.head.pos);
|
||||
}
|
||||
|
||||
if (isRecord && token.kind == LBRACE) {
|
||||
mods.flags |= Flags.COMPACT_RECORD_CONSTRUCTOR;
|
||||
}
|
||||
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, null, names.init, typarams,
|
||||
isInterface, true, isRecord, dc));
|
||||
}
|
||||
|
||||
// Record constructor
|
||||
if (isRecord && type.hasTag(IDENT) && token.kind == THROWS) {
|
||||
// trying to define a compact constructor with a throws clause
|
||||
log.error(DiagnosticFlag.SYNTAX, token.pos,
|
||||
Errors.InvalidCanonicalConstructorInRecord(
|
||||
Fragments.Compact,
|
||||
className,
|
||||
Fragments.ThrowsClauseNotAllowedForCanonicalConstructor(Fragments.Compact)));
|
||||
skip(false, true, false, false);
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, null, names.init, typarams,
|
||||
isInterface, true, isRecord, dc));
|
||||
}
|
||||
|
||||
pos = token.pos;
|
||||
Name name = ident();
|
||||
|
||||
// Method
|
||||
if (token.kind == LPAREN) {
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, type, name, typarams,
|
||||
isInterface, isVoid, false, dc));
|
||||
}
|
||||
|
||||
// Field
|
||||
if (!isVoid && typarams.isEmpty()) {
|
||||
if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) {
|
||||
List<JCTree> defs =
|
||||
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
|
||||
new ListBuffer<JCTree>(), false).toList();
|
||||
accept(SEMI);
|
||||
storeEnd(defs.last(), S.prevToken().endPos);
|
||||
return defs;
|
||||
}
|
||||
|
||||
int errPos = pos;
|
||||
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
|
||||
new ListBuffer<JCTree>(), false).toList();
|
||||
accept(SEMI);
|
||||
return List.of(syntaxError(errPos, null, Errors.RecordCannotDeclareInstanceFields));
|
||||
}
|
||||
|
||||
pos = token.pos;
|
||||
List<JCTree> err;
|
||||
|
||||
// Error recovery
|
||||
if (isVoid || typarams.nonEmpty()) {
|
||||
JCMethodDecl m =
|
||||
toP(F.at(pos).MethodDef(mods, name, type, typarams,
|
||||
List.nil(), List.nil(), null, null));
|
||||
attach(m, dc);
|
||||
err = List.of(m);
|
||||
} else {
|
||||
err = List.nil();
|
||||
}
|
||||
|
||||
return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN)));
|
||||
}
|
||||
|
||||
private List<JCTree> topLevelMethodOrFieldDeclaration(JCModifiers mods) throws AssertionError {
|
||||
int topPos = token.pos;
|
||||
int pos = token.pos;
|
||||
Comment dc = token.comment(CommentStyle.JAVADOC);
|
||||
List<JCTypeParameter> typarams = typeParametersOpt();
|
||||
|
||||
// if there are type parameters but no modifiers, save the start
|
||||
// position of the method in the modifiers.
|
||||
if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
|
||||
mods.pos = pos;
|
||||
storeEnd(mods, pos);
|
||||
}
|
||||
|
||||
List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);
|
||||
|
||||
if (annosAfterParams.nonEmpty()) {
|
||||
mods.annotations = mods.annotations.appendList(annosAfterParams);
|
||||
if (mods.pos == Position.NOPOS)
|
||||
mods.pos = mods.annotations.head.pos;
|
||||
}
|
||||
|
||||
pos = token.pos;
|
||||
JCExpression type;
|
||||
boolean isVoid = token.kind == VOID;
|
||||
|
||||
if (isVoid) {
|
||||
type = to(F.at(pos).TypeIdent(TypeTag.VOID));
|
||||
nextToken();
|
||||
} else {
|
||||
type = unannotatedType(false);
|
||||
}
|
||||
|
||||
if (token.kind == IDENTIFIER) {
|
||||
pos = token.pos;
|
||||
Name name = ident();
|
||||
|
||||
// Method
|
||||
if (token.kind == LPAREN) {
|
||||
return List.of(methodDeclaratorRest(pos, mods, type, name, typarams,
|
||||
false, isVoid, false, dc));
|
||||
}
|
||||
|
||||
// Field
|
||||
if (!isVoid && typarams.isEmpty() && (token.kind == EQ || token.kind == SEMI)) {
|
||||
List<JCTree> defs =
|
||||
variableDeclaratorsRest(pos, mods, type, name, false, dc,
|
||||
new ListBuffer<JCTree>(), false).toList();
|
||||
accept(SEMI);
|
||||
storeEnd(defs.last(), S.prevToken().endPos);
|
||||
|
||||
return defs;
|
||||
}
|
||||
}
|
||||
|
||||
return List.of(F.Erroneous());
|
||||
}
|
||||
|
||||
protected boolean isDeclaration() {
|
||||
return token.kind == CLASS ||
|
||||
token.kind == INTERFACE ||
|
||||
token.kind == ENUM ||
|
||||
isRecordStart() && allowRecords;
|
||||
}
|
||||
|
||||
protected boolean isRecordStart() {
|
||||
if (token.kind == IDENTIFIER && token.name() == names.record && peekToken(TokenKind.IDENTIFIER)) {
|
||||
checkSourceLevel(Feature.RECORDS);
|
||||
|
@ -42,7 +42,7 @@ import static com.sun.tools.javac.parser.Tokens.*;
|
||||
*/
|
||||
public class Scanner implements Lexer {
|
||||
|
||||
private final Tokens tokens;
|
||||
protected Tokens tokens;
|
||||
|
||||
/** The token, set by nextToken().
|
||||
*/
|
||||
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 com.sun.tools.javac.parser;
|
||||
|
||||
import com.sun.tools.javac.parser.Tokens.Token;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCErroneous;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.Error;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import com.sun.tools.javac.util.Position.LineMap;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The virtual parser allows for speculative parsing while not commiting to
|
||||
* consuming tokens unless the speculation is successful.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class VirtualParser extends JavacParser {
|
||||
|
||||
private boolean hasErrors;
|
||||
|
||||
public VirtualParser(JavacParser parser) {
|
||||
super(parser, new VirtualScanner(parser.S));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JCErroneous syntaxError(int pos, Error errorKey) {
|
||||
hasErrors = true;
|
||||
return F.Erroneous();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JCErroneous syntaxError(int pos, List<JCTree> errs, Error errorKey) {
|
||||
hasErrors = true;
|
||||
return F.Erroneous();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reportSyntaxError(int pos, Error errorKey) {
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reportSyntaxError(DiagnosticPosition diagPos, Error errorKey) {
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return hasErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scanner that does token lookahead and throws AssertionErrors if an error
|
||||
* occurs.
|
||||
*/
|
||||
public static class VirtualScanner implements Lexer {
|
||||
/** Parent scanner.
|
||||
*/
|
||||
Lexer S;
|
||||
|
||||
/** Token offset from where parent scanner branched.
|
||||
*/
|
||||
int offset = 0;
|
||||
|
||||
/** The token, set by nextToken().
|
||||
*/
|
||||
private Token token;
|
||||
|
||||
/** The previous token, set by nextToken().
|
||||
*/
|
||||
private Token prevToken;
|
||||
|
||||
public VirtualScanner(Lexer s) {
|
||||
while (s instanceof VirtualScanner virtualScanner) {
|
||||
s = virtualScanner.S;
|
||||
offset += virtualScanner.offset;
|
||||
}
|
||||
S = s;
|
||||
token = s.token();
|
||||
prevToken = S.prevToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextToken() {
|
||||
prevToken = token;
|
||||
offset++;
|
||||
token = token();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token token() {
|
||||
return token(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token token(int lookahead) {
|
||||
return S.token(offset + lookahead);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token prevToken() {
|
||||
return prevToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrevToken(Token prevToken) {
|
||||
this.prevToken = prevToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token split() {
|
||||
Token[] splitTokens = token.split(((Scanner)S).tokens);
|
||||
prevToken = splitTokens[0];
|
||||
token = splitTokens[1];
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errPos() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errPos(int pos) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LineMap getLineMap() {
|
||||
return S.getLineMap();
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
for (int i = 0 ; i < offset ; i++) {
|
||||
S.nextToken(); // advance underlying lexer until position matches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts a parse action and returns true if successful or false if
|
||||
* a parse error is thrown.
|
||||
*
|
||||
* @param parser parent parser
|
||||
* @param parserAction function that takes a parser and invokes a method on that parser
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
public static boolean tryParse(JavacParser parser, Consumer<JavacParser> parserAction) {
|
||||
VirtualParser virtualParser = new VirtualParser(parser);
|
||||
try {
|
||||
parserAction.accept(virtualParser);
|
||||
return true;
|
||||
} catch (AssertionError ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -118,6 +118,7 @@ public class PrintingProcessor extends AbstractProcessor {
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
@SuppressWarnings("preview") // isUnnamed
|
||||
public PrintingElementVisitor visitExecutable(ExecutableElement e, Boolean p) {
|
||||
ElementKind kind = e.getKind();
|
||||
|
||||
@ -125,18 +126,26 @@ public class PrintingProcessor extends AbstractProcessor {
|
||||
kind != INSTANCE_INIT) {
|
||||
Element enclosing = e.getEnclosingElement();
|
||||
|
||||
// Don't print out the constructor of an anonymous class
|
||||
// Don't print out the constructor of an anonymous or unnamed class
|
||||
if (kind == CONSTRUCTOR &&
|
||||
enclosing != null &&
|
||||
NestingKind.ANONYMOUS ==
|
||||
(NestingKind.ANONYMOUS ==
|
||||
// Use an anonymous class to determine anonymity!
|
||||
(new SimpleElementVisitor14<NestingKind, Void>() {
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public NestingKind visitType(TypeElement e, Void p) {
|
||||
return e.getNestingKind();
|
||||
}
|
||||
}).visit(enclosing))
|
||||
}).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)) ) {
|
||||
return this;
|
||||
}
|
||||
|
||||
defaultAction(e, true);
|
||||
printFormalTypeParameters(e, true);
|
||||
@ -169,6 +178,7 @@ public class PrintingProcessor extends AbstractProcessor {
|
||||
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
@SuppressWarnings("preview") // isUnnamed
|
||||
public PrintingElementVisitor visitType(TypeElement e, Boolean p) {
|
||||
ElementKind kind = e.getKind();
|
||||
NestingKind nestingKind = e.getNestingKind();
|
||||
@ -202,6 +212,14 @@ public class PrintingProcessor extends AbstractProcessor {
|
||||
printParameters(constructors.get(0));
|
||||
}
|
||||
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 {
|
||||
if (nestingKind == TOP_LEVEL) {
|
||||
PackageElement pkg = elementUtils.getPackageOf(e);
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 1999, 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
|
||||
@ -512,6 +512,16 @@ compiler.err.invalid.repeatable.annotation.not.applicable.in.context=\
|
||||
compiler.err.duplicate.class=\
|
||||
duplicate class: {0}
|
||||
|
||||
# 0: string
|
||||
compiler.err.bad.file.name=\
|
||||
bad file name: {0}
|
||||
|
||||
compiler.err.unnamed.class.should.not.have.package.declaration=\
|
||||
unnamed class should not have package declaration
|
||||
|
||||
compiler.err.unnamed.class.does.not.have.main.method=\
|
||||
unnamed class does not have main method in the form of void main() or void main(String[] args)
|
||||
|
||||
# 0: name, 1: name
|
||||
compiler.err.same.binary.name=\
|
||||
classes: {0} and {1} have the same binary name
|
||||
@ -3158,6 +3168,9 @@ compiler.misc.feature.string.templates=\
|
||||
compiler.misc.feature.unconditional.patterns.in.instanceof=\
|
||||
unconditional patterns in instanceof
|
||||
|
||||
compiler.misc.feature.unnamed.classes=\
|
||||
unnamed classes
|
||||
|
||||
compiler.warn.underscore.as.identifier=\
|
||||
as of release 9, ''_'' is a keyword, and may not be used as an identifier
|
||||
|
||||
|
@ -119,6 +119,14 @@ launcher.err.cant.find.main.method=\
|
||||
launcher.err.cant.access.main.method=\
|
||||
can''t access main method in class: {0}
|
||||
|
||||
# 0: string
|
||||
launcher.err.cant.find.constructor=\
|
||||
can''t find no argument constructor in class: {0}
|
||||
|
||||
# 0: string
|
||||
launcher.err.cant.access.constructor=\
|
||||
can''t access no argument constructor in class: {0}
|
||||
|
||||
# 0: path, 1: object
|
||||
launcher.err.cant.read.file=\
|
||||
error reading source file {0}: {1}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 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
|
||||
@ -132,6 +132,8 @@ public class TreeMaker implements JCTree.Factory {
|
||||
|| node instanceof JCModuleDecl
|
||||
|| node instanceof JCSkip
|
||||
|| node instanceof JCErroneous
|
||||
|| node instanceof JCMethodDecl
|
||||
|| node instanceof JCVariableDecl
|
||||
|| (node instanceof JCExpressionStatement expressionStatement
|
||||
&& expressionStatement.expr instanceof JCErroneous),
|
||||
() -> node.getClass().getSimpleName());
|
||||
@ -1159,7 +1161,7 @@ public class TreeMaker implements JCTree.Factory {
|
||||
!it.hasNext();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return sym.kind == TYP && (sym.flags_field & Flags.UNNAMED_CLASS) != 0;
|
||||
}
|
||||
|
||||
/** The name of synthetic parameter number `i'.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 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
|
||||
@ -356,8 +356,7 @@ public class Convert {
|
||||
if (start == 0 && end == name.length()) {
|
||||
return name;
|
||||
}
|
||||
return name.subName(
|
||||
name.lastIndexOf((byte)'.') + 1, name.getByteLength());
|
||||
return name.subName(start, end);
|
||||
}
|
||||
|
||||
/** Return the last part of a qualified name from its string representation
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 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
|
||||
@ -193,6 +193,7 @@ public class Names {
|
||||
public final Name module_info;
|
||||
public final Name package_info;
|
||||
public final Name requireNonNull;
|
||||
public final Name main;
|
||||
|
||||
// lambda-related
|
||||
public final Name lambda;
|
||||
@ -388,6 +389,7 @@ public class Names {
|
||||
module_info = fromString("module-info");
|
||||
package_info = fromString("package-info");
|
||||
requireNonNull = fromString("requireNonNull");
|
||||
main = fromString("main");
|
||||
|
||||
//lambda-related
|
||||
lambda = fromString("lambda$");
|
||||
|
190
test/jdk/tools/launcher/InstanceMainTest.java
Normal file
190
test/jdk/tools/launcher/InstanceMainTest.java
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary test execution priority of main methods
|
||||
* @run main InstanceMainTest
|
||||
*/
|
||||
public class InstanceMainTest extends TestHelper {
|
||||
|
||||
private static final String[] SOURCES = new String[] {
|
||||
// static dominating with args
|
||||
"""
|
||||
class MainClass {
|
||||
static void main() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
static void main(String[] args) {
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// static dominating instance
|
||||
"""
|
||||
class MainClass {
|
||||
void main(String[] args) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
static void main() {
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// instance dominating with args
|
||||
"""
|
||||
class MainClass {
|
||||
void main() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
void main(String[] args) {
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// instance no args
|
||||
"""
|
||||
class MainClass {
|
||||
void main() {
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// unnamed class static dominating with args
|
||||
"""
|
||||
static void main() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
static void main(String[] args) {
|
||||
}
|
||||
""",
|
||||
|
||||
// unnamed class static dominating instance
|
||||
"""
|
||||
void main(String[] args) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
static void main() {
|
||||
}
|
||||
""",
|
||||
|
||||
// unnamed class instance dominating with args
|
||||
"""
|
||||
void main() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
void main(String[] args) {
|
||||
}
|
||||
""",
|
||||
|
||||
// unnamed class instance main no args
|
||||
"""
|
||||
void main() {
|
||||
}
|
||||
""",
|
||||
|
||||
// instance main dominating super static
|
||||
"""
|
||||
class MainClass extends SuperClass {
|
||||
void main() {
|
||||
}
|
||||
}
|
||||
class SuperClass {
|
||||
void main(String[] args) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// super instance main with args dominating
|
||||
"""
|
||||
public class MainClass extends Super {
|
||||
}
|
||||
|
||||
class Super {
|
||||
public void main(String... args) {
|
||||
}
|
||||
|
||||
public void main() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// ignore super instance main
|
||||
"""
|
||||
public class MainClass extends Super {
|
||||
public static void main(String... args) {
|
||||
}
|
||||
}
|
||||
|
||||
class Super {
|
||||
public static void main(String... args) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// enum main
|
||||
"""
|
||||
enum MainClass {
|
||||
A;
|
||||
|
||||
public static void main() {
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
// record main
|
||||
"""
|
||||
record MainClass() {
|
||||
static void main() {
|
||||
System.out.println("Done!");
|
||||
}
|
||||
}
|
||||
""",
|
||||
// interface main
|
||||
"""
|
||||
interface MainClass {
|
||||
static void main() {
|
||||
System.out.println("Done!");
|
||||
}
|
||||
}
|
||||
"""
|
||||
};
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
for (String source : SOURCES) {
|
||||
Files.writeString(Path.of("MainClass.java"), source);
|
||||
var version = System.getProperty("java.specification.version");
|
||||
var tr = doExec(javaCmd, "--enable-preview", "--source", version, "MainClass.java");
|
||||
if (!tr.isOK()) {
|
||||
System.err.println(source);
|
||||
System.err.println(tr);
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
test/langtools/tools/javac/diags/examples/UnnamedClass.java
Normal file
29
test/langtools/tools/javac/diags/examples/UnnamedClass.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// key: compiler.misc.feature.unnamed.classes
|
||||
// key: compiler.warn.preview.feature.use.plural
|
||||
// options: -source ${jdk.version} --enable-preview -Xlint:preview
|
||||
|
||||
public static void main(String... args) {
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// key: compiler.err.bad.file.name
|
||||
// key: compiler.note.preview.filename
|
||||
// key: compiler.note.preview.recompile
|
||||
// options: -source ${jdk.version} --enable-preview
|
||||
|
||||
public static void main(String... args) {
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// key: compiler.err.unnamed.class.should.not.have.package.declaration
|
||||
// key: compiler.note.preview.filename
|
||||
// key: compiler.note.preview.recompile
|
||||
// options: -source ${jdk.version} --enable-preview
|
||||
|
||||
package unnamed.classes;
|
||||
|
||||
public static void main(String... args) {
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// key: compiler.err.unnamed.class.does.not.have.main.method
|
||||
// key: compiler.note.preview.filename
|
||||
// key: compiler.note.preview.recompile
|
||||
// options: -source ${jdk.version} --enable-preview
|
||||
|
||||
public void ordinaryMethod() {
|
||||
}
|
@ -553,7 +553,7 @@ public class SourceLauncherTest extends TestRunner {
|
||||
tb.writeJavaFiles(base,
|
||||
"class NotPublic { static void main(String... args) { } }");
|
||||
testError(base.resolve("NotPublic.java"), "",
|
||||
"error: 'main' method is not declared 'public static'");
|
||||
"error: can't find main(String[]) method in class: NotPublic");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* A class with no-name.
|
||||
*/
|
||||
|
||||
void main() {
|
||||
printMessage();
|
||||
}
|
||||
|
||||
private static String horseName = null;
|
||||
|
||||
private static void printMessage() {
|
||||
if (horseName == null) {
|
||||
System.out.print("A horse has no name.");
|
||||
} else
|
||||
System.out.print("A horse's name is " + horseName);
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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
|
||||
* @summary Test basic processing of unnamed classes.
|
||||
* @library /tools/javac/lib
|
||||
* @modules java.compiler
|
||||
* jdk.compiler
|
||||
* @build JavacTestingAbstractProcessor TestUnnamedClass
|
||||
* @compile -processor TestUnnamedClass -proc:only --enable-preview --release ${jdk.version} Anonymous.java
|
||||
*/
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.util.Elements;
|
||||
import static javax.lang.model.util.ElementFilter.*;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/*
|
||||
* Ideally, this processor would test both the compile-time
|
||||
* representation of an unnamed class starting from a source file as
|
||||
* well as the representation starting from a class file. Currently,
|
||||
* only the source file based view will be tested.
|
||||
*
|
||||
* For future work to test the class file based view, an additional jtreg directive like the following could
|
||||
* be used:
|
||||
*
|
||||
* @compile/process -processor TestUnnamedClass -proc:only Anonymous Nameless
|
||||
*/
|
||||
@SuppressWarnings("preview")
|
||||
public class TestUnnamedClass extends JavacTestingAbstractProcessor {
|
||||
|
||||
private static int round = 0;
|
||||
|
||||
public boolean process(Set<? extends TypeElement> annotations,
|
||||
RoundEnvironment roundEnv) {
|
||||
if (round == 0) { // Check file from comamnd line
|
||||
checkRoots(roundEnv);
|
||||
generateUnnamed();
|
||||
}
|
||||
|
||||
if (!roundEnv.processingOver()) { // Test generated file(s)
|
||||
checkRoots(roundEnv);
|
||||
}
|
||||
|
||||
round++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkRoots(RoundEnvironment roundEnv) {
|
||||
int checks = 0;
|
||||
for (TypeElement type : typesIn(roundEnv.getRootElements())) {
|
||||
System.out.println("Checking " + type.getQualifiedName());
|
||||
checks++;
|
||||
checkUnnamedClassProperties(type);
|
||||
}
|
||||
if (checks == 0) {
|
||||
messager.printError("No checking done of any candidate unnamed classes.");
|
||||
}
|
||||
}
|
||||
|
||||
private void generateUnnamed() {
|
||||
try {
|
||||
String unnamedSource = """
|
||||
void main() {
|
||||
System.out.println("Nameless, but not voiceless.");
|
||||
}
|
||||
""";
|
||||
|
||||
JavaFileObject outputFile = processingEnv.getFiler().createSourceFile("Nameless");
|
||||
try(Writer w = outputFile.openWriter()) {
|
||||
w.append(unnamedSource);
|
||||
}
|
||||
} catch (java.io.IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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).
|
||||
*
|
||||
* The body of the class contains every ClassMemberDeclaration
|
||||
* from the unnamed class compilation unit. It is not possible for
|
||||
* an unnamed class compilation unit to declare an instance
|
||||
* initializer, static initializer, or constructor.
|
||||
*
|
||||
* It has an implicitly declared default constructor (8.8.9).
|
||||
* All members of this class, including any implicitly declared
|
||||
* members, are subject to the usual rules for member declarations
|
||||
* in a class.
|
||||
*
|
||||
* It is a compile-time error if this class does not declare a candidate main method (12.1.4).
|
||||
*/
|
||||
void checkUnnamedClassProperties(TypeElement unnamedClass) {
|
||||
if (unnamedClass.getNestingKind() != NestingKind.TOP_LEVEL) {
|
||||
messager.printError("Unnamed class is not top-level.", unnamedClass);
|
||||
}
|
||||
|
||||
if (!unnamedClass.isUnnamed()) {
|
||||
messager.printError("Unnamed class is _not_ indicated as such.", unnamedClass);
|
||||
}
|
||||
|
||||
if (unnamedClass.getSimpleName().isEmpty()) {
|
||||
messager.printError("Unnamed class does have an empty simple name.", unnamedClass);
|
||||
}
|
||||
|
||||
if (!unnamedClass.getQualifiedName().isEmpty()) {
|
||||
messager.printError("Unnamed class does _not_ have an empty qualified name.", unnamedClass);
|
||||
}
|
||||
|
||||
if (unnamedClass.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
messager.printError("Unnamed class is abstract.", unnamedClass);
|
||||
}
|
||||
|
||||
if (!unnamedClass.getModifiers().contains(Modifier.FINAL)) {
|
||||
messager.printError("Unnamed class is _not_ final.", unnamedClass);
|
||||
}
|
||||
|
||||
if (!elements.getPackageOf(unnamedClass).isUnnamed()) {
|
||||
messager.printError("Unnamed class is _not_ in an unnamed package.", unnamedClass);
|
||||
}
|
||||
|
||||
if (unnamedClass.getModifiers().contains(Modifier.PUBLIC) ||
|
||||
unnamedClass.getModifiers().contains(Modifier.PRIVATE) ||
|
||||
unnamedClass.getModifiers().contains(Modifier.PROTECTED)) {
|
||||
messager.printError("Unnamed class does _not_ have package access.", unnamedClass);
|
||||
}
|
||||
|
||||
if ( !types.isSameType(unnamedClass.getSuperclass(),
|
||||
elements.getTypeElement("java.lang.Object").asType())) {
|
||||
messager.printError("Unnamed class does _not_ have java.lang.Object as a superclass.", unnamedClass);
|
||||
}
|
||||
|
||||
if (!unnamedClass.getInterfaces().isEmpty()) {
|
||||
messager.printError("Unnamed class has superinterfaces.", unnamedClass);
|
||||
}
|
||||
|
||||
List<ExecutableElement> ctors = constructorsIn(unnamedClass.getEnclosedElements());
|
||||
if (ctors.size() != 1 ) {
|
||||
messager.printError("Did not find exactly one constructor", unnamedClass);
|
||||
}
|
||||
|
||||
ExecutableElement ctor = ctors.getFirst();
|
||||
if (elements.getOrigin(ctor) != Elements.Origin.MANDATED) {
|
||||
messager.printError("Constructor was not marked as mandated", ctor);
|
||||
}
|
||||
|
||||
List<ExecutableElement> methods = methodsIn(unnamedClass.getEnclosedElements());
|
||||
// Just look for a method named "main"; don't check the other details.
|
||||
boolean mainFound = false;
|
||||
Name mainName = elements.getName("main");
|
||||
for (var method : methods) {
|
||||
if (method.getSimpleName().equals(mainName)) {
|
||||
mainFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mainFound) {
|
||||
messager.printError("No main mehtod found", unnamedClass);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @test /nodynamiccopyright/
|
||||
* @compile/fail/ref=UnnamedClassRecovery.out -XDrawDiagnostics --enable-preview --source ${jdk.version} UnnamedClassRecovery.java
|
||||
*/
|
||||
public void main() {
|
||||
//the following is intentionally missing a semicolon:
|
||||
System.err.println("Hello!")
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
UnnamedClassRecovery.java:7:33: compiler.err.expected: ';'
|
||||
- compiler.note.preview.filename: UnnamedClassRecovery.java, DEFAULT
|
||||
- compiler.note.preview.recompile
|
||||
1 error
|
34
test/langtools/tools/javac/unnamedclass/NestedEnum.java
Normal file
34
test/langtools/tools/javac/unnamedclass/NestedEnum.java
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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
|
||||
* @summary enums: ensure unnamed class is visible to java.lang.Enum<unnamed_class>
|
||||
* @enablePreview
|
||||
* @compile NestedEnum.java
|
||||
*/
|
||||
|
||||
enum Foo {A, B}
|
||||
void main() {
|
||||
System.out.println(Foo.A);
|
||||
}
|
Loading…
Reference in New Issue
Block a user