Skip to content

Commit

Permalink
8306112: Implementation of JEP 445: Unnamed Classes and Instance Main…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
5 people committed Jun 5, 2023
1 parent e970ddb commit 98b53c0
Show file tree
Hide file tree
Showing 40 changed files with 1,653 additions and 175 deletions.
1 change: 1 addition & 0 deletions make/CompileInterimLangtools.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -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), \
Expand Down
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
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 $}".
Expand All @@ -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) {
Expand Down Expand Up @@ -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>
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
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 {
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);
}
}
Loading

1 comment on commit 98b53c0

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.