Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK-8306112 Implementation of JEP 445: Unnamed Classes and Instance Main Methods (Preview) #13689

Closed
wants to merge 56 commits into from
Closed
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
bbd1de8
JEP 445
Apr 20, 2023
8545dcc
Merge branch 'master' into 8306112
Apr 24, 2023
d5ebe61
Revised main method lookup
Apr 24, 2023
a7bca07
remnant import
Apr 24, 2023
891d00b
Clean up
Apr 25, 2023
8c023f5
Merge branch 'master' into 8306112
Apr 25, 2023
db12c9a
Update TestJavacTaskScanner.java
Apr 25, 2023
44af61d
Clean up testing
Apr 26, 2023
ef56b9e
Update VM.java
Apr 26, 2023
3f174fc
Corrections
Apr 26, 2023
a89cb96
Missing exception
Apr 26, 2023
cfe08f3
Clean up isPreview
Apr 27, 2023
a55af76
PreviewFeatures.isEnabled()
Apr 27, 2023
53a5321
Merge branch 'master' into 8306112
Apr 27, 2023
f66f6e4
Recommended changes #1
Apr 27, 2023
e5ca303
Can't be invokeExact for instance main
Apr 28, 2023
6f81996
Unused variables
Apr 28, 2023
2c32183
Leave exception alone
Apr 28, 2023
201c9ee
Revert java launch
Apr 29, 2023
a09a0a1
Move AnonymousMainClass to parser
Apr 29, 2023
ffc7763
Add test
May 1, 2023
ff7cd4c
Anonymous main classes renamed to unnamed classes
May 1, 2023
788b4e5
Recommended changes #2
May 5, 2023
0c8add6
Typo
May 5, 2023
b91e75a
Refactor source code launcher
May 5, 2023
c3cfa83
Merge branch 'master' into 8306112
May 11, 2023
90b1e98
Update VirtualParser.java
May 11, 2023
2b625a3
Requested Changes #2
May 15, 2023
185532a
Merge branch 'master' into 8306112
May 16, 2023
83f3152
Give subclass priority
May 18, 2023
6b57ee9
Issue warning if traditional main not used.
May 23, 2023
b2e9c4f
Merge branch 'master' into 8306112
May 23, 2023
b55f82f
Fix missing constructor error messages and handle inner class launching
May 23, 2023
e590c73
Allow unqualified access to unnamed class (internally visible)
May 24, 2023
8ccb950
Ignore SKIPs (semicolon class declarations)
May 24, 2023
cfac821
Improving error recovery in presence of less important syntactic erro…
May 25, 2023
4e54c17
Add main tests for inferface/enum/record
May 26, 2023
06aa43e
Remove trailing whitespace
May 26, 2023
a8b3101
Remove mandated flag
May 31, 2023
d0189fc
Merge branch 'master' into 8306112
May 31, 2023
4bfb802
Restrict access to unnamed class members when doing separate compilat…
Jun 1, 2023
850a215
Missing Preview feature enum
Jun 1, 2023
9d111c2
Integrate JDK-8308916 and JDK-8308831 - javax.model
Jun 1, 2023
f1acdb0
Integrating JDK-8308913
Jun 1, 2023
e39e3fe
Merge branch 'master' into 8306112
Jun 1, 2023
f1e875c
Update reflection to follow spec
Jun 2, 2023
0edcd9a
Merge branch 'master' into 8306112
Jun 2, 2023
0d21a30
ClassSymbol#getSimpleName() returning empty name breaks some tools
Jun 2, 2023
97a522f
Use getQualifiedName() to indicate unnamed class
Jun 2, 2023
55b6be5
Update the specification for TypeElement#getQualifiedName and TypeEle…
Jun 2, 2023
c75ebf5
Source code launcher to use getSimpleName() when launching unnamed class
Jun 5, 2023
319795b
Requested clean ups
Jun 5, 2023
94d1eef
Merge branch 'master' into 8306112
Jun 5, 2023
3bbdb8f
Final clean up
Jun 5, 2023
663ca88
Merge branch 'master' into 8306112
Jun 5, 2023
d0fdf69
-Xprint unnamed classes to use e.getSimpleName()
Jun 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions make/CompileInterimLangtools.gmk
Original file line number Diff line number Diff line change
@@ -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), \
61 changes: 59 additions & 2 deletions src/java.base/share/classes/java/lang/Class.java
Original file line number Diff line number Diff line change
@@ -70,8 +70,10 @@
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.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 @@
* 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 @@
* 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 Class<?> getEnclosingClass() throws SecurityException {
/**
* 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 Class<?> getEnclosingClass() throws SecurityException {
* @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 String getTypeName() {
* <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 String getTypeName() {
* @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 @@ private String getCanonicalName0() {
}
}

/**
* {@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
Original file line number Diff line number Diff line change
@@ -72,6 +72,8 @@ public enum Feature {
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.
*/
169 changes: 169 additions & 0 deletions src/java.base/share/classes/jdk/internal/misc/MainMethodFinder.java
Original file line number Diff line number Diff line change
@@ -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 {
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
boolean isTraditionMain = !PreviewFeatures.isEnabled();
if (isTraditionMain) {
return mainClass.getMethod("main", String[].class);
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
}

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);
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
}

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);
}
}
Loading