diff --git a/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java b/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java
index 5e7da4bb6d4..203fde39335 100644
--- a/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java
+++ b/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java
@@ -272,22 +272,6 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer {
// (Done collecting options from props.)
- boolean isClassFile(String name) {
- if (!name.endsWith(".class")) return false;
- for (String prefix = name; ; ) {
- if (passFiles.contains(prefix)) return false;
- int chop = prefix.lastIndexOf('/');
- if (chop < 0) break;
- prefix = prefix.substring(0, chop);
- }
- return true;
- }
-
- boolean isMetaInfFile(String name) {
- return name.startsWith("/" + Utils.METAINF) ||
- name.startsWith(Utils.METAINF);
- }
-
// Get a new package, based on the old one.
private void makeNextPackage() {
pkg.reset();
@@ -332,6 +316,29 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer {
InFile(JarEntry je) {
this(null, je);
}
+ boolean isClassFile() {
+ if (!name.endsWith(".class")) {
+ return false;
+ }
+ for (String prefix = name;;) {
+ if (passFiles.contains(prefix)) {
+ return false;
+ }
+ int chop = prefix.lastIndexOf('/');
+ if (chop < 0) {
+ break;
+ }
+ prefix = prefix.substring(0, chop);
+ }
+ return true;
+ }
+ boolean isMetaInfFile() {
+ return name.startsWith("/" + Utils.METAINF)
+ || name.startsWith(Utils.METAINF);
+ }
+ boolean mustProcess() {
+ return !isMetaInfFile() && isClassFile();
+ }
long getInputLength() {
long len = (je != null)? je.getSize(): f.length();
assert(len >= 0) : this+".len="+len;
@@ -391,7 +398,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer {
Package.File file = null;
// (5078608) : discount the resource files in META-INF
// from segment computation.
- long inflen = (isMetaInfFile(name))
+ long inflen = (inFile.isMetaInfFile())
? 0L
: inFile.getInputLength();
@@ -406,7 +413,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer {
assert(je.isDirectory() == name.endsWith("/"));
- if (isClassFile(name)) {
+ if (inFile.mustProcess()) {
file = readClass(name, bits.getInputStream());
}
if (file == null) {
@@ -429,7 +436,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer {
for (InFile inFile : inFiles) {
String name = inFile.name;
// (5078608) : discount the resource files completely from segmenting
- long inflen = (isMetaInfFile(name))
+ long inflen = (inFile.isMetaInfFile())
? 0L
: inFile.getInputLength() ;
if ((segmentSize += inflen) > segmentLimit) {
@@ -447,7 +454,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer {
if (verbose > 1)
Utils.log.fine("Reading " + name);
Package.File file = null;
- if (isClassFile(name)) {
+ if (inFile.mustProcess()) {
file = readClass(name, strm);
if (file == null) {
strm.close();
diff --git a/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java
index 65f3c497b18..3cd12eeac15 100644
--- a/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java
+++ b/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java
@@ -1584,7 +1584,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
* @param dstBegin the char index, not offset of byte[]
* @param coder the coder of dst[]
*/
- protected void getBytes(byte dst[], int dstBegin, byte coder) {
+ void getBytes(byte dst[], int dstBegin, byte coder) {
if (this.coder == coder) {
System.arraycopy(value, 0, dst, dstBegin << coder, count << coder);
} else { // this.coder == LATIN && coder == UTF16
@@ -1593,7 +1593,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
}
/* for readObject() */
- protected void initBytes(char[] value, int off, int len) {
+ void initBytes(char[] value, int off, int len) {
if (String.COMPACT_STRINGS) {
this.value = StringUTF16.compress(value, off, len);
if (this.value != null) {
diff --git a/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java b/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java
index 36b48b75d39..d014ddb3378 100644
--- a/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java
+++ b/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java
@@ -348,6 +348,19 @@ import java.util.StringTokenizer;
* {@code java.util.spi.LocaleServiceProvider} for more
* information.
*
+ *
+ *
+ *
loggerFinder
+ *
This {@code RuntimePermission} is required to be granted to
+ * classes which subclass or call methods on
+ * {@code java.lang.System.LoggerFinder}. The permission is
+ * checked during invocation of the abstract base class constructor, as
+ * well as on the invocation of its public methods.
+ * This permission ensures trust in classes which provide loggers
+ * to system classes.
+ *
See {@link java.lang.System.LoggerFinder java.lang.System.LoggerFinder}
+ * for more information.
+ *
*
*
* @implNote
diff --git a/jdk/src/java.base/share/classes/java/lang/System.java b/jdk/src/java.base/share/classes/java/lang/System.java
index 44336f9ca2b..9a109ce756e 100644
--- a/jdk/src/java.base/share/classes/java/lang/System.java
+++ b/jdk/src/java.base/share/classes/java/lang/System.java
@@ -30,13 +30,14 @@ import java.lang.annotation.Annotation;
import java.security.AccessControlContext;
import java.util.Properties;
import java.util.PropertyPermission;
-import java.util.StringTokenizer;
import java.util.Map;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import java.security.AllPermission;
import java.nio.channels.Channel;
import java.nio.channels.spi.SelectorProvider;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
import sun.nio.ch.Interruptible;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
@@ -45,6 +46,9 @@ import sun.reflect.annotation.AnnotationType;
import jdk.internal.HotSpotIntrinsicCandidate;
import jdk.internal.misc.JavaLangAccess;;
import jdk.internal.misc.SharedSecrets;;
+import jdk.internal.logger.LoggerFinderLoader;
+import jdk.internal.logger.LazyLoggers;
+import jdk.internal.logger.LocalizedLoggerWrapper;
/**
* The System class contains several useful class fields
@@ -943,6 +947,648 @@ public final class System {
return ProcessEnvironment.getenv();
}
+ /**
+ * {@code System.Logger} instances log messages that will be
+ * routed to the underlying logging framework the {@link System.LoggerFinder
+ * LoggerFinder} uses.
+ *
+ * {@code System.Logger} instances are typically obtained from
+ * the {@link java.lang.System System} class, by calling
+ * {@link java.lang.System#getLogger(java.lang.String) System.getLogger(loggerName)}
+ * or {@link java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle)
+ * System.getLogger(loggerName, bundle)}.
+ *
+ * @see java.lang.System#getLogger(java.lang.String)
+ * @see java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle)
+ * @see java.lang.System.LoggerFinder
+ *
+ * @since 9
+ *
+ */
+ public interface Logger {
+
+ /**
+ * System {@linkplain Logger loggers} levels.
+ *
+ * A level has a {@linkplain #getName() name} and {@linkplain
+ * #getSeverity() severity}.
+ * Level values are {@link #ALL}, {@link #TRACE}, {@link #DEBUG},
+ * {@link #INFO}, {@link #WARNING}, {@link #ERROR}, {@link #OFF},
+ * by order of increasing severity.
+ *
+ * {@link #ALL} and {@link #OFF}
+ * are simple markers with severities mapped respectively to
+ * {@link java.lang.Integer#MIN_VALUE Integer.MIN_VALUE} and
+ * {@link java.lang.Integer#MAX_VALUE Integer.MAX_VALUE}.
+ *
+ * Severity values and Mapping to {@code java.util.logging.Level}.
+ *
+ * {@linkplain System.Logger.Level System logger levels} are mapped to
+ * {@linkplain java.util.logging.Level java.util.logging levels}
+ * of corresponding severity.
+ * The mapping is as follows:
+ *
+ *
+ *
System.Logger Severity Level Mapping
+ *
System.Logger Levels
+ *
{@link Logger.Level#ALL ALL}
+ *
{@link Logger.Level#TRACE TRACE}
+ *
{@link Logger.Level#DEBUG DEBUG}
+ *
{@link Logger.Level#INFO INFO}
+ *
{@link Logger.Level#WARNING WARNING}
+ *
{@link Logger.Level#ERROR ERROR}
+ *
{@link Logger.Level#OFF OFF}
+ *
+ *
java.util.logging Levels
+ *
{@link java.util.logging.Level#ALL ALL}
+ *
{@link java.util.logging.Level#FINER FINER}
+ *
{@link java.util.logging.Level#FINE FINE}
+ *
{@link java.util.logging.Level#INFO INFO}
+ *
{@link java.util.logging.Level#WARNING WARNING}
+ *
{@link java.util.logging.Level#SEVERE SEVERE}
+ *
{@link java.util.logging.Level#OFF OFF}
+ *
+ *
+ *
+ * @since 9
+ *
+ * @see java.lang.System.LoggerFinder
+ * @see java.lang.System.Logger
+ */
+ public enum Level {
+
+ // for convenience, we're reusing java.util.logging.Level int values
+ // the mapping logic in sun.util.logging.PlatformLogger depends
+ // on this.
+ /**
+ * A marker to indicate that all levels are enabled.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@link Integer#MIN_VALUE}.
+ */
+ ALL(Integer.MIN_VALUE), // typically mapped to/from j.u.l.Level.ALL
+ /**
+ * {@code TRACE} level: usually used to log diagnostic information.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@code 400}.
+ */
+ TRACE(400), // typically mapped to/from j.u.l.Level.FINER
+ /**
+ * {@code DEBUG} level: usually used to log debug information traces.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@code 500}.
+ */
+ DEBUG(500), // typically mapped to/from j.u.l.Level.FINEST/FINE/CONFIG
+ /**
+ * {@code INFO} level: usually used to log information messages.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@code 800}.
+ */
+ INFO(800), // typically mapped to/from j.u.l.Level.INFO
+ /**
+ * {@code WARNING} level: usually used to log warning messages.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@code 900}.
+ */
+ WARNING(900), // typically mapped to/from j.u.l.Level.WARNING
+ /**
+ * {@code ERROR} level: usually used to log error messages.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@code 1000}.
+ */
+ ERROR(1000), // typically mapped to/from j.u.l.Level.SEVERE
+ /**
+ * A marker to indicate that all levels are disabled.
+ * This level {@linkplain #getSeverity() severity} is
+ * {@link Integer#MAX_VALUE}.
+ */
+ OFF(Integer.MAX_VALUE); // typically mapped to/from j.u.l.Level.OFF
+
+ private final int severity;
+
+ private Level(int severity) {
+ this.severity = severity;
+ }
+
+ /**
+ * Returns the name of this level.
+ * @return this level {@linkplain #name()}.
+ */
+ public final String getName() {
+ return name();
+ }
+
+ /**
+ * Returns the severity of this level.
+ * A higher severity means a more severe condition.
+ * @return this level severity.
+ */
+ public final int getSeverity() {
+ return severity;
+ }
+ }
+
+ /**
+ * Returns the name of this logger.
+ *
+ * @return the logger name.
+ */
+ public String getName();
+
+ /**
+ * Checks if a message of the given level would be logged by
+ * this logger.
+ *
+ * @param level the log message level.
+ * @return {@code true} if the given log message level is currently
+ * being logged.
+ *
+ * @throws NullPointerException if {@code level} is {@code null}.
+ */
+ public boolean isLoggable(Level level);
+
+ /**
+ * Logs a message.
+ *
+ * @implSpec The default implementation for this method calls
+ * {@code this.log(level, (ResourceBundle)null, msg, (Object[])null);}
+ *
+ * @param level the log message level.
+ * @param msg the string message (or a key in the message catalog, if
+ * this logger is a {@link
+ * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)
+ * localized logger}); can be {@code null}.
+ *
+ * @throws NullPointerException if {@code level} is {@code null}.
+ */
+ public default void log(Level level, String msg) {
+ log(level, (ResourceBundle) null, msg, (Object[]) null);
+ }
+
+ /**
+ * Logs a lazily supplied message.
+ *
+ * If the logger is currently enabled for the given log message level
+ * then a message is logged that is the result produced by the
+ * given supplier function. Otherwise, the supplier is not operated on.
+ *
+ * @implSpec When logging is enabled for the given level, the default
+ * implementation for this method calls
+ * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), (Object[])null);}
+ *
+ * @param level the log message level.
+ * @param msgSupplier a supplier function that produces a message.
+ *
+ * @throws NullPointerException if {@code level} is {@code null},
+ * or {@code msgSupplier} is {@code null}.
+ */
+ public default void log(Level level, Supplier msgSupplier) {
+ Objects.requireNonNull(msgSupplier);
+ if (isLoggable(Objects.requireNonNull(level))) {
+ log(level, (ResourceBundle) null, msgSupplier.get(), (Object[]) null);
+ }
+ }
+
+ /**
+ * Logs a message produced from the given object.
+ *
+ * If the logger is currently enabled for the given log message level then
+ * a message is logged that, by default, is the result produced from
+ * calling toString on the given object.
+ * Otherwise, the object is not operated on.
+ *
+ * @implSpec When logging is enabled for the given level, the default
+ * implementation for this method calls
+ * {@code this.log(level, (ResourceBundle)null, obj.toString(), (Object[])null);}
+ *
+ * @param level the log message level.
+ * @param obj the object to log.
+ *
+ * @throws NullPointerException if {@code level} is {@code null}, or
+ * {@code obj} is {@code null}.
+ */
+ public default void log(Level level, Object obj) {
+ Objects.requireNonNull(obj);
+ if (isLoggable(Objects.requireNonNull(level))) {
+ this.log(level, (ResourceBundle) null, obj.toString(), (Object[]) null);
+ }
+ }
+
+ /**
+ * Logs a message associated with a given throwable.
+ *
+ * @implSpec The default implementation for this method calls
+ * {@code this.log(level, (ResourceBundle)null, msg, thrown);}
+ *
+ * @param level the log message level.
+ * @param msg the string message (or a key in the message catalog, if
+ * this logger is a {@link
+ * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)
+ * localized logger}); can be {@code null}.
+ * @param thrown a {@code Throwable} associated with the log message;
+ * can be {@code null}.
+ *
+ * @throws NullPointerException if {@code level} is {@code null}.
+ */
+ public default void log(Level level, String msg, Throwable thrown) {
+ this.log(level, null, msg, thrown);
+ }
+
+ /**
+ * Logs a lazily supplied message associated with a given throwable.
+ *
+ * If the logger is currently enabled for the given log message level
+ * then a message is logged that is the result produced by the
+ * given supplier function. Otherwise, the supplier is not operated on.
+ *
+ * @implSpec When logging is enabled for the given level, the default
+ * implementation for this method calls
+ * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), thrown);}
+ *
+ * @param level one of the log message level identifiers.
+ * @param msgSupplier a supplier function that produces a message.
+ * @param thrown a {@code Throwable} associated with log message;
+ * can be {@code null}.
+ *
+ * @throws NullPointerException if {@code level} is {@code null}, or
+ * {@code msgSupplier} is {@code null}.
+ */
+ public default void log(Level level, Supplier msgSupplier,
+ Throwable thrown) {
+ Objects.requireNonNull(msgSupplier);
+ if (isLoggable(Objects.requireNonNull(level))) {
+ this.log(level, null, msgSupplier.get(), thrown);
+ }
+ }
+
+ /**
+ * Logs a message with an optional list of parameters.
+ *
+ * @implSpec The default implementation for this method calls
+ * {@code this.log(level, (ResourceBundle)null, format, params);}
+ *
+ * @param level one of the log message level identifiers.
+ * @param format the string message format in {@link
+ * java.text.MessageFormat} format, (or a key in the message
+ * catalog, if this logger is a {@link
+ * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)
+ * localized logger}); can be {@code null}.
+ * @param params an optional list of parameters to the message (may be
+ * none).
+ *
+ * @throws NullPointerException if {@code level} is {@code null}.
+ */
+ public default void log(Level level, String format, Object... params) {
+ this.log(level, null, format, params);
+ }
+
+ /**
+ * Logs a localized message associated with a given throwable.
+ *
+ * If the given resource bundle is non-{@code null}, the {@code msg}
+ * string is localized using the given resource bundle.
+ * Otherwise the {@code msg} string is not localized.
+ *
+ * @param level the log message level.
+ * @param bundle a resource bundle to localize {@code msg}; can be
+ * {@code null}.
+ * @param msg the string message (or a key in the message catalog,
+ * if {@code bundle} is not {@code null}); can be {@code null}.
+ * @param thrown a {@code Throwable} associated with the log message;
+ * can be {@code null}.
+ *
+ * @throws NullPointerException if {@code level} is {@code null}.
+ */
+ public void log(Level level, ResourceBundle bundle, String msg,
+ Throwable thrown);
+
+ /**
+ * Logs a message with resource bundle and an optional list of
+ * parameters.
+ *
+ * If the given resource bundle is non-{@code null}, the {@code format}
+ * string is localized using the given resource bundle.
+ * Otherwise the {@code format} string is not localized.
+ *
+ * @param level the log message level.
+ * @param bundle a resource bundle to localize {@code format}; can be
+ * {@code null}.
+ * @param format the string message format in {@link
+ * java.text.MessageFormat} format, (or a key in the message
+ * catalog if {@code bundle} is not {@code null}); can be {@code null}.
+ * @param params an optional list of parameters to the message (may be
+ * none).
+ *
+ * @throws NullPointerException if {@code level} is {@code null}.
+ */
+ public void log(Level level, ResourceBundle bundle, String format,
+ Object... params);
+
+
+ }
+
+ /**
+ * The {@code LoggerFinder} service is responsible for creating, managing,
+ * and configuring loggers to the underlying framework it uses.
+ *
+ * A logger finder is a concrete implementation of this class that has a
+ * zero-argument constructor and implements the abstract methods defined
+ * by this class.
+ * The loggers returned from a logger finder are capable of routing log
+ * messages to the logging backend this provider supports.
+ * A given invocation of the Java Runtime maintains a single
+ * system-wide LoggerFinder instance that is loaded as follows:
+ *
+ *
First it finds any custom {@code LoggerFinder} provider
+ * using the {@link java.util.ServiceLoader} facility with the
+ * {@linkplain ClassLoader#getSystemClassLoader() system class
+ * loader}.
+ *
If no {@code LoggerFinder} provider is found, the system default
+ * {@code LoggerFinder} implementation will be used.
+ *
+ *
+ * An application can replace the logging backend
+ * even when the java.logging module is present, by simply providing
+ * and declaring an implementation of the {@link LoggerFinder} service.
+ *
+ * Default Implementation
+ *
+ * The system default {@code LoggerFinder} implementation uses
+ * {@code java.util.logging} as the backend framework when the
+ * {@code java.logging} module is present.
+ * It returns a {@linkplain System.Logger logger} instance
+ * that will route log messages to a {@link java.util.logging.Logger
+ * java.util.logging.Logger}. Otherwise, if {@code java.logging} is not
+ * present, the default implementation will return a simple logger
+ * instance that will route log messages of {@code INFO} level and above to
+ * the console ({@code System.err}).
+ *
+ * Logging Configuration
+ *
+ * {@linkplain Logger Logger} instances obtained from the
+ * {@code LoggerFinder} factory methods are not directly configurable by
+ * the application. Configuration is the responsibility of the underlying
+ * logging backend, and usually requires using APIs specific to that backend.
+ *
For the default {@code LoggerFinder} implementation
+ * using {@code java.util.logging} as its backend, refer to
+ * {@link java.util.logging java.util.logging} for logging configuration.
+ * For the default {@code LoggerFinder} implementation returning simple loggers
+ * when the {@code java.logging} module is absent, the configuration
+ * is implementation dependent.
+ *
+ * Usually an application that uses a logging framework will log messages
+ * through a logger facade defined (or supported) by that framework.
+ * Applications that wish to use an external framework should log
+ * through the facade associated with that framework.
+ *
+ * A system class that needs to log messages will typically obtain
+ * a {@link System.Logger} instance to route messages to the logging
+ * framework selected by the application.
+ *
+ * Libraries and classes that only need loggers to produce log messages
+ * should not attempt to configure loggers by themselves, as that
+ * would make them dependent from a specific implementation of the
+ * {@code LoggerFinder} service.
+ *
+ * In addition, when a security manager is present, loggers provided to
+ * system classes should not be directly configurable through the logging
+ * backend without requiring permissions.
+ *
+ * It is the responsibility of the provider of
+ * the concrete {@code LoggerFinder} implementation to ensure that
+ * these loggers are not configured by untrusted code without proper
+ * permission checks, as configuration performed on such loggers usually
+ * affects all applications in the same Java Runtime.
+ *
+ * Message Levels and Mapping to backend levels
+ *
+ * A logger finder is responsible for mapping from a {@code
+ * System.Logger.Level} to a level supported by the logging backend it uses.
+ * The default LoggerFinder using {@code java.util.logging} as the backend
+ * maps {@code System.Logger} levels to
+ * {@linkplain java.util.logging.Level java.util.logging} levels
+ * of corresponding severity - as described in {@link Logger.Level
+ * Logger.Level}.
+ *
+ * @see java.lang.System
+ * @see java.lang.System.Logger
+ *
+ * @since 9
+ */
+ public static abstract class LoggerFinder {
+ /**
+ * The {@code RuntimePermission("loggerFinder")} is
+ * necessary to subclass and instantiate the {@code LoggerFinder} class,
+ * as well as to obtain loggers from an instance of that class.
+ */
+ static final RuntimePermission LOGGERFINDER_PERMISSION =
+ new RuntimePermission("loggerFinder");
+
+ /**
+ * Creates a new instance of {@code LoggerFinder}.
+ *
+ * @implNote It is recommended that a {@code LoggerFinder} service
+ * implementation does not perform any heavy initialization in its
+ * constructor, in order to avoid possible risks of deadlock or class
+ * loading cycles during the instantiation of the service provider.
+ *
+ * @throws SecurityException if a security manager is present and its
+ * {@code checkPermission} method doesn't allow the
+ * {@code RuntimePermission("loggerFinder")}.
+ */
+ protected LoggerFinder() {
+ this(checkPermission());
+ }
+
+ private LoggerFinder(Void unused) {
+ // nothing to do.
+ }
+
+ private static Void checkPermission() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(LOGGERFINDER_PERMISSION);
+ }
+ return null;
+ }
+
+ /**
+ * Returns an instance of {@link Logger Logger}
+ * for the given {@code caller}.
+ *
+ * @param name the name of the logger.
+ * @param caller the class for which the logger is being requested;
+ * can be {@code null}.
+ *
+ * @return a {@link Logger logger} suitable for the given caller's
+ * use.
+ * @throws NullPointerException if {@code name} is {@code null} or
+ * {@code caller} is {@code null}.
+ * @throws SecurityException if a security manager is present and its
+ * {@code checkPermission} method doesn't allow the
+ * {@code RuntimePermission("loggerFinder")}.
+ */
+ public abstract Logger getLogger(String name, /* Module */ Class> caller);
+
+ /**
+ * Returns a localizable instance of {@link Logger Logger}
+ * for the given {@code caller}.
+ * The returned logger will use the provided resource bundle for
+ * message localization.
+ *
+ * @implSpec By default, this method calls {@link
+ * #getLogger(java.lang.String, java.lang.Class)
+ * this.getLogger(name, caller)} to obtain a logger, then wraps that
+ * logger in a {@link Logger} instance where all methods that do not
+ * take a {@link ResourceBundle} as parameter are redirected to one
+ * which does - passing the given {@code bundle} for
+ * localization. So for instance, a call to {@link
+ * Logger#log(Level, String) Logger.log(Level.INFO, msg)}
+ * will end up as a call to {@link
+ * Logger#log(Level, ResourceBundle, String, Object...)
+ * Logger.log(Level.INFO, bundle, msg, (Object[])null)} on the wrapped
+ * logger instance.
+ * Note however that by default, string messages returned by {@link
+ * java.util.function.Supplier Supplier<String>} will not be
+ * localized, as it is assumed that such strings are messages which are
+ * already constructed, rather than keys in a resource bundle.
+ *
+ * An implementation of {@code LoggerFinder} may override this method,
+ * for example, when the underlying logging backend provides its own
+ * mechanism for localizing log messages, then such a
+ * {@code LoggerFinder} would be free to return a logger
+ * that makes direct use of the mechanism provided by the backend.
+ *
+ * @param name the name of the logger.
+ * @param bundle a resource bundle; can be {@code null}.
+ * @param caller the class for which the logger is being requested.
+ * @return an instance of {@link Logger Logger} which will use the
+ * provided resource bundle for message localization.
+ *
+ * @throws NullPointerException if {@code name} is {@code null} or
+ * {@code caller} is {@code null}.
+ * @throws SecurityException if a security manager is present and its
+ * {@code checkPermission} method doesn't allow the
+ * {@code RuntimePermission("loggerFinder")}.
+ */
+ public Logger getLocalizedLogger(String name, ResourceBundle bundle,
+ /* Module */ Class> caller) {
+ return new LocalizedLoggerWrapper<>(getLogger(name, caller), bundle);
+ }
+
+ /**
+ * Returns the {@code LoggerFinder} instance. There is one
+ * single system-wide {@code LoggerFinder} instance in
+ * the Java Runtime. See the class specification of how the
+ * {@link LoggerFinder LoggerFinder} implementation is located and
+ * loaded.
+
+ * @return the {@link LoggerFinder LoggerFinder} instance.
+ * @throws SecurityException if a security manager is present and its
+ * {@code checkPermission} method doesn't allow the
+ * {@code RuntimePermission("loggerFinder")}.
+ */
+ public static LoggerFinder getLoggerFinder() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(LOGGERFINDER_PERMISSION);
+ }
+ return accessProvider();
+ }
+
+
+ private static volatile LoggerFinder service;
+ static LoggerFinder accessProvider() {
+ // We do not need to synchronize: LoggerFinderLoader will
+ // always return the same instance, so if we don't have it,
+ // just fetch it again.
+ if (service == null) {
+ PrivilegedAction pa =
+ () -> LoggerFinderLoader.getLoggerFinder();
+ service = AccessController.doPrivileged(pa, null,
+ LOGGERFINDER_PERMISSION);
+ }
+ return service;
+ }
+
+ }
+
+
+ /**
+ * Returns an instance of {@link Logger Logger} for the caller's
+ * use.
+ *
+ * @implSpec
+ * Instances returned by this method route messages to loggers
+ * obtained by calling {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class)
+ * LoggerFinder.getLogger(name, caller)}.
+ *
+ * @apiNote
+ * This method may defer calling the {@link
+ * LoggerFinder#getLogger(java.lang.String, java.lang.Class)
+ * LoggerFinder.getLogger} method to create an actual logger supplied by
+ * the logging backend, for instance, to allow loggers to be obtained during
+ * the system initialization time.
+ *
+ * @param name the name of the logger.
+ * @return an instance of {@link Logger} that can be used by the calling
+ * class.
+ * @throws NullPointerException if {@code name} is {@code null}.
+ */
+ @CallerSensitive
+ public static Logger getLogger(String name) {
+ Objects.requireNonNull(name);
+ final Class> caller = Reflection.getCallerClass();
+ return LazyLoggers.getLogger(name, caller);
+ }
+
+ /**
+ * Returns a localizable instance of {@link Logger
+ * Logger} for the caller's use.
+ * The returned logger will use the provided resource bundle for message
+ * localization.
+ *
+ * @implSpec
+ * The returned logger will perform message localization as specified
+ * by {@link LoggerFinder#getLocalizedLogger(java.lang.String,
+ * java.util.ResourceBundle, java.lang.Class)
+ * LoggerFinder.getLocalizedLogger(name, bundle, caller}.
+ *
+ * @apiNote
+ * This method is intended to be used after the system is fully initialized.
+ * This method may trigger the immediate loading and initialization
+ * of the {@link LoggerFinder} service, which may cause issues if the
+ * Java Runtime is not ready to initialize the concrete service
+ * implementation yet.
+ * System classes which may be loaded early in the boot sequence and
+ * need to log localized messages should create a logger using
+ * {@link #getLogger(java.lang.String)} and then use the log methods that
+ * take a resource bundle as parameter.
+ *
+ * @param name the name of the logger.
+ * @param bundle a resource bundle.
+ * @return an instance of {@link Logger} which will use the provided
+ * resource bundle for message localization.
+ * @throws NullPointerException if {@code name} is {@code null} or
+ * {@code bundle} is {@code null}.
+ */
+ @CallerSensitive
+ public static Logger getLogger(String name, ResourceBundle bundle) {
+ final ResourceBundle rb = Objects.requireNonNull(bundle);
+ Objects.requireNonNull(name);
+ final Class> caller = Reflection.getCallerClass();
+ final SecurityManager sm = System.getSecurityManager();
+ // We don't use LazyLoggers if a resource bundle is specified.
+ // Bootstrap sensitive classes in the JDK do not use resource bundles
+ // when logging. This could be revisited later, if it needs to.
+ if (sm != null) {
+ return AccessController.doPrivileged((PrivilegedAction)
+ () -> LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller),
+ null,
+ LoggerFinder.LOGGERFINDER_PERMISSION);
+ }
+ return LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller);
+ }
+
/**
* Terminates the currently running Java Virtual Machine. The
* argument serves as a status code; by convention, a nonzero status
diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java
index 1ab16ac5c94..2023bfabfde 100644
--- a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java
@@ -1781,23 +1781,27 @@ class LambdaForm {
NamedFunction idFun;
LambdaForm zeForm;
NamedFunction zeFun;
+
+ // Create the LFs and NamedFunctions. Precompiling LFs to byte code is needed to break circular
+ // bootstrap dependency on this method in case we're interpreting LFs
if (isVoid) {
Name[] idNames = new Name[] { argument(0, L_TYPE) };
idForm = new LambdaForm(idMem.getName(), 1, idNames, VOID_RESULT);
+ idForm.compileToBytecode();
idFun = new NamedFunction(idMem, SimpleMethodHandle.make(idMem.getInvocationType(), idForm));
- assert(zeMem == null);
zeForm = idForm;
zeFun = idFun;
} else {
Name[] idNames = new Name[] { argument(0, L_TYPE), argument(1, type) };
idForm = new LambdaForm(idMem.getName(), 2, idNames, 1);
+ idForm.compileToBytecode();
idFun = new NamedFunction(idMem, SimpleMethodHandle.make(idMem.getInvocationType(), idForm));
- assert(zeMem != null);
Object zeValue = Wrapper.forBasicType(btChar).zero();
Name[] zeNames = new Name[] { argument(0, L_TYPE), new Name(idFun, zeValue) };
zeForm = new LambdaForm(zeMem.getName(), 1, zeNames, 1);
+ zeForm.compileToBytecode();
zeFun = new NamedFunction(zeMem, SimpleMethodHandle.make(zeMem.getInvocationType(), zeForm));
}
diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java
index 67017b6b02a..3be1b7ea7df 100644
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java
@@ -872,13 +872,54 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
* @see #asCollector
*/
public MethodHandle asSpreader(Class> arrayType, int arrayLength) {
- MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength);
- int arity = type().parameterCount();
- int spreadArgPos = arity - arrayLength;
+ return asSpreader(type().parameterCount() - arrayLength, arrayType, arrayLength);
+ }
+
+ /**
+ * Makes an array-spreading method handle, which accepts an array argument at a given position and spreads
+ * its elements as positional arguments in place of the array. The new method handle adapts, as its target,
+ * the current method handle. The type of the adapter will be the same as the type of the target, except that the
+ * {@code arrayLength} parameters of the target's type, starting at the zero-based position {@code spreadArgPos},
+ * are replaced by a single array parameter of type {@code arrayType}.
+ *
+ * This method behaves very much like {@link #asSpreader(Class, int)}, but accepts an additional {@code spreadArgPos}
+ * argument to indicate at which position in the parameter list the spreading should take place.
+ *
+ * @param spreadArgPos the position (zero-based index) in the argument list at which spreading should start.
+ * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments
+ * @param arrayLength the number of arguments to spread from an incoming array argument
+ * @return a new method handle which spreads an array argument at a given position,
+ * before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type,
+ * or if target does not have at least
+ * {@code arrayLength} parameter types,
+ * or if {@code arrayLength} is negative,
+ * or if {@code spreadArgPos} has an illegal value (negative, or together with arrayLength exceeding the
+ * number of arguments),
+ * or if the resulting method handle's type would have
+ * too many parameters
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ *
+ * @see #asSpreader(Class, int)
+ * @since 9
+ */
+ public MethodHandle asSpreader(int spreadArgPos, Class> arrayType, int arrayLength) {
+ MethodType postSpreadType = asSpreaderChecks(arrayType, spreadArgPos, arrayLength);
MethodHandle afterSpread = this.asType(postSpreadType);
BoundMethodHandle mh = afterSpread.rebind();
LambdaForm lform = mh.editor().spreadArgumentsForm(1 + spreadArgPos, arrayType, arrayLength);
- MethodType preSpreadType = postSpreadType.replaceParameterTypes(spreadArgPos, arity, arrayType);
+ MethodType preSpreadType = postSpreadType.replaceParameterTypes(spreadArgPos, spreadArgPos + arrayLength, arrayType);
return mh.copyWith(preSpreadType, lform);
}
@@ -886,15 +927,18 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
* See if {@code asSpreader} can be validly called with the given arguments.
* Return the type of the method handle call after spreading but before conversions.
*/
- private MethodType asSpreaderChecks(Class> arrayType, int arrayLength) {
+ private MethodType asSpreaderChecks(Class> arrayType, int pos, int arrayLength) {
spreadArrayChecks(arrayType, arrayLength);
int nargs = type().parameterCount();
if (nargs < arrayLength || arrayLength < 0)
throw newIllegalArgumentException("bad spread array length");
+ if (pos < 0 || pos + arrayLength > nargs) {
+ throw newIllegalArgumentException("bad spread position");
+ }
Class> arrayElement = arrayType.getComponentType();
MethodType mtype = type();
boolean match = true, fail = false;
- for (int i = nargs - arrayLength; i < nargs; i++) {
+ for (int i = pos; i < arrayLength; i++) {
Class> ptype = mtype.parameterType(i);
if (ptype != arrayElement) {
match = false;
@@ -905,7 +949,7 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
}
}
if (match) return mtype;
- MethodType needType = mtype.asSpreaderType(arrayType, arrayLength);
+ MethodType needType = mtype.asSpreaderType(arrayType, pos, arrayLength);
if (!fail) return needType;
// elicit an error:
this.asType(needType);
@@ -998,10 +1042,53 @@ assertEquals("[123]", (String) longsToString.invokeExact((long)123));
* @see #asVarargsCollector
*/
public MethodHandle asCollector(Class> arrayType, int arrayLength) {
- asCollectorChecks(arrayType, arrayLength);
- int collectArgPos = type().parameterCount() - 1;
+ return asCollector(type().parameterCount() - 1, arrayType, arrayLength);
+ }
+
+ /**
+ * Makes an array-collecting method handle, which accepts a given number of positional arguments starting
+ * at a given position, and collects them into an array argument. The new method handle adapts, as its
+ * target, the current method handle. The type of the adapter will be the same as the type of the target,
+ * except that the parameter at the position indicated by {@code collectArgPos} (usually of type {@code arrayType})
+ * is replaced by {@code arrayLength} parameters whose type is element type of {@code arrayType}.
+ *
+ * This method behaves very much like {@link #asCollector(Class, int)}, but differs in that its {@code
+ * collectArgPos} argument indicates at which position in the parameter list arguments should be collected. This
+ * index is zero-based.
+ *
+ * @param collectArgPos the zero-based position in the parameter list at which to start collecting.
+ * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+ * @param arrayLength the number of arguments to collect into a new array argument
+ * @return a new method handle which collects some arguments
+ * into an array, before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type
+ * or {@code arrayType} is not assignable to this method handle's array parameter type,
+ * or {@code arrayLength} is not a legal array size,
+ * or {@code collectArgPos} has an illegal value (negative, or greater than the number of arguments),
+ * or the resulting method handle's type would have
+ * too many parameters
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ *
+ * @see #asCollector(Class, int)
+ * @since 9
+ */
+ public MethodHandle asCollector(int collectArgPos, Class> arrayType, int arrayLength) {
+ asCollectorChecks(arrayType, collectArgPos, arrayLength);
BoundMethodHandle mh = rebind();
- MethodType resultType = type().asCollectorType(arrayType, arrayLength);
+ MethodType resultType = type().asCollectorType(arrayType, collectArgPos, arrayLength);
MethodHandle newArray = MethodHandleImpl.varargsArray(arrayType, arrayLength);
LambdaForm lform = mh.editor().collectArgumentArrayForm(1 + collectArgPos, newArray);
if (lform != null) {
@@ -1015,15 +1102,18 @@ assertEquals("[123]", (String) longsToString.invokeExact((long)123));
* See if {@code asCollector} can be validly called with the given arguments.
* Return false if the last parameter is not an exact match to arrayType.
*/
- /*non-public*/ boolean asCollectorChecks(Class> arrayType, int arrayLength) {
+ /*non-public*/ boolean asCollectorChecks(Class> arrayType, int pos, int arrayLength) {
spreadArrayChecks(arrayType, arrayLength);
int nargs = type().parameterCount();
- if (nargs != 0) {
- Class> lastParam = type().parameterType(nargs-1);
- if (lastParam == arrayType) return true;
- if (lastParam.isAssignableFrom(arrayType)) return false;
+ if (pos < 0 || pos >= nargs) {
+ throw newIllegalArgumentException("bad collect position");
}
- throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType);
+ if (nargs != 0) {
+ Class> param = type().parameterType(pos);
+ if (param == arrayType) return true;
+ if (param.isAssignableFrom(arrayType)) return false;
+ }
+ throw newIllegalArgumentException("array type not assignable to argument", this, arrayType);
}
/**
@@ -1178,7 +1268,7 @@ assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
*/
public MethodHandle asVarargsCollector(Class> arrayType) {
Objects.requireNonNull(arrayType);
- boolean lastMatch = asCollectorChecks(arrayType, 0);
+ boolean lastMatch = asCollectorChecks(arrayType, type().parameterCount() - 1, 0);
if (isVarargsCollector() && lastMatch)
return this;
return MethodHandleImpl.makeVarargsCollector(this, arrayType);
@@ -1341,7 +1431,6 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
// cannot be cracked into MethodHandleInfo.
assert viewAsTypeChecks(newType, strict);
BoundMethodHandle mh = rebind();
- assert(!((MethodHandle)mh instanceof DirectMethodHandle));
return mh.copyWith(newType, mh.form);
}
diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
index a07896b7752..7ad49cd38aa 100644
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
@@ -27,16 +27,17 @@ package java.lang.invoke;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
import java.util.function.Function;
+import java.util.stream.Collectors;
import sun.invoke.empty.Empty;
import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyType;
import sun.invoke.util.Wrapper;
-import jdk.internal.HotSpotIntrinsicCandidate;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
import static java.lang.invoke.LambdaForm.*;
@@ -1297,7 +1298,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
@Override
public MethodHandle asCollector(Class> arrayType, int arrayLength) {
if (intrinsicName == Intrinsic.IDENTITY) {
- MethodType resultType = type().asCollectorType(arrayType, arrayLength);
+ MethodType resultType = type().asCollectorType(arrayType, type().parameterCount() - 1, arrayLength);
MethodHandle newArray = MethodHandleImpl.varargsArray(arrayType, arrayLength);
return newArray.asType(resultType);
}
@@ -1619,17 +1620,251 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
}
}
+ /**
+ * Assembles a loop method handle from the given handles and type information. This works by binding and configuring
+ * the {@linkplain #looper(MethodHandle[], MethodHandle[], MethodHandle[], MethodHandle[], int, int, Object[]) "most
+ * generic loop"}.
+ *
+ * @param tloop the return type of the loop.
+ * @param targs types of the arguments to be passed to the loop.
+ * @param tvars types of loop-local variables.
+ * @param init sanitized array of initializers for loop-local variables.
+ * @param step sanitited array of loop bodies.
+ * @param pred sanitized array of predicates.
+ * @param fini sanitized array of loop finalizers.
+ *
+ * @return a handle that, when invoked, will execute the loop.
+ */
+ static MethodHandle makeLoop(Class> tloop, List> targs, List> tvars, List init,
+ List step, List pred, List fini) {
+ MethodHandle[] ainit = toArrayArgs(init);
+ MethodHandle[] astep = toArrayArgs(step);
+ MethodHandle[] apred = toArrayArgs(pred);
+ MethodHandle[] afini = toArrayArgs(fini);
+
+ MethodHandle l = getConstantHandle(MH_looper);
+
+ // Bind the statically known arguments.
+ l = MethodHandles.insertArguments(l, 0, ainit, astep, apred, afini, tvars.size(), targs.size());
+
+ // Turn the args array into an argument list.
+ l = l.asCollector(Object[].class, targs.size());
+
+ // Finally, make loop type.
+ MethodType loopType = MethodType.methodType(tloop, targs);
+ l = l.asType(loopType);
+
+ return l;
+ }
+
+ /**
+ * Converts all handles in the {@code hs} array to handles that accept an array of arguments.
+ *
+ * @param hs method handles to be converted.
+ *
+ * @return the {@code hs} array, with all method handles therein converted.
+ */
+ static MethodHandle[] toArrayArgs(List hs) {
+ return hs.stream().map(h -> h.asSpreader(Object[].class, h.type().parameterCount())).toArray(MethodHandle[]::new);
+ }
+
+ /**
+ * This method embodies the most generic loop for use by {@link MethodHandles#loop(MethodHandle[][])}. A handle on
+ * it will be transformed into a handle on a concrete loop instantiation by {@link #makeLoop}.
+ *
+ * @param init loop-local variable initializers.
+ * @param step bodies.
+ * @param pred predicates.
+ * @param fini finalizers.
+ * @param varSize number of loop-local variables.
+ * @param nArgs number of arguments passed to the loop.
+ * @param args arguments to the loop invocation.
+ *
+ * @return the result of executing the loop.
+ */
+ static Object looper(MethodHandle[] init, MethodHandle[] step, MethodHandle[] pred, MethodHandle[] fini,
+ int varSize, int nArgs, Object[] args) throws Throwable {
+ Object[] varsAndArgs = new Object[varSize + nArgs];
+ for (int i = 0, v = 0; i < init.length; ++i) {
+ if (init[i].type().returnType() == void.class) {
+ init[i].invoke(args);
+ } else {
+ varsAndArgs[v++] = init[i].invoke(args);
+ }
+ }
+ System.arraycopy(args, 0, varsAndArgs, varSize, nArgs);
+ final int nSteps = step.length;
+ for (; ; ) {
+ for (int i = 0, v = 0; i < nSteps; ++i) {
+ MethodHandle p = pred[i];
+ MethodHandle s = step[i];
+ MethodHandle f = fini[i];
+ if (s.type().returnType() == void.class) {
+ s.invoke(varsAndArgs);
+ } else {
+ varsAndArgs[v++] = s.invoke(varsAndArgs);
+ }
+ if (!(boolean) p.invoke(varsAndArgs)) {
+ return f.invoke(varsAndArgs);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is bound as the predicate in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle,
+ * MethodHandle) counting loops}.
+ *
+ * @param counter the counter parameter, passed in during loop execution.
+ * @param limit the upper bound of the parameter, statically bound at loop creation time.
+ *
+ * @return whether the counter has reached the limit.
+ */
+ static boolean countedLoopPredicate(int counter, int limit) {
+ return counter <= limit;
+ }
+
+ /**
+ * This method is bound as the step function in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle,
+ * MethodHandle) counting loops} to increment the counter.
+ *
+ * @param counter the loop counter.
+ *
+ * @return the loop counter incremented by 1.
+ */
+ static int countedLoopStep(int counter, int limit) {
+ return counter + 1;
+ }
+
+ /**
+ * This is bound to initialize the loop-local iterator in {@linkplain MethodHandles#iteratedLoop iterating loops}.
+ *
+ * @param it the {@link Iterable} over which the loop iterates.
+ *
+ * @return an {@link Iterator} over the argument's elements.
+ */
+ static Iterator> initIterator(Iterable> it) {
+ return it.iterator();
+ }
+
+ /**
+ * This method is bound as the predicate in {@linkplain MethodHandles#iteratedLoop iterating loops}.
+ *
+ * @param it the iterator to be checked.
+ *
+ * @return {@code true} iff there are more elements to iterate over.
+ */
+ static boolean iteratePredicate(Iterator> it) {
+ return it.hasNext();
+ }
+
+ /**
+ * This method is bound as the step for retrieving the current value from the iterator in {@linkplain
+ * MethodHandles#iteratedLoop iterating loops}.
+ *
+ * @param it the iterator.
+ *
+ * @return the next element from the iterator.
+ */
+ static Object iterateNext(Iterator> it) {
+ return it.next();
+ }
+
+ /**
+ * Makes a {@code try-finally} handle that conforms to the type constraints.
+ *
+ * @param target the target to execute in a {@code try-finally} block.
+ * @param cleanup the cleanup to execute in the {@code finally} block.
+ * @param type the result type of the entire construct.
+ * @param argTypes the types of the arguments.
+ *
+ * @return a handle on the constructed {@code try-finally} block.
+ */
+ static MethodHandle makeTryFinally(MethodHandle target, MethodHandle cleanup, Class> type, List> argTypes) {
+ MethodHandle tf = getConstantHandle(type == void.class ? MH_tryFinallyVoidExec : MH_tryFinallyExec);
+
+ // Bind the statically known arguments.
+ tf = MethodHandles.insertArguments(tf, 0, target, cleanup);
+
+ // Turn the args array into an argument list.
+ tf = tf.asCollector(Object[].class, argTypes.size());
+
+ // Finally, make try-finally type.
+ MethodType tfType = MethodType.methodType(type, argTypes);
+ tf = tf.asType(tfType);
+
+ return tf;
+ }
+
+ /**
+ * A method that will be bound during construction of a {@code try-finally} handle with non-{@code void} return type
+ * by {@link MethodHandles#tryFinally(MethodHandle, MethodHandle)}.
+ *
+ * @param target the handle to wrap in a {@code try-finally} block. This will be bound.
+ * @param cleanup the handle to run in any case before returning. This will be bound.
+ * @param args the arguments to the call. These will remain as the argument list.
+ *
+ * @return whatever the execution of the {@code target} returned (it may have been modified by the execution of
+ * {@code cleanup}).
+ * @throws Throwable in case anything is thrown by the execution of {@code target}, the {@link Throwable} will be
+ * passed to the {@code cleanup} handle, which may decide to throw any exception it sees fit.
+ */
+ static Object tryFinallyExecutor(MethodHandle target, MethodHandle cleanup, Object[] args) throws Throwable {
+ Throwable t = null;
+ Object r = null;
+ try {
+ r = target.invoke(args);
+ } catch (Throwable thrown) {
+ t = thrown;
+ throw t;
+ } finally {
+ r = cleanup.invoke(t, r, args);
+ }
+ return r;
+ }
+
+ /**
+ * A method that will be bound during construction of a {@code try-finally} handle with {@code void} return type by
+ * {@link MethodHandles#tryFinally(MethodHandle, MethodHandle)}.
+ *
+ * @param target the handle to wrap in a {@code try-finally} block. This will be bound.
+ * @param cleanup the handle to run in any case before returning. This will be bound.
+ * @param args the arguments to the call. These will remain as the argument list.
+ *
+ * @throws Throwable in case anything is thrown by the execution of {@code target}, the {@link Throwable} will be
+ * passed to the {@code cleanup} handle, which may decide to throw any exception it sees fit.
+ */
+ static void tryFinallyVoidExecutor(MethodHandle target, MethodHandle cleanup, Object[] args) throws Throwable {
+ Throwable t = null;
+ try {
+ target.invoke(args);
+ } catch (Throwable thrown) {
+ t = thrown;
+ throw t;
+ } finally {
+ cleanup.invoke(t, args);
+ }
+ }
+
// Indexes into constant method handles:
- private static final int
+ static final int
MH_cast = 0,
MH_selectAlternative = 1,
MH_copyAsPrimitiveArray = 2,
MH_fillNewTypedArray = 3,
MH_fillNewArray = 4,
MH_arrayIdentity = 5,
- MH_LIMIT = 6;
+ MH_looper = 6,
+ MH_countedLoopPred = 7,
+ MH_countedLoopStep = 8,
+ MH_iteratePred = 9,
+ MH_initIterator = 10,
+ MH_iterateNext = 11,
+ MH_tryFinallyExec = 12,
+ MH_tryFinallyVoidExec = 13,
+ MH_LIMIT = 14;
- private static MethodHandle getConstantHandle(int idx) {
+ static MethodHandle getConstantHandle(int idx) {
MethodHandle handle = HANDLES[idx];
if (handle != null) {
return handle;
@@ -1672,6 +1907,31 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
return makeIntrinsic(IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "selectAlternative",
MethodType.methodType(MethodHandle.class, boolean.class, MethodHandle.class, MethodHandle.class)),
Intrinsic.SELECT_ALTERNATIVE);
+ case MH_looper:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "looper", MethodType.methodType(Object.class,
+ MethodHandle[].class, MethodHandle[].class, MethodHandle[].class, MethodHandle[].class,
+ int.class, int.class, Object[].class));
+ case MH_countedLoopPred:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "countedLoopPredicate",
+ MethodType.methodType(boolean.class, int.class, int.class));
+ case MH_countedLoopStep:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "countedLoopStep",
+ MethodType.methodType(int.class, int.class, int.class));
+ case MH_iteratePred:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iteratePredicate",
+ MethodType.methodType(boolean.class, Iterator.class));
+ case MH_initIterator:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "initIterator",
+ MethodType.methodType(Iterator.class, Iterable.class));
+ case MH_iterateNext:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iterateNext",
+ MethodType.methodType(Object.class, Iterator.class));
+ case MH_tryFinallyExec:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "tryFinallyExecutor",
+ MethodType.methodType(Object.class, MethodHandle.class, MethodHandle.class, Object[].class));
+ case MH_tryFinallyVoidExec:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "tryFinallyVoidExecutor",
+ MethodType.methodType(void.class, MethodHandle.class, MethodHandle.class, Object[].class));
}
} catch (ReflectiveOperationException ex) {
throw newInternalError(ex);
diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
index 13520e45f1c..8378ab9e1c3 100644
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
@@ -26,10 +26,7 @@
package java.lang.invoke;
import java.lang.reflect.*;
-import java.util.BitSet;
-import java.util.List;
-import java.util.Arrays;
-import java.util.Objects;
+import java.util.*;
import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyAccess;
@@ -39,11 +36,13 @@ import sun.reflect.Reflection;
import sun.reflect.misc.ReflectUtil;
import sun.security.util.SecurityConstants;
import java.lang.invoke.LambdaForm.BasicType;
-import static java.lang.invoke.LambdaForm.BasicType.*;
+
import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* This class consists exclusively of static methods that operate on or return
@@ -176,7 +175,7 @@ public class MethodHandles {
* equivalent of a particular bytecode behavior.
* (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.)
* Here is a summary of the correspondence between these factory methods and
- * the behavior the resulting method handles:
+ * the behavior of the resulting method handles:
*
*
*
lookup expression
@@ -235,6 +234,10 @@ public class MethodHandles {
*
*
* Here, the type {@code C} is the class or interface being searched for a member,
@@ -255,6 +258,10 @@ public class MethodHandles {
* The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand
* for reflective objects corresponding to the given members.
*
+ * The bytecode behavior for a {@code findClass} operation is a load of a constant class,
+ * as if by {@code ldc CONSTANT_Class}.
+ * The behavior is represented, not as a method handle, but directly as a {@code Class} constant.
+ *
* In cases where the given member is of variable arity (i.e., a method or constructor)
* the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}.
* In all other cases, the returned method handle will be of fixed arity.
@@ -423,7 +430,7 @@ public class MethodHandles {
* and the Core Reflection API
* (as found on {@link java.lang.Class Class}).
*
- * If a security manager is present, member lookups are subject to
+ * If a security manager is present, member and class lookups are subject to
* additional checks.
* From one to three calls are made to the security manager.
* Any of these calls can refuse access by throwing a
@@ -433,6 +440,8 @@ public class MethodHandles {
* {@code refc} as the containing class in which the member
* is being sought, and {@code defc} as the class in which the
* member is actually defined.
+ * (If a class or other type is being accessed,
+ * the {@code refc} and {@code defc} values are the class itself.)
* The value {@code lookc} is defined as not present
* if the current lookup object does not have
* private access.
@@ -444,11 +453,16 @@ public class MethodHandles {
* then {@link SecurityManager#checkPackageAccess
* smgr.checkPackageAccess(refcPkg)} is called,
* where {@code refcPkg} is the package of {@code refc}.
- *
Step 2:
+ *
Step 2a:
* If the retrieved member is not public and
* {@code lookc} is not present, then
* {@link SecurityManager#checkPermission smgr.checkPermission}
* with {@code RuntimePermission("accessDeclaredMembers")} is called.
+ *
Step 2b:
+ * If the retrieved class has a {@code null} class loader,
+ * and {@code lookc} is not present, then
+ * {@link SecurityManager#checkPermission smgr.checkPermission}
+ * with {@code RuntimePermission("getClassLoader")} is called.
*
Step 3:
* If the retrieved member is not public,
* and if {@code lookc} is not present,
@@ -458,9 +472,9 @@ public class MethodHandles {
* where {@code defcPkg} is the package of {@code defc}.
*
* Security checks are performed after other access checks have passed.
- * Therefore, the above rules presuppose a member that is public,
+ * Therefore, the above rules presuppose a member or class that is public,
* or else that is being accessed from a lookup class that has
- * rights to access the member.
+ * rights to access the member or class.
*
*
Caller sensitive methods
* A small number of Java methods have a special property called caller sensitivity.
@@ -921,6 +935,49 @@ assertEquals("[x, y, z]", pb.command().toString());
return getDirectConstructor(refc, ctor);
}
+ /**
+ * Looks up a class by name from the lookup context defined by this {@code Lookup} object. The static
+ * initializer of the class is not run.
+ *
+ * @param targetName the fully qualified name of the class to be looked up.
+ * @return the requested class.
+ * @exception SecurityException if a security manager is present and it
+ * refuses access
+ * @throws LinkageError if the linkage fails
+ * @throws ClassNotFoundException if the class does not exist.
+ * @throws IllegalAccessException if the class is not accessible, using the allowed access
+ * modes.
+ * @exception SecurityException if a security manager is present and it
+ * refuses access
+ * @since 9
+ */
+ public Class> findClass(String targetName) throws ClassNotFoundException, IllegalAccessException {
+ Class> targetClass = Class.forName(targetName, false, lookupClass.getClassLoader());
+ return accessClass(targetClass);
+ }
+
+ /**
+ * Determines if a class can be accessed from the lookup context defined by this {@code Lookup} object. The
+ * static initializer of the class is not run.
+ *
+ * @param targetClass the class to be access-checked
+ *
+ * @return the class that has been access-checked
+ *
+ * @throws IllegalAccessException if the class is not accessible from the lookup class, using the allowed access
+ * modes.
+ * @exception SecurityException if a security manager is present and it
+ * refuses access
+ * @since 9
+ */
+ public Class> accessClass(Class> targetClass) throws IllegalAccessException {
+ if (!VerifyAccess.isClassAccessible(targetClass, lookupClass, allowedModes)) {
+ throw new MemberName(targetClass).makeAccessException("access violation", this);
+ }
+ checkSecurityManager(targetClass, null);
+ return targetClass;
+ }
+
/**
* Produces an early-bound method handle for a virtual method.
* It will bypass checks for overriding methods on the receiver,
@@ -995,7 +1052,7 @@ assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
*/
public MethodHandle findSpecial(Class> refc, String name, MethodType type,
Class> specialCaller) throws NoSuchMethodException, IllegalAccessException {
- checkSpecialCaller(specialCaller);
+ checkSpecialCaller(specialCaller, refc);
Lookup specialLookup = this.in(specialCaller);
MemberName method = specialLookup.resolveOrFail(REF_invokeSpecial, refc, name, type);
return specialLookup.getDirectMethod(REF_invokeSpecial, refc, method, findBoundCallerClass(method));
@@ -1224,7 +1281,7 @@ return mh1;
* @throws NullPointerException if any argument is null
*/
public MethodHandle unreflectSpecial(Method m, Class> specialCaller) throws IllegalAccessException {
- checkSpecialCaller(specialCaller);
+ checkSpecialCaller(specialCaller, null);
Lookup specialLookup = this.in(specialCaller);
MemberName method = new MemberName(m, true);
assert(method.isMethod());
@@ -1444,7 +1501,15 @@ return mh1;
ReflectUtil.checkPackageAccess(refc);
}
- // Step 2:
+ if (m == null) { // findClass or accessClass
+ // Step 2b:
+ if (!fullPowerLookup) {
+ smgr.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
+ }
+ return;
+ }
+
+ // Step 2a:
if (m.isPublic()) return;
if (!fullPowerLookup) {
smgr.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
@@ -1557,11 +1622,13 @@ return mh1;
private static final boolean ALLOW_NESTMATE_ACCESS = false;
- private void checkSpecialCaller(Class> specialCaller) throws IllegalAccessException {
+ private void checkSpecialCaller(Class> specialCaller, Class> refc) throws IllegalAccessException {
int allowedModes = this.allowedModes;
if (allowedModes == TRUSTED) return;
if (!hasPrivateAccess()
|| (specialCaller != lookupClass()
+ // ensure non-abstract methods in superinterfaces can be special-invoked
+ && !(refc != null && refc.isInterface() && refc.isAssignableFrom(specialCaller))
&& !(ALLOW_NESTMATE_ACCESS &&
VerifyAccess.isSamePackageMember(specialCaller, lookupClass()))))
throw new MemberName(specialCaller).
@@ -1888,7 +1955,7 @@ return invoker;
MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
if (leadingArgCount < 0 || leadingArgCount > type.parameterCount())
throw newIllegalArgumentException("bad argument count", leadingArgCount);
- type = type.asSpreaderType(Object[].class, type.parameterCount() - leadingArgCount);
+ type = type.asSpreaderType(Object[].class, leadingArgCount, type.parameterCount() - leadingArgCount);
return type.invokers().spreadInvoker(leadingArgCount);
}
@@ -2924,19 +2991,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
*/
public static
MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
- int foldPos = 0;
- MethodType targetType = target.type();
- MethodType combinerType = combiner.type();
- Class> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
- BoundMethodHandle result = target.rebind();
- boolean dropResult = (rtype == void.class);
- // Note: This may cache too many distinct LFs. Consider backing off to varargs code.
- LambdaForm lform = result.editor().foldArgumentsForm(1 + foldPos, dropResult, combinerType.basicType());
- MethodType newType = targetType;
- if (!dropResult)
- newType = newType.dropParameterTypes(foldPos, foldPos + 1);
- result = result.copyWithExtendL(newType, lform, combiner);
- return result;
+ return foldArguments(target, 0, combiner);
}
private static Class> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) {
@@ -2949,7 +3004,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
.equals(targetType.parameterList().subList(afterInsertPos,
afterInsertPos + foldArgs))))
ok = false;
- if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0))
+ if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(foldPos))
ok = false;
if (!ok)
throw misMatchedTypes("target and combiner types", targetType, combinerType);
@@ -3011,7 +3066,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
return MethodHandleImpl.makeGuardWithTest(test, target, fallback);
}
- static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) {
+ static RuntimeException misMatchedTypes(String what, T t1, T t2) {
return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2);
}
@@ -3057,6 +3112,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
* the given exception type, or if the method handle types do
* not match in their return types and their
* corresponding parameters
+ * @see MethodHandles#tryFinally(MethodHandle, MethodHandle)
*/
public static
MethodHandle catchException(MethodHandle target,
@@ -3100,4 +3156,913 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
throw new ClassCastException(exType.getName());
return MethodHandleImpl.throwException(MethodType.methodType(returnType, exType));
}
+
+ /**
+ * Constructs a method handle representing a loop with several loop variables that are updated and checked upon each
+ * iteration. Upon termination of the loop due to one of the predicates, a corresponding finalizer is run and
+ * delivers the loop's result, which is the return value of the resulting handle.
+ *
+ * Intuitively, every loop is formed by one or more "clauses", each specifying a local iteration value and/or a loop
+ * exit. Each iteration of the loop executes each clause in order. A clause can optionally update its iteration
+ * variable; it can also optionally perform a test and conditional loop exit. In order to express this logic in
+ * terms of method handles, each clause will determine four actions:
+ *
Before the loop executes, the initialization of an iteration variable or loop invariant local.
+ *
When a clause executes, an update step for the iteration variable.
+ *
When a clause executes, a predicate execution to test for loop exit.
+ *
If a clause causes a loop exit, a finalizer execution to compute the loop's return value.
+ *
+ *
+ * Some of these clause parts may be omitted according to certain rules, and useful default behavior is provided in
+ * this case. See below for a detailed description.
+ *
+ * Each clause function, with the exception of clause initializers, is able to observe the entire loop state,
+ * because it will be passed all current iteration variable values, as well as all incoming loop
+ * parameters. Most clause functions will not need all of this information, but they will be formally connected as
+ * if by {@link #dropArguments}.
+ *
+ * Given a set of clauses, there is a number of checks and adjustments performed to connect all the parts of the
+ * loop. They are spelled out in detail in the steps below. In these steps, every occurrence of the word "must"
+ * corresponds to a place where {@link IllegalArgumentException} may be thrown if the required constraint is not met
+ * by the inputs to the loop combinator. The term "effectively identical", applied to parameter type lists, means
+ * that they must be identical, or else one list must be a proper prefix of the other.
+ *
+ * Step 0: Determine clause structure.
+ *
The clause array (of type {@code MethodHandle[][]} must be non-{@code null} and contain at least one element.
+ *
The clause array may not contain {@code null}s or sub-arrays longer than four elements.
+ *
Clauses shorter than four elements are treated as if they were padded by {@code null} elements to length
+ * four. Padding takes place by appending elements to the array.
+ *
Clauses with all {@code null}s are disregarded.
+ *
Each clause is treated as a four-tuple of functions, called "init", "step", "pred", and "fini".
+ *
+ *
+ * Step 1A: Determine iteration variables.
+ *
Examine init and step function return types, pairwise, to determine each clause's iteration variable type.
+ *
If both functions are omitted, use {@code void}; else if one is omitted, use the other's return type; else
+ * use the common return type (they must be identical).
+ *
Form the list of return types (in clause order), omitting all occurrences of {@code void}.
+ *
This list of types is called the "common prefix".
+ *
+ *
+ * Step 1B: Determine loop parameters.
+ *
Examine init function parameter lists.
+ *
Omitted init functions are deemed to have {@code null} parameter lists.
+ *
All init function parameter lists must be effectively identical.
+ *
The longest parameter list (which is necessarily unique) is called the "common suffix".
+ *
+ *
+ * Step 1C: Determine loop return type.
+ *
Examine fini function return types, disregarding omitted fini functions.
+ *
If there are no fini functions, use {@code void} as the loop return type.
+ *
Otherwise, use the common return type of the fini functions; they must all be identical.
+ *
+ *
+ * Step 1D: Check other types.
+ *
There must be at least one non-omitted pred function.
+ *
Every non-omitted pred function must have a {@code boolean} return type.
+ *
+ *
+ * (Implementation Note: Steps 1A, 1B, 1C, 1D are logically independent of each other, and may be performed in any
+ * order.)
+ *
+ * Step 2: Determine parameter lists.
+ *
The parameter list for the resulting loop handle will be the "common suffix".
+ *
The parameter list for init functions will be adjusted to the "common suffix". (Note that their parameter
+ * lists are already effectively identical to the common suffix.)
+ *
The parameter list for non-init (step, pred, and fini) functions will be adjusted to the common prefix
+ * followed by the common suffix, called the "common parameter sequence".
+ *
Every non-init, non-omitted function parameter list must be effectively identical to the common parameter
+ * sequence.
+ *
+ *
+ * Step 3: Fill in omitted functions.
+ *
If an init function is omitted, use a {@linkplain #constant constant function} of the appropriate
+ * {@code null}/zero/{@code false}/{@code void} type. (For this purpose, a constant {@code void} is simply a
+ * function which does nothing and returns {@code void}; it can be obtained from another constant function by
+ * {@linkplain MethodHandle#asType type conversion}.)
+ *
If a step function is omitted, use an {@linkplain #identity identity function} of the clause's iteration
+ * variable type; insert dropped argument parameters before the identity function parameter for the non-{@code void}
+ * iteration variables of preceding clauses. (This will turn the loop variable into a local loop invariant.)
+ *
If a pred function is omitted, the corresponding fini function must also be omitted.
+ *
If a pred function is omitted, use a constant {@code true} function. (This will keep the loop going, as far
+ * as this clause is concerned.)
+ *
If a fini function is omitted, use a constant {@code null}/zero/{@code false}/{@code void} function of the
+ * loop return type.
+ *
+ *
+ * Step 4: Fill in missing parameter types.
+ *
At this point, every init function parameter list is effectively identical to the common suffix, but some
+ * lists may be shorter. For every init function with a short parameter list, pad out the end of the list by
+ * {@linkplain #dropArguments dropping arguments}.
+ *
At this point, every non-init function parameter list is effectively identical to the common parameter
+ * sequence, but some lists may be shorter. For every non-init function with a short parameter list, pad out the end
+ * of the list by {@linkplain #dropArguments dropping arguments}.
+ *
+ *
+ * Final observations.
+ *
After these steps, all clauses have been adjusted by supplying omitted functions and arguments.
+ *
All init functions have a common parameter type list, which the final loop handle will also have.
+ *
All fini functions have a common return type, which the final loop handle will also have.
+ *
All non-init functions have a common parameter type list, which is the common parameter sequence, of
+ * (non-{@code void}) iteration variables followed by loop parameters.
+ *
Each pair of init and step functions agrees in their return types.
+ *
Each non-init function will be able to observe the current values of all iteration variables, by means of the
+ * common prefix.
+ *
+ *
+ * Loop execution.
+ *
When the loop is called, the loop input values are saved in locals, to be passed (as the common suffix) to
+ * every clause function. These locals are loop invariant.
+ *
Each init function is executed in clause order (passing the common suffix) and the non-{@code void} values
+ * are saved (as the common prefix) into locals. These locals are loop varying (unless their steps are identity
+ * functions, as noted above).
+ *
All function executions (except init functions) will be passed the common parameter sequence, consisting of
+ * the non-{@code void} iteration values (in clause order) and then the loop inputs (in argument order).
+ *
The step and pred functions are then executed, in clause order (step before pred), until a pred function
+ * returns {@code false}.
+ *
The non-{@code void} result from a step function call is used to update the corresponding loop variable. The
+ * updated value is immediately visible to all subsequent function calls.
+ *
If a pred function returns {@code false}, the corresponding fini function is called, and the resulting value
+ * is returned from the loop as a whole.
+ *
+ *
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the types / values
+ * of loop variables; {@code A}/{@code a}, those of arguments passed to the resulting loop; and {@code R}, the
+ * result types of finalizers as well as of the resulting loop.
+ *
{@code
+ * // iterative implementation of the factorial function as a loop handle
+ * static int one(int k) { return 1; }
+ * int inc(int i, int acc, int k) { return i + 1; }
+ * int mult(int i, int acc, int k) { return i * acc; }
+ * boolean pred(int i, int acc, int k) { return i < k; }
+ * int fin(int i, int acc, int k) { return acc; }
+ * // assume MH_one, MH_inc, MH_mult, MH_pred, and MH_fin are handles to the above methods
+ * // null initializer for counter, should initialize to 0
+ * MethodHandle[] counterClause = new MethodHandle[]{null, MH_inc};
+ * MethodHandle[] accumulatorClause = new MethodHandle[]{MH_one, MH_mult, MH_pred, MH_fin};
+ * MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
+ * assertEquals(120, loop.invoke(5));
+ * }
+ *
+ * @param clauses an array of arrays (4-tuples) of {@link MethodHandle}s adhering to the rules described above.
+ *
+ * @return a method handle embodying the looping behavior as defined by the arguments.
+ *
+ * @throws IllegalArgumentException in case any of the constraints described above is violated.
+ *
+ * @see MethodHandles#whileLoop(MethodHandle, MethodHandle, MethodHandle)
+ * @see MethodHandles#doWhileLoop(MethodHandle, MethodHandle, MethodHandle)
+ * @see MethodHandles#countedLoop(MethodHandle, MethodHandle, MethodHandle)
+ * @see MethodHandles#iteratedLoop(MethodHandle, MethodHandle, MethodHandle)
+ * @since 9
+ */
+ public static MethodHandle loop(MethodHandle[]... clauses) {
+ // Step 0: determine clause structure.
+ checkLoop0(clauses);
+
+ List init = new ArrayList<>();
+ List step = new ArrayList<>();
+ List pred = new ArrayList<>();
+ List fini = new ArrayList<>();
+
+ Stream.of(clauses).filter(c -> Stream.of(c).anyMatch(Objects::nonNull)).forEach(clause -> {
+ init.add(clause[0]); // all clauses have at least length 1
+ step.add(clause.length <= 1 ? null : clause[1]);
+ pred.add(clause.length <= 2 ? null : clause[2]);
+ fini.add(clause.length <= 3 ? null : clause[3]);
+ });
+
+ assert Stream.of(init, step, pred, fini).map(List::size).distinct().count() == 1;
+ final int nclauses = init.size();
+
+ // Step 1A: determine iteration variables.
+ final List> iterationVariableTypes = new ArrayList<>();
+ for (int i = 0; i < nclauses; ++i) {
+ MethodHandle in = init.get(i);
+ MethodHandle st = step.get(i);
+ if (in == null && st == null) {
+ iterationVariableTypes.add(void.class);
+ } else if (in != null && st != null) {
+ checkLoop1a(i, in, st);
+ iterationVariableTypes.add(in.type().returnType());
+ } else {
+ iterationVariableTypes.add(in == null ? st.type().returnType() : in.type().returnType());
+ }
+ }
+ final List> commonPrefix = iterationVariableTypes.stream().filter(t -> t != void.class).
+ collect(Collectors.toList());
+
+ // Step 1B: determine loop parameters.
+ final List> empty = new ArrayList<>();
+ final List> commonSuffix = init.stream().filter(Objects::nonNull).map(MethodHandle::type).
+ map(MethodType::parameterList).reduce((p, q) -> p.size() >= q.size() ? p : q).orElse(empty);
+ checkLoop1b(init, commonSuffix);
+
+ // Step 1C: determine loop return type.
+ // Step 1D: check other types.
+ final Class> loopReturnType = fini.stream().filter(Objects::nonNull).map(MethodHandle::type).
+ map(MethodType::returnType).findFirst().orElse(void.class);
+ checkLoop1cd(pred, fini, loopReturnType);
+
+ // Step 2: determine parameter lists.
+ final List> commonParameterSequence = new ArrayList<>(commonPrefix);
+ commonParameterSequence.addAll(commonSuffix);
+ checkLoop2(step, pred, fini, commonParameterSequence);
+
+ // Step 3: fill in omitted functions.
+ for (int i = 0; i < nclauses; ++i) {
+ Class> t = iterationVariableTypes.get(i);
+ if (init.get(i) == null) {
+ init.set(i, zeroHandle(t));
+ }
+ if (step.get(i) == null) {
+ step.set(i, dropArguments(t == void.class ? zeroHandle(t) : identity(t), 0, commonPrefix.subList(0, i)));
+ }
+ if (pred.get(i) == null) {
+ pred.set(i, constant(boolean.class, true));
+ }
+ if (fini.get(i) == null) {
+ fini.set(i, zeroHandle(t));
+ }
+ }
+
+ // Step 4: fill in missing parameter types.
+ List finit = fillParameterTypes(init, commonSuffix);
+ List fstep = fillParameterTypes(step, commonParameterSequence);
+ List fpred = fillParameterTypes(pred, commonParameterSequence);
+ List ffini = fillParameterTypes(fini, commonParameterSequence);
+
+ assert finit.stream().map(MethodHandle::type).map(MethodType::parameterList).
+ allMatch(pl -> pl.equals(commonSuffix));
+ assert Stream.of(fstep, fpred, ffini).flatMap(List::stream).map(MethodHandle::type).map(MethodType::parameterList).
+ allMatch(pl -> pl.equals(commonParameterSequence));
+
+ return MethodHandleImpl.makeLoop(loopReturnType, commonSuffix, commonPrefix, finit, fstep, fpred, ffini);
+ }
+
+ private static List fillParameterTypes(List hs, final List> targetParams) {
+ return hs.stream().map(h -> {
+ int pc = h.type().parameterCount();
+ int tpsize = targetParams.size();
+ return pc < tpsize ? dropArguments(h, pc, targetParams.subList(pc, tpsize)) : h;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * Constructs a {@code while} loop from an initializer, a body, and a predicate. This is a convenience wrapper for
+ * the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ *
+ * The loop handle's result type is the same as the sole loop variable's, i.e., the result type of {@code init}.
+ * The parameter type list of {@code init} also determines that of the resulting handle. The {@code pred} handle
+ * must have an additional leading parameter of the same type as {@code init}'s result, and so must the {@code
+ * body}. These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][])
+ * generic loop combinator}.
+ *
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
+ * passed to the loop.
+ *
{@code
+ * V init(A);
+ * boolean pred(V, A);
+ * V body(V, A);
+ * V whileLoop(A a) {
+ * V v = init(a);
+ * while (pred(v, a)) {
+ * v = body(v, a);
+ * }
+ * return v;
+ * }
+ * }
+ *
+ * @apiNote Example:
+ *
{@code
+ * // implement the zip function for lists as a loop handle
+ * List initZip(Iterator a, Iterator b) { return new ArrayList<>(); }
+ * boolean zipPred(List zip, Iterator a, Iterator b) { return a.hasNext() && b.hasNext(); }
+ * List zipStep(List zip, Iterator a, Iterator b) {
+ * zip.add(a.next());
+ * zip.add(b.next());
+ * return zip;
+ * }
+ * // assume MH_initZip, MH_zipPred, and MH_zipStep are handles to the above methods
+ * MethodHandle loop = MethodHandles.doWhileLoop(MH_initZip, MH_zipPred, MH_zipStep);
+ * List a = Arrays.asList("a", "b", "c", "d");
+ * List b = Arrays.asList("e", "f", "g", "h");
+ * List zipped = Arrays.asList("a", "e", "b", "f", "c", "g", "d", "h");
+ * assertEquals(zipped, (List) loop.invoke(a.iterator(), b.iterator()));
+ * }
+ *
+ *
+ * @implSpec The implementation of this method is equivalent to:
+ *
+ *
+ * @param init initializer: it should provide the initial value of the loop variable. This controls the loop's
+ * result type. Passing {@code null} or a {@code void} init function will make the loop's result type
+ * {@code void}.
+ * @param pred condition for the loop, which may not be {@code null}.
+ * @param body body of the loop, which may not be {@code null}.
+ *
+ * @return the value of the loop variable as the loop terminates.
+ * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ *
+ * @see MethodHandles#loop(MethodHandle[][])
+ * @since 9
+ */
+ public static MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) {
+ MethodHandle fin = init == null ? zeroHandle(void.class) : identity(init.type().returnType());
+ MethodHandle[] checkExit = {null, null, pred, fin};
+ MethodHandle[] varBody = {init, body};
+ return loop(checkExit, varBody);
+ }
+
+ /**
+ * Constructs a {@code do-while} loop from an initializer, a body, and a predicate. This is a convenience wrapper
+ * for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}.
+ *
+ * The loop handle's result type is the same as the sole loop variable's, i.e., the result type of {@code init}.
+ * The parameter type list of {@code init} also determines that of the resulting handle. The {@code pred} handle
+ * must have an additional leading parameter of the same type as {@code init}'s result, and so must the {@code
+ * body}. These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][])
+ * generic loop combinator}.
+ *
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
+ * passed to the loop.
+ *
{@code
+ * V init(A);
+ * boolean pred(V, A);
+ * V body(V, A);
+ * V doWhileLoop(A a) {
+ * V v = init(a);
+ * do {
+ * v = body(v, a);
+ * } while (pred(v, a));
+ * return v;
+ * }
+ * }
+ *
+ * @apiNote Example:
+ *
{@code
+ * // int i = 0; while (i < limit) { ++i; } return i; => limit
+ * int zero(int limit) { return 0; }
+ * int step(int i, int limit) { return i + 1; }
+ * boolean pred(int i, int limit) { return i < limit; }
+ * // assume MH_zero, MH_step, and MH_pred are handles to the above methods
+ * MethodHandle loop = MethodHandles.doWhileLoop(MH_zero, MH_step, MH_pred);
+ * assertEquals(23, loop.invoke(23));
+ * }
+ *
+ *
+ * @implSpec The implementation of this method is equivalent to:
+ *
+ *
+ *
+ * @param init initializer: it should provide the initial value of the loop variable. This controls the loop's
+ * result type. Passing {@code null} or a {@code void} init function will make the loop's result type
+ * {@code void}.
+ * @param pred condition for the loop, which may not be {@code null}.
+ * @param body body of the loop, which may not be {@code null}.
+ *
+ * @return the value of the loop variable as the loop terminates.
+ * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ *
+ * @see MethodHandles#loop(MethodHandle[][])
+ * @since 9
+ */
+ public static MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) {
+ MethodHandle fin = init == null ? zeroHandle(void.class) : identity(init.type().returnType());
+ MethodHandle[] clause = {init, body, pred, fin};
+ return loop(clause);
+ }
+
+ /**
+ * Constructs a loop that runs a given number of iterations. The loop counter is an {@code int} initialized from the
+ * {@code iterations} handle evaluation result. The counter is passed to the {@code body} function, so that must
+ * accept an initial {@code int} argument. The result of the loop execution is the final value of the additional
+ * local state. This is a convenience wrapper for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop
+ * combinator}.
+ *
+ * The result type and parameter type list of {@code init} determine those of the resulting handle. The {@code
+ * iterations} handle must accept the same parameter types as {@code init} but return an {@code int}. The {@code
+ * body} handle must accept the same parameter types as well, preceded by an {@code int} parameter for the counter,
+ * and a parameter of the same type as {@code init}'s result. These constraints follow directly from those described
+ * for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}.
+ *
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
+ * passed to the loop.
+ *
{@code
+ * int iterations(A);
+ * V init(A);
+ * V body(int, V, A);
+ * V countedLoop(A a) {
+ * int end = iterations(a);
+ * V v = init(a);
+ * for (int i = 0; i < end; ++i) {
+ * v = body(i, v, a);
+ * }
+ * return v;
+ * }
+ * }
+ *
+ * @apiNote Example:
+ *
{@code
+ * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
+ * // => a variation on a well known theme
+ * String start(String arg) { return arg; }
+ * String step(int counter, String v, String arg) { return "na " + v; }
+ * // assume MH_start and MH_step are handles to the two methods above
+ * MethodHandle loop = MethodHandles.countedLoop(13, MH_start, MH_step);
+ * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
+ * }
+ *
+ *
+ * @implSpec The implementation of this method is equivalent to:
+ *
+ *
+ * @param iterations a handle to return the number of iterations this loop should run.
+ * @param init initializer for additional loop state. This determines the loop's result type.
+ * Passing {@code null} or a {@code void} init function will make the loop's result type
+ * {@code void}.
+ * @param body the body of the loop, which must not be {@code null}.
+ * It must accept an initial {@code int} parameter (for the counter), and then any
+ * additional loop-local variable plus loop parameters.
+ *
+ * @return a method handle representing the loop.
+ * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ *
+ * @since 9
+ */
+ public static MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
+ return countedLoop(null, iterations, init, body);
+ }
+
+ /**
+ * Constructs a loop that counts over a range of numbers. The loop counter is an {@code int} that will be
+ * initialized to the {@code int} value returned from the evaluation of the {@code start} handle and run to the
+ * value returned from {@code end} (exclusively) with a step width of 1. The counter value is passed to the {@code
+ * body} function in each iteration; it has to accept an initial {@code int} parameter
+ * for that. The result of the loop execution is the final value of the additional local state
+ * obtained by running {@code init}.
+ * This is a
+ * convenience wrapper for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}.
+ *
+ * The constraints for the {@code init} and {@code body} handles are the same as for {@link
+ * #countedLoop(MethodHandle, MethodHandle, MethodHandle)}. Additionally, the {@code start} and {@code end} handles
+ * must return an {@code int} and accept the same parameters as {@code init}.
+ *
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
+ * passed to the loop.
+ *
{@code
+ * int start(A);
+ * int end(A);
+ * V init(A);
+ * V body(int, V, A);
+ * V countedLoop(A a) {
+ * int s = start(a);
+ * int e = end(a);
+ * V v = init(a);
+ * for (int i = s; i < e; ++i) {
+ * v = body(i, v, a);
+ * }
+ * return v;
+ * }
+ * }
+ *
+ *
+ * @implSpec The implementation of this method is equivalent to:
+ *
{@code
+ * MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
+ * MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, int.class, int.class);
+ * // assume MH_increment and MH_lessThan are handles to x+1 and x
+ *
+ * @param start a handle to return the start value of the loop counter.
+ * If it is {@code null}, a constant zero is assumed.
+ * @param end a non-{@code null} handle to return the end value of the loop counter (the loop will run to {@code end-1}).
+ * @param init initializer for additional loop state. This determines the loop's result type.
+ * Passing {@code null} or a {@code void} init function will make the loop's result type
+ * {@code void}.
+ * @param body the body of the loop, which must not be {@code null}.
+ * It must accept an initial {@code int} parameter (for the counter), and then any
+ * additional loop-local variable plus loop parameters.
+ *
+ * @return a method handle representing the loop.
+ * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ *
+ * @since 9
+ */
+ public static MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
+ MethodHandle returnVar = dropArguments(init == null ? zeroHandle(void.class) : identity(init.type().returnType()),
+ 0, int.class, int.class);
+ MethodHandle[] indexVar = {start, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopStep)};
+ MethodHandle[] loopLimit = {end, null, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopPred), returnVar};
+ MethodHandle[] bodyClause = {init, dropArguments(body, 1, int.class)};
+ return loop(indexVar, loopLimit, bodyClause);
+ }
+
+ /**
+ * Constructs a loop that ranges over the elements produced by an {@code Iterator}.
+ * The iterator will be produced by the evaluation of the {@code iterator} handle.
+ * If this handle is passed as {@code null} the method {@link Iterable#iterator} will be used instead,
+ * and will be applied to a leading argument of the loop handle.
+ * Each value produced by the iterator is passed to the {@code body}, which must accept an initial {@code T} parameter.
+ * The result of the loop execution is the final value of the additional local state
+ * obtained by running {@code init}.
+ *
+ * This is a convenience wrapper for the
+ * {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}, and the constraints imposed on the {@code body}
+ * handle follow directly from those described for the latter.
+ *
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the loop variable as well as the result type of the loop; {@code T}/{@code t}, that of the elements of the
+ * structure the loop iterates over, and {@code A}/{@code a}, that of the argument passed to the loop.
+ *
{@code
+ * Iterator iterator(A); // defaults to Iterable::iterator
+ * V init(A);
+ * V body(T,V,A);
+ * V iteratedLoop(A a) {
+ * Iterator it = iterator(a);
+ * V v = init(a);
+ * for (T t : it) {
+ * v = body(t, v, a);
+ * }
+ * return v;
+ * }
+ * }
+ *
+ * The type {@code T} may be either a primitive or reference.
+ * Since type {@code Iterator} is erased in the method handle representation to the raw type
+ * {@code Iterator}, the {@code iteratedLoop} combinator adjusts the leading argument type for {@code body}
+ * to {@code Object} as if by the {@link MethodHandle#asType asType} conversion method.
+ * Therefore, if an iterator of the wrong type appears as the loop is executed,
+ * runtime exceptions may occur as the result of dynamic conversions performed by {@code asType}.
+ *
+ * @apiNote Example:
+ *
{@code
+ * // reverse a list
+ * List reverseStep(String e, List r) {
+ * r.add(0, e);
+ * return r;
+ * }
+ * List newArrayList() { return new ArrayList<>(); }
+ * // assume MH_reverseStep, MH_newArrayList are handles to the above methods
+ * MethodHandle loop = MethodHandles.iteratedLoop(null, MH_newArrayList, MH_reverseStep);
+ * List list = Arrays.asList("a", "b", "c", "d", "e");
+ * List reversedList = Arrays.asList("e", "d", "c", "b", "a");
+ * assertEquals(reversedList, (List) loop.invoke(list));
+ * }
+ *
+ * @implSpec The implementation of this method is equivalent to:
+ *
+ *
+ * @param iterator a handle to return the iterator to start the loop.
+ * Passing {@code null} will make the loop call {@link Iterable#iterator()} on the first
+ * incoming value.
+ * @param init initializer for additional loop state. This determines the loop's result type.
+ * Passing {@code null} or a {@code void} init function will make the loop's result type
+ * {@code void}.
+ * @param body the body of the loop, which must not be {@code null}.
+ * It must accept an initial {@code T} parameter (for the iterated values), and then any
+ * additional loop-local variable plus loop parameters.
+ *
+ * @return a method handle embodying the iteration loop functionality.
+ * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ *
+ * @since 9
+ */
+ public static MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) {
+ checkIteratedLoop(body);
+
+ MethodHandle initit = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_initIterator);
+ MethodHandle initIterator = iterator == null ?
+ initit.asType(initit.type().changeParameterType(0, body.type().parameterType(init == null ? 1 : 2))) :
+ iterator;
+ Class> itype = initIterator.type().returnType();
+ Class> ttype = body.type().parameterType(0);
+
+ MethodHandle returnVar =
+ dropArguments(init == null ? zeroHandle(void.class) : identity(init.type().returnType()), 0, itype);
+ MethodHandle initnx = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iterateNext);
+ MethodHandle nextVal = initnx.asType(initnx.type().changeReturnType(ttype));
+
+ MethodHandle[] iterVar = {initIterator, null, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iteratePred), returnVar};
+ MethodHandle[] bodyClause = {init, filterArgument(body, 0, nextVal)};
+
+ return loop(iterVar, bodyClause);
+ }
+
+ /**
+ * Makes a method handle that adapts a {@code target} method handle by wrapping it in a {@code try-finally} block.
+ * Another method handle, {@code cleanup}, represents the functionality of the {@code finally} block. Any exception
+ * thrown during the execution of the {@code target} handle will be passed to the {@code cleanup} handle. The
+ * exception will be rethrown, unless {@code cleanup} handle throws an exception first. The
+ * value returned from the {@code cleanup} handle's execution will be the result of the execution of the
+ * {@code try-finally} handle.
+ *
+ * The {@code cleanup} handle will be passed one or two additional leading arguments.
+ * The first is the exception thrown during the
+ * execution of the {@code target} handle, or {@code null} if no exception was thrown.
+ * The second is the result of the execution of the {@code target} handle, or, if it throws an exception,
+ * a {@code null}, zero, or {@code false} value of the required type is supplied as a placeholder.
+ * The second argument is not present if the {@code target} handle has a {@code void} return type.
+ * (Note that, except for argument type conversions, combinators represent {@code void} values in parameter lists
+ * by omitting the corresponding paradoxical arguments, not by inserting {@code null} or zero values.)
+ *
+ * The {@code target} and {@code cleanup} handles' return types must be the same. Their parameter type lists also
+ * must be the same, but the {@code cleanup} handle must accept one or two more leading parameters:
+ *
a {@code Throwable}, which will carry the exception thrown by the {@code target} handle (if any); and
+ *
a parameter of the same type as the return type of both {@code target} and {@code cleanup}, which will carry
+ * the result from the execution of the {@code target} handle.
+ * This parameter is not present if the {@code target} returns {@code void}.
+ *
+ *
+ * The pseudocode for the resulting adapter looks as follows. In the code, {@code V} represents the result type of
+ * the {@code try/finally} construct; {@code A}/{@code a}, the types and values of arguments to the resulting
+ * handle consumed by the cleanup; and {@code B}/{@code b}, those of arguments to the resulting handle discarded by
+ * the cleanup.
+ *
{@code
+ * V target(A..., B...);
+ * V cleanup(Throwable, V, A...);
+ * V adapter(A... a, B... b) {
+ * V result = (zero value for V);
+ * Throwable throwable = null;
+ * try {
+ * result = target(a..., b...);
+ * } catch (Throwable t) {
+ * throwable = t;
+ * throw t;
+ * } finally {
+ * result = cleanup(throwable, result, a...);
+ * }
+ * return result;
+ * }
+ * }
+ *
+ * Note that the saved arguments ({@code a...} in the pseudocode) cannot
+ * be modified by execution of the target, and so are passed unchanged
+ * from the caller to the cleanup, if it is invoked.
+ *
+ * The target and cleanup must return the same type, even if the cleanup
+ * always throws.
+ * To create such a throwing cleanup, compose the cleanup logic
+ * with {@link #throwException throwException},
+ * in order to create a method handle of the correct return type.
+ *
+ * Note that {@code tryFinally} never converts exceptions into normal returns.
+ * In rare cases where exceptions must be converted in that way, first wrap
+ * the target with {@link #catchException(MethodHandle, Class, MethodHandle)}
+ * to capture an outgoing exception, and then wrap with {@code tryFinally}.
+ *
+ * @param target the handle whose execution is to be wrapped in a {@code try} block.
+ * @param cleanup the handle that is invoked in the finally block.
+ *
+ * @return a method handle embodying the {@code try-finally} block composed of the two arguments.
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if {@code cleanup} does not accept
+ * the required leading arguments, or if the method handle types do
+ * not match in their return types and their
+ * corresponding trailing parameters
+ *
+ * @see MethodHandles#catchException(MethodHandle, Class, MethodHandle)
+ * @since 9
+ */
+ public static MethodHandle tryFinally(MethodHandle target, MethodHandle cleanup) {
+ List> targetParamTypes = target.type().parameterList();
+ List> cleanupParamTypes = cleanup.type().parameterList();
+ Class> rtype = target.type().returnType();
+
+ checkTryFinally(target, cleanup);
+
+ // Match parameter lists: if the cleanup has a shorter parameter list than the target, add ignored arguments.
+ int tpSize = targetParamTypes.size();
+ int cpPrefixLength = rtype == void.class ? 1 : 2;
+ int cpSize = cleanupParamTypes.size();
+ MethodHandle aCleanup = cpSize - cpPrefixLength < tpSize ?
+ dropArguments(cleanup, cpSize, targetParamTypes.subList(tpSize - (cpSize - cpPrefixLength), tpSize)) :
+ cleanup;
+
+ MethodHandle aTarget = target.asSpreader(Object[].class, target.type().parameterCount());
+ aCleanup = aCleanup.asSpreader(Object[].class, tpSize);
+
+ return MethodHandleImpl.makeTryFinally(aTarget, aCleanup, rtype, targetParamTypes);
+ }
+
+ /**
+ * Adapts a target method handle by pre-processing some of its arguments, starting at a given position, and then
+ * calling the target with the result of the pre-processing, inserted into the original sequence of arguments just
+ * before the folded arguments.
+ *
+ * This method is closely related to {@link #foldArguments(MethodHandle, MethodHandle)}, but allows to control the
+ * position in the parameter list at which folding takes place. The argument controlling this, {@code pos}, is a
+ * zero-based index. The aforementioned method {@link #foldArguments(MethodHandle, MethodHandle)} assumes position
+ * 0.
+ *
{@code
+ * // there are N arguments in A...
+ * T target(Z..., V, A[N]..., B...);
+ * V combiner(A...);
+ * T adapter(Z... z, A... a, B... b) {
+ * V v = combiner(a...);
+ * return target(z..., v, a..., b...);
+ * }
+ * // and if the combiner has a void return:
+ * T target2(Z..., A[N]..., B...);
+ * void combiner2(A...);
+ * T adapter2(Z... z, A... a, B... b) {
+ * combiner2(a...);
+ * return target2(z..., a..., b...);
+ * }
+ * }
+ *
+ * @param target the method handle to invoke after arguments are combined
+ * @param pos the position at which to start folding and at which to insert the folding result; if this is {@code
+ * 0}, the effect is the same as for {@link #foldArguments(MethodHandle, MethodHandle)}.
+ * @param combiner method handle to call initially on the incoming arguments
+ * @return method handle which incorporates the specified argument folding logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if {@code combiner}'s return type
+ * is non-void and not the same as the argument type at position {@code pos} of
+ * the target signature, or if the {@code N} argument types at position {@code pos}
+ * of the target signature
+ * (skipping one matching the {@code combiner}'s return type)
+ * are not identical with the argument types of {@code combiner}
+ *
+ * @see #foldArguments(MethodHandle, MethodHandle)
+ * @since 9
+ */
+ public static MethodHandle foldArguments(MethodHandle target, int pos, MethodHandle combiner) {
+ MethodType targetType = target.type();
+ MethodType combinerType = combiner.type();
+ Class> rtype = foldArgumentChecks(pos, targetType, combinerType);
+ BoundMethodHandle result = target.rebind();
+ boolean dropResult = rtype == void.class;
+ LambdaForm lform = result.editor().foldArgumentsForm(1 + pos, dropResult, combinerType.basicType());
+ MethodType newType = targetType;
+ if (!dropResult) {
+ newType = newType.dropParameterTypes(pos, pos + 1);
+ }
+ result = result.copyWithExtendL(newType, lform, combiner);
+ return result;
+ }
+
+ /**
+ * Wrap creation of a proper zero handle for a given type.
+ *
+ * @param type the type.
+ *
+ * @return a zero value for the given type.
+ */
+ static MethodHandle zeroHandle(Class> type) {
+ return type.isPrimitive() ? zero(Wrapper.forPrimitiveType(type), type) : zero(Wrapper.OBJECT, type);
+ }
+
+ private static void checkLoop0(MethodHandle[][] clauses) {
+ if (clauses == null || clauses.length == 0) {
+ throw newIllegalArgumentException("null or no clauses passed");
+ }
+ if (Stream.of(clauses).anyMatch(Objects::isNull)) {
+ throw newIllegalArgumentException("null clauses are not allowed");
+ }
+ if (Stream.of(clauses).anyMatch(c -> c.length > 4)) {
+ throw newIllegalArgumentException("All loop clauses must be represented as MethodHandle arrays with at most 4 elements.");
+ }
+ }
+
+ private static void checkLoop1a(int i, MethodHandle in, MethodHandle st) {
+ if (in.type().returnType() != st.type().returnType()) {
+ throw misMatchedTypes("clause " + i + ": init and step return types", in.type().returnType(),
+ st.type().returnType());
+ }
+ }
+
+ private static void checkLoop1b(List init, List> commonSuffix) {
+ if (init.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::parameterList).
+ anyMatch(pl -> !pl.equals(commonSuffix.subList(0, pl.size())))) {
+ throw newIllegalArgumentException("found non-effectively identical init parameter type lists: " + init +
+ " (common suffix: " + commonSuffix + ")");
+ }
+ }
+
+ private static void checkLoop1cd(List pred, List fini, Class> loopReturnType) {
+ if (fini.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType).
+ anyMatch(t -> t != loopReturnType)) {
+ throw newIllegalArgumentException("found non-identical finalizer return types: " + fini + " (return type: " +
+ loopReturnType + ")");
+ }
+
+ if (!pred.stream().filter(Objects::nonNull).findFirst().isPresent()) {
+ throw newIllegalArgumentException("no predicate found", pred);
+ }
+ if (pred.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType).
+ anyMatch(t -> t != boolean.class)) {
+ throw newIllegalArgumentException("predicates must have boolean return type", pred);
+ }
+ }
+
+ private static void checkLoop2(List step, List pred, List fini, List> commonParameterSequence) {
+ if (Stream.of(step, pred, fini).flatMap(List::stream).filter(Objects::nonNull).map(MethodHandle::type).
+ map(MethodType::parameterList).anyMatch(pl -> !pl.equals(commonParameterSequence.subList(0, pl.size())))) {
+ throw newIllegalArgumentException("found non-effectively identical parameter type lists:\nstep: " + step +
+ "\npred: " + pred + "\nfini: " + fini + " (common parameter sequence: " + commonParameterSequence + ")");
+ }
+ }
+
+ private static void checkIteratedLoop(MethodHandle body) {
+ if (null == body) {
+ throw newIllegalArgumentException("iterated loop body must not be null");
+ }
+ }
+
+ private static void checkTryFinally(MethodHandle target, MethodHandle cleanup) {
+ Class> rtype = target.type().returnType();
+ if (rtype != cleanup.type().returnType()) {
+ throw misMatchedTypes("target and return types", cleanup.type().returnType(), rtype);
+ }
+ List> cleanupParamTypes = cleanup.type().parameterList();
+ if (!Throwable.class.isAssignableFrom(cleanupParamTypes.get(0))) {
+ throw misMatchedTypes("cleanup first argument and Throwable", cleanup.type(), Throwable.class);
+ }
+ if (rtype != void.class && cleanupParamTypes.get(1) != rtype) {
+ throw misMatchedTypes("cleanup second argument and target return type", cleanup.type(), rtype);
+ }
+ // The cleanup parameter list (minus the leading Throwable and result parameters) must be a sublist of the
+ // target parameter list.
+ int cleanupArgIndex = rtype == void.class ? 1 : 2;
+ if (!cleanupParamTypes.subList(cleanupArgIndex, cleanupParamTypes.size()).
+ equals(target.type().parameterList().subList(0, cleanupParamTypes.size() - cleanupArgIndex))) {
+ throw misMatchedTypes("cleanup parameters after (Throwable,result) and target parameter list prefix",
+ cleanup.type(), target.type());
+ }
+ }
+
}
diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java
index 7f77c5e84ad..be3090a4451 100644
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java
@@ -469,12 +469,13 @@ class MethodType implements java.io.Serializable {
/** Replace the last arrayLength parameter types with the component type of arrayType.
* @param arrayType any array type
+ * @param pos position at which to spread
* @param arrayLength the number of parameter types to change
* @return the resulting type
*/
- /*non-public*/ MethodType asSpreaderType(Class> arrayType, int arrayLength) {
+ /*non-public*/ MethodType asSpreaderType(Class> arrayType, int pos, int arrayLength) {
assert(parameterCount() >= arrayLength);
- int spreadPos = ptypes.length - arrayLength;
+ int spreadPos = pos;
if (arrayLength == 0) return this; // nothing to change
if (arrayType == Object[].class) {
if (isGeneric()) return this; // nothing to change
@@ -489,10 +490,10 @@ class MethodType implements java.io.Serializable {
}
Class> elemType = arrayType.getComponentType();
assert(elemType != null);
- for (int i = spreadPos; i < ptypes.length; i++) {
+ for (int i = spreadPos; i < spreadPos + arrayLength; i++) {
if (ptypes[i] != elemType) {
Class>[] fixedPtypes = ptypes.clone();
- Arrays.fill(fixedPtypes, i, ptypes.length, elemType);
+ Arrays.fill(fixedPtypes, i, spreadPos + arrayLength, elemType);
return methodType(rtype, fixedPtypes);
}
}
@@ -512,12 +513,14 @@ class MethodType implements java.io.Serializable {
/** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType.
* @param arrayType any array type
+ * @param pos position at which to insert parameters
* @param arrayLength the number of parameter types to insert
* @return the resulting type
*/
- /*non-public*/ MethodType asCollectorType(Class> arrayType, int arrayLength) {
+ /*non-public*/ MethodType asCollectorType(Class> arrayType, int pos, int arrayLength) {
assert(parameterCount() >= 1);
- assert(lastParameterType().isAssignableFrom(arrayType));
+ assert(pos < ptypes.length);
+ assert(ptypes[pos].isAssignableFrom(arrayType));
MethodType res;
if (arrayType == Object[].class) {
res = genericMethodType(arrayLength);
@@ -532,7 +535,11 @@ class MethodType implements java.io.Serializable {
if (ptypes.length == 1) {
return res;
} else {
- return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1));
+ // insert after (if need be), then before
+ if (pos < parameterList().size() - 1) {
+ res = res.insertParameterTypes(arrayLength, parameterList().subList(pos + 1, parameterList().size()));
+ }
+ return res.insertParameterTypes(0, parameterList().subList(0, pos));
}
}
diff --git a/jdk/src/java.base/share/classes/java/util/TreeMap.java b/jdk/src/java.base/share/classes/java/util/TreeMap.java
index a5ed0f5e91c..248ef934369 100644
--- a/jdk/src/java.base/share/classes/java/util/TreeMap.java
+++ b/jdk/src/java.base/share/classes/java/util/TreeMap.java
@@ -2581,19 +2581,17 @@ public class TreeMap
}
/**
- * Find the level down to which to assign all nodes BLACK. This is the
- * last `full' level of the complete binary tree produced by
- * buildTree. The remaining nodes are colored RED. (This makes a `nice'
- * set of color assignments wrt future insertions.) This level number is
+ * Finds the level down to which to assign all nodes BLACK. This is the
+ * last `full' level of the complete binary tree produced by buildTree.
+ * The remaining nodes are colored RED. (This makes a `nice' set of
+ * color assignments wrt future insertions.) This level number is
* computed by finding the number of splits needed to reach the zeroeth
- * node. (The answer is ~lg(N), but in any case must be computed by same
- * quick O(lg(N)) loop.)
+ * node.
+ *
+ * @param size the (non-negative) number of keys in the tree to be built
*/
- private static int computeRedLevel(int sz) {
- int level = 0;
- for (int m = sz - 1; m >= 0; m = m / 2 - 1)
- level++;
- return level;
+ private static int computeRedLevel(int size) {
+ return 31 - Integer.numberOfLeadingZeros(size + 1);
}
/**
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java b/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java
new file mode 100644
index 00000000000..95d35ed4fa2
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * An implementation of {@link System.Logger System.Logger}
+ * that redirects all calls to a wrapped instance of {@link
+ * System.Logger System.Logger}
+ *
+ * @param Type of the wrapped Logger: {@code Logger} or
+ * an extension of that interface.
+ *
+ */
+abstract class AbstractLoggerWrapper
+ implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {
+
+ AbstractLoggerWrapper() { }
+
+ abstract L wrapped();
+
+ abstract PlatformLogger.Bridge platformProxy();
+
+ L getWrapped() {
+ return wrapped();
+ }
+
+ @Override
+ public final String getName() {
+ return wrapped().getName();
+ }
+
+ // -----------------------------------------------------------------
+ // Generic methods taking a Level as parameter
+ // -----------------------------------------------------------------
+
+
+ @Override
+ public boolean isLoggable(Level level) {
+ return wrapped().isLoggable(level);
+ }
+
+ @Override
+ public void log(Level level, String msg) {
+ wrapped().log(level, msg);
+ }
+
+ @Override
+ public void log(Level level,
+ Supplier msgSupplier) {
+ wrapped().log(level, msgSupplier);
+ }
+
+ @Override
+ public void log(Level level, Object obj) {
+ wrapped().log(level, obj);
+ }
+
+ @Override
+ public void log(Level level,
+ String msg, Throwable thrown) {
+ wrapped().log(level, msg, thrown);
+ }
+
+ @Override
+ public void log(Level level, Supplier msgSupplier, Throwable thrown) {
+ wrapped().log(level, msgSupplier, thrown);
+ }
+
+ @Override
+ public void log(Level level,
+ String format, Object... params) {
+ wrapped().log(level, format, params);
+ }
+
+ @Override
+ public void log(Level level, ResourceBundle bundle,
+ String key, Throwable thrown) {
+ wrapped().log(level, bundle, key, thrown);
+ }
+
+ @Override
+ public void log(Level level, ResourceBundle bundle,
+ String format, Object... params) {
+ wrapped().log(level, bundle, format, params);
+ }
+
+ // ---------------------------------------------------------
+ // Methods from PlatformLogger.Bridge
+ // ---------------------------------------------------------
+
+ @Override
+ public boolean isLoggable(PlatformLogger.Level level) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) return isLoggable(level.systemLevel());
+ else return platformProxy.isLoggable(level);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ return platformProxy == null || platformProxy.isEnabled();
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(), msg);
+ } else {
+ platformProxy.log(level, msg);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(), msg, thrown);
+ } else {
+ platformProxy.log(level, msg, thrown);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg, Object... params) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(), msg, params);
+ } else {
+ platformProxy.log(level, msg, params);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, Supplier msgSupplier) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(),msgSupplier);
+ } else {
+ platformProxy.log(level,msgSupplier);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, Throwable thrown,
+ Supplier msgSupplier) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(), msgSupplier, thrown);
+ } else {
+ platformProxy.log(level, thrown, msgSupplier);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ if (sourceClass == null && sourceMethod == null) { // best effort
+ wrapped().log(level.systemLevel(), msg);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ sourceClass = sourceClass == null ? "" : sourceClass;
+ sourceMethod = sourceMethod == null ? "" : sourceMethod;
+ msg = msg == null ? "" : msg;
+ wrapped.log(systemLevel, String.format("[%s %s] %s",
+ sourceClass, sourceMethod, msg));
+ }
+ }
+ } else {
+ platformProxy.logp(level, sourceClass, sourceMethod, msg);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, Supplier msgSupplier) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) { // best effort
+ if (sourceClass == null && sourceMethod == null) {
+ wrapped().log(level.systemLevel(), msgSupplier);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ final String sClass = sourceClass == null ? "" : sourceClass;
+ final String sMethod = sourceMethod == null ? "" : sourceMethod;
+ wrapped.log(systemLevel, () -> String.format("[%s %s] %s",
+ sClass, sMethod, msgSupplier.get()));
+ }
+ }
+ } else {
+ platformProxy.logp(level, sourceClass, sourceMethod, msgSupplier);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg, Object... params) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) { // best effort
+ if (sourceClass == null && sourceMethod == null) {
+ wrapped().log(level.systemLevel(), msg, params);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ sourceClass = sourceClass == null ? "" : sourceClass;
+ sourceMethod = sourceMethod == null ? "" : sourceMethod;
+ msg = msg == null ? "" : msg;
+ wrapped.log(systemLevel, String.format("[%s %s] %s",
+ sourceClass, sourceMethod, msg), params);
+ }
+ }
+ } else {
+ platformProxy.logp(level, sourceClass, sourceMethod, msg, params);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg, Throwable thrown) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) { // best effort
+ if (sourceClass == null && sourceMethod == null) {
+ wrapped().log(level.systemLevel(), msg, thrown);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ sourceClass = sourceClass == null ? "" : sourceClass;
+ sourceMethod = sourceMethod == null ? "" : sourceMethod;
+ msg = msg == null ? "" : msg;
+ wrapped.log(systemLevel, String.format("[%s %s] %s",
+ sourceClass, sourceMethod, msg), thrown);
+ }
+ }
+ } else {
+ platformProxy.logp(level, sourceClass, sourceMethod, msg, thrown);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, Throwable thrown,
+ Supplier msgSupplier) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) { // best effort
+ if (sourceClass == null && sourceMethod == null) {
+ wrapped().log(level.systemLevel(), msgSupplier, thrown);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ final String sClass = sourceClass == null ? "" : sourceClass;
+ final String sMethod = sourceMethod == null ? "" : sourceMethod;
+ wrapped.log(systemLevel, () -> String.format("[%s %s] %s",
+ sClass, sMethod, msgSupplier.get()), thrown);
+ }
+ }
+ } else {
+ platformProxy.logp(level, sourceClass, sourceMethod,
+ thrown, msgSupplier);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, ResourceBundle bundle,
+ String msg, Object... params) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) { // best effort
+ if (bundle != null || sourceClass == null && sourceMethod == null) {
+ wrapped().log(level.systemLevel(), bundle, msg, params);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ sourceClass = sourceClass == null ? "" : sourceClass;
+ sourceMethod = sourceMethod == null ? "" : sourceMethod;
+ msg = msg == null ? "" : msg;
+ wrapped.log(systemLevel, bundle, String.format("[%s %s] %s",
+ sourceClass, sourceMethod, msg), params);
+ }
+ }
+ } else {
+ platformProxy.logrb(level, sourceClass, sourceMethod,
+ bundle, msg, params);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, ResourceBundle bundle, String msg,
+ Throwable thrown) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) { // best effort
+ if (bundle != null || sourceClass == null && sourceMethod == null) {
+ wrapped().log(level.systemLevel(), bundle, msg, thrown);
+ } else {
+ Level systemLevel = level.systemLevel();
+ Logger wrapped = wrapped();
+ if (wrapped.isLoggable(systemLevel)) {
+ sourceClass = sourceClass == null ? "" : sourceClass;
+ sourceMethod = sourceMethod == null ? "" : sourceMethod;
+ msg = msg == null ? "" : msg;
+ wrapped.log(systemLevel, bundle, String.format("[%s %s] %s",
+ sourceClass, sourceMethod, msg), thrown);
+ }
+ }
+ } else {
+ platformProxy.logrb(level, sourceClass, sourceMethod, bundle,
+ msg, thrown);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+ String msg, Throwable thrown) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(), bundle, msg, thrown);
+ } else {
+ platformProxy.logrb(level, bundle, msg, thrown);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+ String msg, Object... params) {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ if (platformProxy == null) {
+ wrapped().log(level.systemLevel(), bundle, msg, params);
+ } else {
+ platformProxy.logrb(level, bundle, msg, params);
+ }
+ }
+
+
+ @Override
+ public LoggerConfiguration getLoggerConfiguration() {
+ final PlatformLogger.Bridge platformProxy = platformProxy();
+ return platformProxy == null ? null
+ : PlatformLogger.ConfigurableBridge
+ .getLoggerConfiguration(platformProxy);
+ }
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java b/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java
new file mode 100644
index 00000000000..6338f871644
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java
@@ -0,0 +1,1074 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.ServiceLoader;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import sun.misc.InnocuousThread;
+import sun.misc.VM;
+import sun.util.logging.PlatformLogger;
+import jdk.internal.logger.LazyLoggers.LazyLoggerAccessor;
+
+/**
+ * The BootstrapLogger class handles all the logic needed by Lazy Loggers
+ * to delay the creation of System.Logger instances until the VM is booted.
+ * By extension - it also contains the logic that will delay the creation
+ * of JUL Loggers until the LogManager is initialized by the application, in
+ * the common case where JUL is the default and there is no custom JUL
+ * configuration.
+ *
+ * A BootstrapLogger instance is both a Logger and a
+ * PlatformLogger.Bridge instance, which will put all Log messages in a queue
+ * until the VM is booted.
+ * Once the VM is booted, it obtain the real System.Logger instance from the
+ * LoggerFinder and flushes the message to the queue.
+ *
+ * There are a few caveat:
+ * - the queue may not be flush until the next message is logged after
+ * the VM is booted
+ * - while the BootstrapLogger is active, the default implementation
+ * for all convenience methods is used
+ * - PlatformLogger.setLevel calls are ignored
+ *
+ *
+ */
+public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
+ PlatformLogger.ConfigurableBridge {
+
+ // We use the BootstrapExecutors class to submit delayed messages
+ // to an independent InnocuousThread which will ensure that
+ // delayed log events will be clearly identified as messages that have
+ // been delayed during the boot sequence.
+ private static class BootstrapExecutors implements ThreadFactory {
+
+ // Maybe that should be made configurable with system properties.
+ static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30;
+
+ // The BootstrapMessageLoggerTask is a Runnable which keeps
+ // a hard ref to the ExecutorService that owns it.
+ // This ensure that the ExecutorService is not gc'ed until the thread
+ // has stopped running.
+ private static class BootstrapMessageLoggerTask implements Runnable {
+ ExecutorService owner;
+ Runnable run;
+ public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) {
+ this.owner = owner;
+ this.run = r;
+ }
+ @Override
+ public void run() {
+ try {
+ run.run();
+ } finally {
+ owner = null; // allow the ExecutorService to be gced.
+ }
+ }
+ }
+
+ private static volatile WeakReference executorRef;
+ private static ExecutorService getExecutor() {
+ WeakReference ref = executorRef;
+ ExecutorService executor = ref == null ? null : ref.get();
+ if (executor != null) return executor;
+ synchronized (BootstrapExecutors.class) {
+ ref = executorRef;
+ executor = ref == null ? null : ref.get();
+ if (executor == null) {
+ executor = new ThreadPoolExecutor(0, 1,
+ KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(), new BootstrapExecutors());
+ }
+ // The executor service will be elligible for gc
+ // KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s)
+ // after the execution of its last pending task.
+ executorRef = new WeakReference<>(executor);
+ return executorRef.get();
+ }
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ ExecutorService owner = getExecutor();
+ Thread thread = AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public Thread run() {
+ Thread t = new InnocuousThread(new BootstrapMessageLoggerTask(owner, r));
+ t.setName("BootstrapMessageLoggerTask-"+t.getName());
+ return t;
+ }
+ }, null, new RuntimePermission("enableContextClassLoaderOverride"));
+ thread.setDaemon(true);
+ return thread;
+ }
+
+ static void submit(Runnable r) {
+ getExecutor().execute(r);
+ }
+
+ // This is used by tests.
+ static void join(Runnable r) {
+ try {
+ getExecutor().submit(r).get();
+ } catch (InterruptedException | ExecutionException ex) {
+ // should not happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // This is used by tests.
+ static void awaitPendingTasks() {
+ WeakReference ref = executorRef;
+ ExecutorService executor = ref == null ? null : ref.get();
+ if (ref == null) {
+ synchronized(BootstrapExecutors.class) {
+ ref = executorRef;
+ executor = ref == null ? null : ref.get();
+ }
+ }
+ if (executor != null) {
+ // since our executor uses a FIFO and has a single thread
+ // then awaiting the execution of its pending tasks can be done
+ // simply by registering a new task and waiting until it
+ // completes. This of course would not work if we were using
+ // several threads, but we don't.
+ join(()->{});
+ }
+ }
+
+ // This is used by tests.
+ static boolean isAlive() {
+ WeakReference ref = executorRef;
+ ExecutorService executor = ref == null ? null : ref.get();
+ if (executor != null) return true;
+ synchronized (BootstrapExecutors.class) {
+ ref = executorRef;
+ executor = ref == null ? null : ref.get();
+ return executor != null;
+ }
+ }
+
+ // The pending log event queue. The first event is the head, and
+ // new events are added at the tail
+ static LogEvent head, tail;
+
+ static void enqueue(LogEvent event) {
+ if (event.next != null) return;
+ synchronized (BootstrapExecutors.class) {
+ if (event.next != null) return;
+ event.next = event;
+ if (tail == null) {
+ head = tail = event;
+ } else {
+ tail.next = event;
+ tail = event;
+ }
+ }
+ }
+
+ static void flush() {
+ LogEvent event;
+ // drain the whole queue
+ synchronized(BootstrapExecutors.class) {
+ event = head;
+ head = tail = null;
+ }
+ while(event != null) {
+ LogEvent.log(event);
+ synchronized(BootstrapExecutors.class) {
+ LogEvent prev = event;
+ event = (event.next == event ? null : event.next);
+ prev.next = null;
+ }
+ }
+ }
+ }
+
+ // The accessor in which this logger is temporarily set.
+ final LazyLoggerAccessor holder;
+
+ BootstrapLogger(LazyLoggerAccessor holder) {
+ this.holder = holder;
+ }
+
+ // Temporary data object storing log events
+ // It would be nice to use a Consumer instead of a LogEvent.
+ // This way we could simply do things like:
+ // push((logger) -> logger.log(level, msg));
+ // Unfortunately, if we come to here it means we are in the bootsraping
+ // phase where using lambdas is not safe yet - so we have to use a
+ // a data object instead...
+ //
+ static final class LogEvent {
+ // only one of these two levels should be non null
+ final Level level;
+ final PlatformLogger.Level platformLevel;
+ final BootstrapLogger bootstrap;
+
+ final ResourceBundle bundle;
+ final String msg;
+ final Throwable thrown;
+ final Object[] params;
+ final Supplier msgSupplier;
+ final String sourceClass;
+ final String sourceMethod;
+ final long timeMillis;
+ final long nanoAdjustment;
+
+ // because logging a message may entail calling toString() on
+ // the parameters etc... we need to store the context of the
+ // caller who logged the message - so that we can reuse it when
+ // we finally log the message.
+ final AccessControlContext acc;
+
+ // The next event in the queue
+ LogEvent next;
+
+ private LogEvent(BootstrapLogger bootstrap, Level level,
+ ResourceBundle bundle, String msg,
+ Throwable thrown, Object[] params) {
+ this.acc = AccessController.getContext();
+ this.timeMillis = System.currentTimeMillis();
+ this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+ this.level = level;
+ this.platformLevel = null;
+ this.bundle = bundle;
+ this.msg = msg;
+ this.msgSupplier = null;
+ this.thrown = thrown;
+ this.params = params;
+ this.sourceClass = null;
+ this.sourceMethod = null;
+ this.bootstrap = bootstrap;
+ }
+
+ private LogEvent(BootstrapLogger bootstrap, Level level,
+ Supplier msgSupplier,
+ Throwable thrown, Object[] params) {
+ this.acc = AccessController.getContext();
+ this.timeMillis = System.currentTimeMillis();
+ this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+ this.level = level;
+ this.platformLevel = null;
+ this.bundle = null;
+ this.msg = null;
+ this.msgSupplier = msgSupplier;
+ this.thrown = thrown;
+ this.params = params;
+ this.sourceClass = null;
+ this.sourceMethod = null;
+ this.bootstrap = bootstrap;
+ }
+
+ private LogEvent(BootstrapLogger bootstrap,
+ PlatformLogger.Level platformLevel,
+ String sourceClass, String sourceMethod,
+ ResourceBundle bundle, String msg,
+ Throwable thrown, Object[] params) {
+ this.acc = AccessController.getContext();
+ this.timeMillis = System.currentTimeMillis();
+ this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+ this.level = null;
+ this.platformLevel = platformLevel;
+ this.bundle = bundle;
+ this.msg = msg;
+ this.msgSupplier = null;
+ this.thrown = thrown;
+ this.params = params;
+ this.sourceClass = sourceClass;
+ this.sourceMethod = sourceMethod;
+ this.bootstrap = bootstrap;
+ }
+
+ private LogEvent(BootstrapLogger bootstrap,
+ PlatformLogger.Level platformLevel,
+ String sourceClass, String sourceMethod,
+ Supplier msgSupplier,
+ Throwable thrown, Object[] params) {
+ this.acc = AccessController.getContext();
+ this.timeMillis = System.currentTimeMillis();
+ this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis);
+ this.level = null;
+ this.platformLevel = platformLevel;
+ this.bundle = null;
+ this.msg = null;
+ this.msgSupplier = msgSupplier;
+ this.thrown = thrown;
+ this.params = params;
+ this.sourceClass = sourceClass;
+ this.sourceMethod = sourceMethod;
+ this.bootstrap = bootstrap;
+ }
+
+ // Log this message in the given logger. Do not call directly.
+ // Use LogEvent.log(LogEvent, logger) instead.
+ private void log(Logger logger) {
+ assert platformLevel == null && level != null;
+ //new Exception("logging delayed message").printStackTrace();
+ if (msgSupplier != null) {
+ if (thrown != null) {
+ logger.log(level, msgSupplier, thrown);
+ } else {
+ logger.log(level, msgSupplier);
+ }
+ } else {
+ // BootstrapLoggers are never localized so we can safely
+ // use the method that takes a ResourceBundle parameter
+ // even when that resource bundle is null.
+ if (thrown != null) {
+ logger.log(level, bundle, msg, thrown);
+ } else {
+ logger.log(level, bundle, msg, params);
+ }
+ }
+ }
+
+ // Log this message in the given logger. Do not call directly.
+ // Use LogEvent.doLog(LogEvent, logger) instead.
+ private void log(PlatformLogger.Bridge logger) {
+ assert platformLevel != null && level == null;
+ if (sourceClass == null) {
+ if (msgSupplier != null) {
+ if (thrown != null) {
+ logger.log(platformLevel, thrown, msgSupplier);
+ } else {
+ logger.log(platformLevel, msgSupplier);
+ }
+ } else {
+ // BootstrapLoggers are never localized so we can safely
+ // use the method that takes a ResourceBundle parameter
+ // even when that resource bundle is null.
+ if (thrown != null) {
+ logger.logrb(platformLevel, bundle, msg, thrown);
+ } else {
+ logger.logrb(platformLevel, bundle, msg, params);
+ }
+ }
+ } else {
+ if (msgSupplier != null) {
+ if (thrown != null) {
+ logger.log(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier);
+ } else {
+ logger.log(platformLevel,sourceClass, sourceMethod, msgSupplier);
+ }
+ } else {
+ // BootstrapLoggers are never localized so we can safely
+ // use the method that takes a ResourceBundle parameter
+ // even when that resource bundle is null.
+ if (thrown != null) {
+ logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown);
+ } else {
+ logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params);
+ }
+ }
+ }
+ }
+
+ // non default methods from Logger interface
+ static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+ ResourceBundle bundle, String key, Throwable thrown) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), bundle, key,
+ thrown, null);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+ ResourceBundle bundle, String format, Object[] params) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), bundle, format,
+ null, params);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+ Supplier msgSupplier, Throwable thrown) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level),
+ Objects.requireNonNull(msgSupplier), thrown, null);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, Level level,
+ Supplier msgSupplier) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level),
+ Objects.requireNonNull(msgSupplier), null, null);
+ }
+ static void log(LogEvent log, Logger logger) {
+ final SecurityManager sm = System.getSecurityManager();
+ // not sure we can actually use lambda here. We may need to create
+ // an anonymous class. Although if we reach here, then it means
+ // the VM is booted.
+ if (sm == null || log.acc == null) {
+ BootstrapExecutors.submit(() -> log.log(logger));
+ } else {
+ BootstrapExecutors.submit(() ->
+ AccessController.doPrivileged((PrivilegedAction) () -> {
+ log.log(logger); return null;
+ }, log.acc));
+ }
+ }
+
+ // non default methods from PlatformLogger.Bridge interface
+ static LogEvent valueOf(BootstrapLogger bootstrap,
+ PlatformLogger.Level level, String msg) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), null, null, null,
+ msg, null, null);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ String msg, Throwable thrown) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), null, null, null, msg, thrown, null);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ String msg, Object[] params) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), null, null, null, msg, null, params);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ Supplier msgSupplier) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), null, null, msgSupplier, null, null);
+ }
+ static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ Supplier msgSupplier,
+ Throwable thrown) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), null, null,
+ msgSupplier, thrown, null);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ String sourceClass, String sourceMethod,
+ ResourceBundle bundle, String msg, Object[] params) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), sourceClass,
+ sourceMethod, bundle, msg, null, params);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ String sourceClass, String sourceMethod,
+ ResourceBundle bundle, String msg, Throwable thrown) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), sourceClass,
+ sourceMethod, bundle, msg, thrown, null);
+ }
+ static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+ String sourceClass, String sourceMethod,
+ Supplier msgSupplier, Throwable thrown) {
+ return new LogEvent(Objects.requireNonNull(bootstrap),
+ Objects.requireNonNull(level), sourceClass,
+ sourceMethod, msgSupplier, thrown, null);
+ }
+ static void log(LogEvent log, PlatformLogger.Bridge logger) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null || log.acc == null) {
+ log.log(logger);
+ } else {
+ // not sure we can actually use lambda here. We may need to create
+ // an anonymous class. Although if we reach here, then it means
+ // the VM is booted.
+ AccessController.doPrivileged((PrivilegedAction) () -> {
+ log.log(logger); return null;
+ }, log.acc);
+ }
+ }
+
+ static void log(LogEvent event) {
+ event.bootstrap.flush(event);
+ }
+
+ }
+
+ // Push a log event at the end of the pending LogEvent queue.
+ void push(LogEvent log) {
+ BootstrapExecutors.enqueue(log);
+ // if the queue has been flushed just before we entered
+ // the synchronized block we need to flush it again.
+ checkBootstrapping();
+ }
+
+ // Flushes the queue of pending LogEvents to the logger.
+ void flush(LogEvent event) {
+ assert event.bootstrap == this;
+ if (event.platformLevel != null) {
+ PlatformLogger.Bridge concrete = holder.getConcretePlatformLogger(this);
+ LogEvent.log(event, concrete);
+ } else {
+ Logger concrete = holder.getConcreteLogger(this);
+ LogEvent.log(event, concrete);
+ }
+ }
+
+ /**
+ * The name of this logger. This is the name of the actual logger for which
+ * this logger acts as a temporary proxy.
+ * @return The logger name.
+ */
+ @Override
+ public String getName() {
+ return holder.name;
+ }
+
+ /**
+ * Check whether the VM is still bootstrapping, and if not, arranges
+ * for this logger's holder to create the real logger and flush the
+ * pending event queue.
+ * @return true if the VM is still bootstrapping.
+ */
+ boolean checkBootstrapping() {
+ if (isBooted()) {
+ BootstrapExecutors.flush();
+ return false;
+ }
+ return true;
+ }
+
+ // ----------------------------------
+ // Methods from Logger
+ // ----------------------------------
+
+ @Override
+ public boolean isLoggable(Level level) {
+ if (checkBootstrapping()) {
+ return level.getSeverity() >= Level.INFO.getSeverity();
+ } else {
+ final Logger spi = holder.wrapped();
+ return spi.isLoggable(level);
+ }
+ }
+
+ @Override
+ public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, bundle, key, thrown));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, bundle, key, thrown);
+ }
+ }
+
+ @Override
+ public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, bundle, format, params));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, bundle, format, params);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, null, msg, thrown));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, msg, thrown);
+ }
+ }
+
+ @Override
+ public void log(Level level, String format, Object... params) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, null, format, params));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, format, params);
+ }
+ }
+
+ @Override
+ public void log(Level level, Supplier msgSupplier) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, msgSupplier));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, msgSupplier);
+ }
+ }
+
+ @Override
+ public void log(Level level, Object obj) {
+ if (checkBootstrapping()) {
+ Logger.super.log(level, obj);
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, obj);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, null, msg, (Object[])null));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, msg);
+ }
+ }
+
+ @Override
+ public void log(Level level, Supplier msgSupplier, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, msgSupplier, thrown));
+ } else {
+ final Logger spi = holder.wrapped();
+ spi.log(level, msgSupplier, thrown);
+ }
+ }
+
+ // ----------------------------------
+ // Methods from PlatformLogger.Bridge
+ // ----------------------------------
+
+ @Override
+ public boolean isLoggable(PlatformLogger.Level level) {
+ if (checkBootstrapping()) {
+ return level.intValue() >= PlatformLogger.Level.INFO.intValue();
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ return spi.isLoggable(level);
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ if (checkBootstrapping()) {
+ return true;
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ return spi.isEnabled();
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, msg));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.log(level, msg);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, msg, thrown));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.log(level, msg, thrown);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg, Object... params) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, msg, params));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.log(level, msg, params);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, Supplier msgSupplier) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, msgSupplier));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.log(level, msgSupplier);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, Throwable thrown,
+ Supplier msgSupplier) {
+ if (checkBootstrapping()) {
+ push(LogEvent.vaueOf(this, level, msgSupplier, thrown));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.log(level, thrown, msgSupplier);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null,
+ msg, (Object[])null));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logp(level, sourceClass, sourceMethod, msg);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, Supplier msgSupplier) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, null));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logp(level, sourceClass, sourceMethod, msgSupplier);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg, Object... params) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, params));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logp(level, sourceClass, sourceMethod, msg, params);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, thrown));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logp(level, sourceClass, sourceMethod, msg, thrown);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, Throwable thrown, Supplier msgSupplier) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, thrown));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, ResourceBundle bundle, String msg, Object... params) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, params));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, thrown));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+ String msg, Object... params) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, null, null, bundle, msg, params));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logrb(level, bundle, msg, params);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+ if (checkBootstrapping()) {
+ push(LogEvent.valueOf(this, level, null, null, bundle, msg, thrown));
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ spi.logrb(level, bundle, msg, thrown);
+ }
+ }
+
+ @Override
+ public LoggerConfiguration getLoggerConfiguration() {
+ if (checkBootstrapping()) {
+ // This practically means that PlatformLogger.setLevel()
+ // calls will be ignored if the VM is still bootstrapping. We could
+ // attempt to fix that but is it worth it?
+ return PlatformLogger.ConfigurableBridge.super.getLoggerConfiguration();
+ } else {
+ final PlatformLogger.Bridge spi = holder.platform();
+ return PlatformLogger.ConfigurableBridge.getLoggerConfiguration(spi);
+ }
+ }
+
+ // This BooleanSupplier is a hook for tests - so that we can simulate
+ // what would happen before the VM is booted.
+ private static volatile BooleanSupplier isBooted;
+ public static boolean isBooted() {
+ if (isBooted != null) return isBooted.getAsBoolean();
+ else return VM.isBooted();
+ }
+
+ // A bit of black magic. We try to find out the nature of the logging
+ // backend without actually loading it.
+ private static enum LoggingBackend {
+ // There is no LoggerFinder and JUL is not present
+ NONE(true),
+
+ // There is no LoggerFinder, but we have found a
+ // JdkLoggerFinder installed (which means JUL is present),
+ // and we haven't found any custom configuration for JUL.
+ // Until LogManager is initialized we can use a simple console
+ // logger.
+ JUL_DEFAULT(false),
+
+ // Same as above, except that we have found a custom configuration
+ // for JUL. We cannot use the simple console logger in this case.
+ JUL_WITH_CONFIG(true),
+
+ // We have found a custom LoggerFinder.
+ CUSTOM(true);
+
+ final boolean useLoggerFinder;
+ private LoggingBackend(boolean useLoggerFinder) {
+ this.useLoggerFinder = useLoggerFinder;
+ }
+ };
+
+ // The purpose of this class is to delay the initialization of
+ // the detectedBackend field until it is actually read.
+ // We do not want this field to get initialized if VM.isBooted() is false.
+ private static final class DetectBackend {
+ static final LoggingBackend detectedBackend;
+ static {
+ detectedBackend = AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public LoggingBackend run() {
+ final Iterator iterator =
+ ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader())
+ .iterator();
+ if (iterator.hasNext()) {
+ return LoggingBackend.CUSTOM; // Custom Logger Provider is registered
+ }
+ // No custom logger provider: we will be using the default
+ // backend.
+ final Iterator iterator2 =
+ ServiceLoader.loadInstalled(DefaultLoggerFinder.class)
+ .iterator();
+ if (iterator2.hasNext()) {
+ // LoggingProviderImpl is registered. The default
+ // implementation is java.util.logging
+ String cname = System.getProperty("java.util.logging.config.class");
+ String fname = System.getProperty("java.util.logging.config.file");
+ return (cname != null || fname != null)
+ ? LoggingBackend.JUL_WITH_CONFIG
+ : LoggingBackend.JUL_DEFAULT;
+ } else {
+ // SimpleLogger is used
+ return LoggingBackend.NONE;
+ }
+ }
+ });
+
+ }
+ }
+
+ // We will use temporary SimpleConsoleLoggers if
+ // the logging backend is JUL, there is no custom config,
+ // and the LogManager has not been initialized yet.
+ private static boolean useTemporaryLoggers() {
+ // being paranoid: this should already have been checked
+ if (!isBooted()) return true;
+ return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT
+ && !logManagerConfigured;
+ }
+
+ // We will use lazy loggers if:
+ // - the VM is not yet booted
+ // - the logging backend is a custom backend
+ // - the logging backend is JUL, there is no custom config,
+ // and the LogManager has not been initialized yet.
+ public static synchronized boolean useLazyLoggers() {
+ return !BootstrapLogger.isBooted()
+ || DetectBackend.detectedBackend == LoggingBackend.CUSTOM
+ || useTemporaryLoggers();
+ }
+
+ // Called by LazyLoggerAccessor. This method will determine whether
+ // to create a BootstrapLogger (if the VM is not yet booted),
+ // a SimpleConsoleLogger (if JUL is the default backend and there
+ // is no custom JUL configuration and LogManager is not yet initialized),
+ // or a logger returned by the loaded LoggerFinder (all other cases).
+ static Logger getLogger(LazyLoggerAccessor accessor) {
+ if (!BootstrapLogger.isBooted()) {
+ return new BootstrapLogger(accessor);
+ } else {
+ boolean temporary = useTemporaryLoggers();
+ if (temporary) {
+ // JUL is the default backend, there is no custom configuration,
+ // LogManager has not been used.
+ synchronized(BootstrapLogger.class) {
+ if (useTemporaryLoggers()) {
+ return makeTemporaryLogger(accessor);
+ }
+ }
+ }
+ // Already booted. Return the real logger.
+ return accessor.createLogger();
+ }
+ }
+
+
+ // If the backend is JUL, and there is no custom configuration, and
+ // nobody has attempted to call LogManager.getLogManager() yet, then
+ // we can temporarily substitute JUL Logger with SimpleConsoleLoggers,
+ // which avoids the cost of actually loading up the LogManager...
+ // The TemporaryLoggers class has the logic to create such temporary
+ // loggers, and to possibly replace them with real JUL loggers if
+ // someone calls LogManager.getLogManager().
+ static final class TemporaryLoggers implements
+ Function {
+
+ // all accesses must be synchronized on the outer BootstrapLogger.class
+ final Map temporaryLoggers =
+ new HashMap<>();
+
+ // all accesses must be synchronized on the outer BootstrapLogger.class
+ // The temporaryLoggers map will be cleared when LogManager is initialized.
+ boolean cleared;
+
+ @Override
+ // all accesses must be synchronized on the outer BootstrapLogger.class
+ public SimpleConsoleLogger apply(LazyLoggerAccessor t) {
+ if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+ return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true);
+ }
+
+ // all accesses must be synchronized on the outer BootstrapLogger.class
+ SimpleConsoleLogger get(LazyLoggerAccessor a) {
+ if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+ return temporaryLoggers.computeIfAbsent(a, this);
+ }
+
+ // all accesses must be synchronized on the outer BootstrapLogger.class
+ Map drainTemporaryLoggers() {
+ if (temporaryLoggers.isEmpty()) return null;
+ if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+ final Map accessors = new HashMap<>(temporaryLoggers);
+ temporaryLoggers.clear();
+ cleared = true;
+ return accessors;
+ }
+
+ static void resetTemporaryLoggers(Map accessors) {
+ // When the backend is JUL we want to force the creation of
+ // JUL loggers here: some tests are expecting that the
+ // PlatformLogger will create JUL loggers as soon as the
+ // LogManager is initialized.
+ //
+ // If the backend is not JUL then we can delay the re-creation
+ // of the wrapped logger until they are next accessed.
+ //
+ final LoggingBackend detectedBackend = DetectBackend.detectedBackend;
+ final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT
+ && detectedBackend != LoggingBackend.JUL_WITH_CONFIG;
+ for (Map.Entry a : accessors.entrySet()) {
+ a.getKey().release(a.getValue(), !lazy);
+ }
+ }
+
+ // all accesses must be synchronized on the outer BootstrapLogger.class
+ static final TemporaryLoggers INSTANCE = new TemporaryLoggers();
+ }
+
+ static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) {
+ // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
+ return TemporaryLoggers.INSTANCE.get(a);
+ }
+
+ private static volatile boolean logManagerConfigured;
+
+ private static synchronized Map
+ releaseTemporaryLoggers() {
+ // first check whether there's a chance that we have used
+ // temporary loggers; Will be false if logManagerConfigured is already
+ // true.
+ final boolean clearTemporaryLoggers = useTemporaryLoggers();
+
+ // then sets the flag that tells that the log manager is configured
+ logManagerConfigured = true;
+
+ // finally replace all temporary loggers by real JUL loggers
+ if (clearTemporaryLoggers) {
+ // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class
+ return TemporaryLoggers.INSTANCE.drainTemporaryLoggers();
+ } else {
+ return null;
+ }
+ }
+
+ public static void redirectTemporaryLoggers() {
+ // This call is synchronized on BootstrapLogger.class.
+ final Map accessors =
+ releaseTemporaryLoggers();
+
+ // We will now reset the logger accessors, triggering the
+ // (possibly lazy) replacement of any temporary logger by the
+ // real logger returned from the loaded LoggerFinder.
+ if (accessors != null) {
+ TemporaryLoggers.resetTemporaryLoggers(accessors);
+ }
+
+ BootstrapExecutors.flush();
+ }
+
+ // Hook for tests which need to wait until pending messages
+ // are processed.
+ static void awaitPendingTasks() {
+ BootstrapExecutors.awaitPendingTasks();
+ }
+ static boolean isAlive() {
+ return BootstrapExecutors.isAlive();
+ }
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java b/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java
new file mode 100644
index 00000000000..da255a3e2b8
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.ref.ReferenceQueue;
+import java.util.Collection;
+import java.util.ResourceBundle;
+
+/**
+ * Internal Service Provider Interface (SPI) that makes it possible to use
+ * {@code java.util.logging} as backend when the {@link
+ * sun.util.logging.internal.LoggingProviderImpl
+ * sun.util.logging.internal.LoggingProviderImpl} is present.
+ *
+ * The JDK default implementation of the {@link LoggerFinder} will
+ * attempt to locate and load an {@linkplain
+ * java.util.ServiceLoader#loadInstalled(java.lang.Class) installed}
+ * implementation of the {@code DefaultLoggerFinder}. If {@code java.util.logging}
+ * is present, this will usually resolve to an instance of {@link
+ * sun.util.logging.internal.LoggingProviderImpl sun.util.logging.internal.LoggingProviderImpl}.
+ * Otherwise, if no concrete service provider is declared for
+ * {@code DefaultLoggerFinder}, the default implementation provided by this class
+ * will be used.
+ *
+ * When the {@link sun.util.logging.internal.LoggingProviderImpl
+ * sun.util.logging.internal.LoggingProviderImpl} is not present then the
+ * default implementation provided by this class is to use a simple logger
+ * that will log messages whose level is INFO and above to the console.
+ * These simple loggers are not configurable.
+ *
+ * When configuration is needed, an application should either link with
+ * {@code java.util.logging} - and use the {@code java.util.logging} for
+ * configuration, or link with {@link LoggerFinder another implementation}
+ * of the {@link LoggerFinder}
+ * that provides the necessary configuration.
+ *
+ * @apiNote Programmers are not expected to call this class directly.
+ * Instead they should rely on the static methods defined by {@link
+ * java.lang.System java.lang.System} or {@link sun.util.logging.PlatformLogger
+ * sun.util.logging.PlatformLogger}.
+ *
+ * @see java.lang.System.LoggerFinder
+ * @see jdk.internal.logger
+ * @see sun.util.logging.internal
+ *
+ */
+public class DefaultLoggerFinder extends LoggerFinder {
+
+ static final RuntimePermission LOGGERFINDER_PERMISSION =
+ new RuntimePermission("loggerFinder");
+
+ /**
+ * Creates a new instance of DefaultLoggerFinder.
+ * @throws SecurityException if the calling code does not have the
+ * {@code RuntimePermission("loggerFinder")}
+ */
+ protected DefaultLoggerFinder() {
+ this(checkPermission());
+ }
+
+ private DefaultLoggerFinder(Void unused) {
+ // nothing to do.
+ }
+
+ private static Void checkPermission() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(LOGGERFINDER_PERMISSION);
+ }
+ return null;
+ }
+
+ // SharedLoggers is a default cache of loggers used when JUL is not
+ // present - in that case we use instances of SimpleConsoleLogger which
+ // cannot be directly configure through public APIs.
+ //
+ // We can therefore afford to simply maintain two domains - one for the
+ // system, and one for the application.
+ //
+ static final class SharedLoggers {
+ private final Map> loggers =
+ new HashMap<>();
+ private final ReferenceQueue queue = new ReferenceQueue<>();
+
+ synchronized Logger get(Function loggerSupplier, final String name) {
+ Reference extends Logger> ref = loggers.get(name);
+ Logger w = ref == null ? null : ref.get();
+ if (w == null) {
+ w = loggerSupplier.apply(name);
+ loggers.put(name, new WeakReference<>(w, queue));
+ }
+
+ // Remove stale mapping...
+ Collection> values = null;
+ while ((ref = queue.poll()) != null) {
+ if (values == null) values = loggers.values();
+ values.remove(ref);
+ }
+ return w;
+ }
+
+
+ final static SharedLoggers system = new SharedLoggers();
+ final static SharedLoggers application = new SharedLoggers();
+ }
+
+ @Override
+ public final Logger getLogger(String name, /* Module */ Class> caller) {
+ checkPermission();
+ return demandLoggerFor(name, caller);
+ }
+
+ @Override
+ public final Logger getLocalizedLogger(String name, ResourceBundle bundle,
+ /* Module */ Class> caller) {
+ return super.getLocalizedLogger(name, bundle, caller);
+ }
+
+
+
+ /**
+ * Returns a {@link Logger logger} suitable for the caller usage.
+ *
+ * @implSpec The default implementation for this method is to return a
+ * simple logger that will print all messages of INFO level and above
+ * to the console. That simple logger is not configurable.
+ *
+ * @param name The name of the logger.
+ * @param caller The class on behalf of which the logger is created.
+ * @return A {@link Logger logger} suitable for the application usage.
+ * @throws SecurityException if the calling code does not have the
+ * {@code RuntimePermission("loggerFinder")}.
+ */
+ protected Logger demandLoggerFor(String name, /* Module */ Class> caller) {
+ checkPermission();
+ if (caller.getClassLoader() == null) {
+ return SharedLoggers.system.get(SimpleConsoleLogger::makeSimpleLogger, name);
+ } else {
+ return SharedLoggers.application.get(SimpleConsoleLogger::makeSimpleLogger, name);
+ }
+ }
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java
new file mode 100644
index 00000000000..c59ff195cc4
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.function.BiFunction;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import sun.misc.VM;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * This class is a factory for Lazy Loggers; only system loggers can be
+ * Lazy Loggers.
+ */
+public final class LazyLoggers {
+
+ static final RuntimePermission LOGGERFINDER_PERMISSION =
+ new RuntimePermission("loggerFinder");
+
+ private LazyLoggers() {
+ throw new InternalError();
+ }
+
+ /**
+ * This class is used to hold the factories that a Lazy Logger will use
+ * to create (or map) its wrapped logger.
+ * @param {@link Logger} or a subclass of {@link Logger}.
+ */
+ private static final class LazyLoggerFactories {
+
+ /**
+ * A factory method to create an SPI logger.
+ * Usually, this will be something like LazyLoggers::getSystemLogger.
+ */
+ final BiFunction, L> loggerSupplier;
+
+
+ public LazyLoggerFactories(BiFunction, L> loggerSupplier) {
+ this(Objects.requireNonNull(loggerSupplier),
+ (Void)null);
+ }
+
+ private LazyLoggerFactories(BiFunction, L> loggerSupplier,
+ Void unused) {
+ this.loggerSupplier = loggerSupplier;
+ }
+
+ }
+
+ static interface LoggerAccessor {
+ /**
+ * The logger name.
+ * @return The name of the logger that is / will be lazily created.
+ */
+ public String getLoggerName();
+
+ /**
+ * Returns the wrapped logger object.
+ * @return the wrapped logger object.
+ */
+ public Logger wrapped();
+
+ /**
+ * A PlatformLogger.Bridge view of the wrapped logger object.
+ * @return A PlatformLogger.Bridge view of the wrapped logger object.
+ */
+ public PlatformLogger.Bridge platform();
+ }
+
+ /**
+ * The LazyLoggerAccessor class holds all the logic that delays the creation
+ * of the SPI logger until such a time that the VM is booted and the logger
+ * is actually used for logging.
+ *
+ * This class uses the services of the BootstrapLogger class to instantiate
+ * temporary loggers if appropriate.
+ */
+ static final class LazyLoggerAccessor implements LoggerAccessor {
+
+ // The factories that will be used to create the logger lazyly
+ final LazyLoggerFactories extends Logger> factories;
+
+ // We need to pass the actual caller when creating the logger.
+ private final WeakReference> callerRef;
+
+ // The name of the logger that will be created lazyly
+ final String name;
+ // The plain logger SPI object - null until it is accessed for the
+ // first time.
+ private volatile Logger w;
+ // A PlatformLogger.Bridge view of w.
+ private volatile PlatformLogger.Bridge p;
+
+
+ private LazyLoggerAccessor(String name,
+ LazyLoggerFactories extends Logger> factories,
+ Class> caller) {
+ this(Objects.requireNonNull(name), Objects.requireNonNull(factories),
+ Objects.requireNonNull(caller), null);
+ }
+
+ private LazyLoggerAccessor(String name,
+ LazyLoggerFactories extends Logger> factories,
+ Class> caller, Void unused) {
+ this.name = name;
+ this.factories = factories;
+ this.callerRef = new WeakReference>(caller);
+ }
+
+ /**
+ * The logger name.
+ * @return The name of the logger that is / will be lazily created.
+ */
+ @Override
+ public String getLoggerName() {
+ return name;
+ }
+
+ // must be called in synchronized block
+ // set wrapped logger if not set
+ private void setWrappedIfNotSet(Logger wrapped) {
+ if (w == null) {
+ w = wrapped;
+ }
+ }
+
+ /**
+ * Returns the logger SPI object, creating it if 'w' is still null.
+ * @return the logger SPI object.
+ */
+ public Logger wrapped() {
+ Logger wrapped = w;
+ if (wrapped != null) return wrapped;
+ // Wrapped logger not created yet: create it.
+ // BootstrapLogger has the logic to decide whether to invoke the
+ // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
+ // logger.
+ wrapped = BootstrapLogger.getLogger(this);
+ synchronized(this) {
+ // if w has already been in between, simply drop 'wrapped'.
+ setWrappedIfNotSet(wrapped);
+ return w;
+ }
+ }
+
+ /**
+ * A PlatformLogger.Bridge view of the wrapped logger.
+ * @return A PlatformLogger.Bridge view of the wrapped logger.
+ */
+ public PlatformLogger.Bridge platform() {
+ // We can afford to return the platform view of the previous
+ // logger - if that view is not null.
+ // Because that view will either be the BootstrapLogger, which
+ // will redirect to the new wrapper properly, or the temporary
+ // logger - which in effect is equivalent to logging something
+ // just before the application initialized LogManager.
+ PlatformLogger.Bridge platform = p;
+ if (platform != null) return platform;
+ synchronized (this) {
+ if (w != null) {
+ if (p == null) p = PlatformLogger.Bridge.convert(w);
+ return p;
+ }
+ }
+ // If we reach here it means that the wrapped logger may not
+ // have been created yet: attempt to create it.
+ // BootstrapLogger has the logic to decide whether to invoke the
+ // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
+ // logger.
+ final Logger wrapped = BootstrapLogger.getLogger(this);
+ synchronized(this) {
+ // if w has already been set, simply drop 'wrapped'.
+ setWrappedIfNotSet(wrapped);
+ if (p == null) p = PlatformLogger.Bridge.convert(w);
+ return p;
+ }
+ }
+
+ /**
+ * Makes this accessor release a temporary logger.
+ * This method is called
+ * by BootstrapLogger when JUL is the default backend and LogManager
+ * is initialized, in order to replace temporary SimpleConsoleLoggers by
+ * real JUL loggers. See BootstrapLogger for more details.
+ * If {@code replace} is {@code true}, then this method will force
+ * the accessor to eagerly recreate its wrapped logger.
+ * Note: passing {@code replace=false} is no guarantee that the
+ * method will not actually replace the released logger.
+ * @param temporary The temporary logger too be released.
+ * @param replace Whether the released logger should be eagerly
+ * replaced.
+ */
+ void release(SimpleConsoleLogger temporary, boolean replace) {
+ PlatformLogger.ConfigurableBridge.LoggerConfiguration conf =
+ PlatformLogger.ConfigurableBridge.getLoggerConfiguration(temporary);
+ PlatformLogger.Level level = conf != null
+ ? conf.getPlatformLevel()
+ : null;
+ synchronized (this) {
+ if (this.w == temporary) {
+ this.w = null; this.p = null;
+ }
+ }
+ PlatformLogger.Bridge platform = replace || level != null
+ ? this.platform() : null;
+
+ if (level != null) {
+ conf = (platform != null && platform != temporary)
+ ? PlatformLogger.ConfigurableBridge.getLoggerConfiguration(platform)
+ : null;
+ if (conf != null) conf.setPlatformLevel(level);
+ }
+ }
+
+ /**
+ * Replace 'w' by the real SPI logger and flush the log messages pending
+ * in the temporary 'bootstrap' Logger. Called by BootstrapLogger when
+ * this accessor's bootstrap logger is accessed and BootstrapLogger
+ * notices that the VM is no longer booting.
+ * @param bootstrap This accessor's bootstrap logger (usually this is 'w').
+ */
+ Logger getConcreteLogger(BootstrapLogger bootstrap) {
+ assert VM.isBooted();
+ synchronized(this) {
+ // another thread may have already invoked flush()
+ if (this.w == bootstrap) {
+ this.w = null; this.p = null;
+ }
+ }
+ return this.wrapped();
+ }
+
+ PlatformLogger.Bridge getConcretePlatformLogger(BootstrapLogger bootstrap) {
+ assert VM.isBooted();
+ synchronized(this) {
+ // another thread may have already invoked flush()
+ if (this.w == bootstrap) {
+ this.w = null; this.p = null;
+ }
+ }
+ return this.platform();
+ }
+
+ // Creates the wrapped logger by invoking the SPI.
+ Logger createLogger() {
+ final Class> caller = callerRef.get();
+ if (caller == null) {
+ throw new IllegalStateException("The class for which this logger"
+ + " was created has been garbage collected");
+ }
+ return this.factories.loggerSupplier.apply(name, caller);
+ }
+
+ /**
+ * Creates a new lazy logger accessor for the named logger. The given
+ * factories will be use when it becomes necessary to actually create
+ * the logger.
+ * @param An interface that extends {@link Logger}.
+ * @param name The logger name.
+ * @param factories The factories that should be used to create the
+ * wrapped logger.
+ * @return A new LazyLoggerAccessor.
+ */
+ public static LazyLoggerAccessor makeAccessor(String name,
+ LazyLoggerFactories extends Logger> factories, Class> caller) {
+ return new LazyLoggerAccessor(name, factories, caller);
+ }
+
+ }
+
+ /**
+ * An implementation of {@link Logger} that redirects all calls to a wrapped
+ * instance of {@code Logger}.
+ */
+ private static class LazyLoggerWrapper
+ extends AbstractLoggerWrapper {
+
+ final LoggerAccessor loggerAccessor;
+
+ public LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier) {
+ this(Objects.requireNonNull(loggerSinkSupplier), (Void)null);
+ }
+
+ private LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier,
+ Void unused) {
+ this.loggerAccessor = loggerSinkSupplier;
+ }
+
+ @Override
+ final Logger wrapped() {
+ return loggerAccessor.wrapped();
+ }
+
+ @Override
+ PlatformLogger.Bridge platformProxy() {
+ return loggerAccessor.platform();
+ }
+
+ }
+
+ // Do not expose this outside of this package.
+ private static volatile LoggerFinder provider = null;
+ private static LoggerFinder accessLoggerFinder() {
+ if (provider == null) {
+ // no need to lock: it doesn't matter if we call
+ // getLoggerFinder() twice - since LoggerFinder already caches
+ // the result.
+ // This is just an optimization to avoid the cost of calling
+ // doPrivileged every time.
+ final SecurityManager sm = System.getSecurityManager();
+ provider = sm == null ? LoggerFinder.getLoggerFinder() :
+ AccessController.doPrivileged(
+ (PrivilegedAction)LoggerFinder::getLoggerFinder);
+ }
+ return provider;
+ }
+
+ // Avoid using lambda here as lazy loggers could be created early
+ // in the bootstrap sequence...
+ private static final BiFunction, Logger> loggerSupplier =
+ new BiFunction<>() {
+ @Override
+ public Logger apply(String name, Class> caller) {
+ return LazyLoggers.getLoggerFromFinder(name, caller);
+ }
+ };
+
+ private static final LazyLoggerFactories factories =
+ new LazyLoggerFactories<>(loggerSupplier);
+
+
+
+ // A concrete implementation of Logger that delegates to a System.Logger,
+ // but only creates the System.Logger instance lazily when it's used for
+ // the first time.
+ // The JdkLazyLogger uses a LazyLoggerAccessor objects, which relies
+ // on the logic embedded in BootstrapLogger to avoid loading the concrete
+ // logger provider until the VM has finished booting.
+ //
+ private static final class JdkLazyLogger extends LazyLoggerWrapper {
+ JdkLazyLogger(String name, Class> caller) {
+ this(LazyLoggerAccessor.makeAccessor(name, factories, caller),
+ (Void)null);
+ }
+ private JdkLazyLogger(LazyLoggerAccessor holder, Void unused) {
+ super(holder);
+ }
+ }
+
+ /**
+ * Gets a logger from the LoggerFinder. Creates the actual concrete
+ * logger.
+ * @param name name of the logger
+ * @param caller class on behalf of which the logger is created
+ * @return The logger returned by the LoggerFinder.
+ */
+ static Logger getLoggerFromFinder(String name, Class> caller) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ return accessLoggerFinder().getLogger(name, caller);
+ } else {
+ return AccessController.doPrivileged((PrivilegedAction)
+ () -> {return accessLoggerFinder().getLogger(name, caller);},
+ null, LOGGERFINDER_PERMISSION);
+ }
+ }
+
+ /**
+ * Returns a (possibly lazy) Logger for the caller.
+ *
+ * @param name the logger name
+ * @param caller The class on behalf of which the logger is created.
+ * If the caller is not loaded from the Boot ClassLoader,
+ * the LoggerFinder is accessed and the logger returned
+ * by {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class)}
+ * is returned to the caller directly.
+ * Otherwise, the logger returned by
+ * {@link #getLazyLogger(java.lang.String, java.lang.Class)}
+ * is returned to the caller.
+ *
+ * @return a (possibly lazy) Logger instance.
+ */
+ public static final Logger getLogger(String name, Class> caller) {
+ if (caller.getClassLoader() == null) {
+ return getLazyLogger(name, caller);
+ } else {
+ return getLoggerFromFinder(name, caller);
+ }
+ }
+
+ /**
+ * Returns a (possibly lazy) Logger suitable for system classes.
+ * Whether the returned logger is lazy or not depend on the result
+ * returned by {@link BootstrapLogger#useLazyLoggers()}.
+ *
+ * @param name the logger name
+ * @param caller the class on behalf of which the logger is created.
+ * @return a (possibly lazy) Logger instance.
+ */
+ public static final Logger getLazyLogger(String name, Class> caller) {
+
+ // BootstrapLogger has the logic to determine whether a LazyLogger
+ // should be used. Usually, it is worth it only if:
+ // - the VM is not yet booted
+ // - or, the backend is JUL and there is no configuration
+ // - or, the backend is a custom backend, as we don't know what
+ // that is going to load...
+ // So if for instance the VM is booted and we use JUL with a custom
+ // configuration, we're not going to delay the creation of loggers...
+ final boolean useLazyLogger = BootstrapLogger.useLazyLoggers();
+ if (useLazyLogger) {
+ return new JdkLazyLogger(name, caller);
+ } else {
+ // Directly invoke the LoggerFinder.
+ return getLoggerFromFinder(name, caller);
+ }
+ }
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java
new file mode 100644
index 00000000000..361ebc30e7b
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+
+/**
+ * This implementation of {@link Logger} redirects all logging method
+ * calls to calls to {@code log(Level, String, ResourceBundle, ...)}
+ * methods, passing the Logger's ResourceBundle as parameter.
+ * So for instance a call to {@link Logger#log(Level, String)
+ * log(Level.INFO, msg)} will be redirected
+ * to a call to {@link #log(java.lang.System.Logger.Level,
+ * java.util.ResourceBundle, java.lang.String, java.lang.Object...)
+ * this.log(Level.INFO, this.bundle, msg, (Object[]) null)}.
+ *
+ * Note that methods that take a {@link Supplier Supplier<String>}
+ * or an Object are not redirected. It is assumed that a string returned
+ * by a {@code Supplier} is already localized, or cannot be localized.
+ *
+ * @param Type of the wrapped Logger: {@code Logger} or an
+ * extension of the {@code Logger} interface.
+ */
+public class LocalizedLoggerWrapper extends LoggerWrapper {
+
+ private final ResourceBundle bundle;
+
+ public LocalizedLoggerWrapper(L wrapped, ResourceBundle bundle) {
+ super(wrapped);
+ this.bundle = bundle;
+ }
+
+ public final ResourceBundle getBundle() {
+ return bundle;
+ }
+
+ // We assume that messages returned by Supplier and Object are
+ // either already localized or not localizable. To be evaluated.
+
+ // -----------------------------------------------------------------
+ // Generic methods taking a Level as parameter
+ // -----------------------------------------------------------------
+
+ @Override
+ public final void log(Level level, String msg) {
+ log(level, bundle, msg, (Object[]) null);
+ }
+
+ @Override
+ public final void log(Level level,
+ String msg, Throwable thrown) {
+ log(level, bundle, msg, thrown);
+ }
+
+ @Override
+ public final void log(Level level,
+ String format, Object... params) {
+ log(level, bundle, format, params);
+ }
+
+ @Override
+ public final void log(Level level, Object obj) {
+ wrapped.log(level, obj);
+ }
+
+ @Override
+ public final void log(Level level, Supplier msgSupplier) {
+ wrapped.log(level, msgSupplier);
+ }
+
+ @Override
+ public final void log(Level level, Supplier msgSupplier, Throwable thrown) {
+ wrapped.log(level, msgSupplier, thrown);
+ }
+
+ @Override
+ public final void log(Level level, ResourceBundle bundle, String format, Object... params) {
+ wrapped.log(level, bundle, format, params);
+ }
+
+ @Override
+ public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+ wrapped.log(level, bundle, key, thrown);
+ }
+
+ @Override
+ public final boolean isLoggable(Level level) {
+ return wrapped.isLoggable(level);
+ }
+
+ // Override methods from PlatformLogger.Bridge that don't take a
+ // resource bundle...
+
+ @Override
+ public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+ String key) {
+ logrb(level, sourceClass, sourceMethod, bundle, key, (Object[]) null);
+ }
+
+ @Override
+ public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+ String key, Throwable thrown) {
+ logrb(level, sourceClass, sourceMethod, bundle, key, thrown);
+ }
+
+ @Override
+ public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+ String key, Object... params) {
+ logrb(level, sourceClass, sourceMethod, bundle, key, params);
+ }
+
+ @Override
+ public final void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable thrown) {
+ logrb(level, bundle, msg, thrown);
+ }
+
+ @Override
+ public final void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+ logrb(level, bundle, msg, (Object[]) null);
+ }
+
+ @Override
+ public final void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) {
+ logrb(level, bundle, format, params);
+ }
+
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java
new file mode 100644
index 00000000000..7d315ba7057
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.io.FilePermission;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import sun.security.util.SecurityConstants;
+
+/**
+ * Helper class used to load the {@link java.lang.System.LoggerFinder}.
+ */
+public final class LoggerFinderLoader {
+ private static volatile System.LoggerFinder service;
+ private static final Object lock = new int[0];
+ static final Permission CLASSLOADER_PERMISSION =
+ SecurityConstants.GET_CLASSLOADER_PERMISSION;
+ static final Permission READ_PERMISSION =
+ new FilePermission("<>",
+ SecurityConstants.FILE_READ_ACTION);
+ public static final RuntimePermission LOGGERFINDER_PERMISSION =
+ new RuntimePermission("loggerFinder");
+
+ // This is used to control how the LoggerFinderLoader handles
+ // errors when instantiating the LoggerFinder provider.
+ // ERROR => throws ServiceConfigurationError
+ // WARNING => Do not fail, use plain default (simple logger) implementation,
+ // prints warning on console. (this is the default)
+ // DEBUG => Do not fail, use plain default (simple logger) implementation,
+ // prints warning and exception stack trace on console.
+ // QUIET => Do not fail and stay silent.
+ private static enum ErrorPolicy { ERROR, WARNING, DEBUG, QUIET };
+
+ // This class is static and cannot be instantiated.
+ private LoggerFinderLoader() {
+ throw new InternalError("LoggerFinderLoader cannot be instantiated");
+ }
+
+
+ // Return the loaded LoggerFinder, or load it if not already loaded.
+ private static System.LoggerFinder service() {
+ if (service != null) return service;
+ synchronized(lock) {
+ if (service != null) return service;
+ service = loadLoggerFinder();
+ }
+ // Since the LoggerFinder is already loaded - we can stop using
+ // temporary loggers.
+ BootstrapLogger.redirectTemporaryLoggers();
+ return service;
+ }
+
+ // Get configuration error policy
+ private static ErrorPolicy configurationErrorPolicy() {
+ final PrivilegedAction getConfigurationErrorPolicy =
+ () -> System.getProperty("jdk.logger.finder.error");
+ String errorPolicy = AccessController.doPrivileged(getConfigurationErrorPolicy);
+ if (errorPolicy == null || errorPolicy.isEmpty()) {
+ return ErrorPolicy.WARNING;
+ }
+ try {
+ return ErrorPolicy.valueOf(errorPolicy.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException x) {
+ return ErrorPolicy.WARNING;
+ }
+ }
+
+ // Whether multiple provider should be considered as an error.
+ // This is further submitted to the configuration error policy.
+ private static boolean ensureSingletonProvider() {
+ final PrivilegedAction ensureSingletonProvider =
+ () -> Boolean.getBoolean("jdk.logger.finder.singleton");
+ return AccessController.doPrivileged(ensureSingletonProvider);
+ }
+
+ private static Iterator findLoggerFinderProviders() {
+ final Iterator iterator;
+ if (System.getSecurityManager() == null) {
+ iterator = ServiceLoader.load(System.LoggerFinder.class,
+ ClassLoader.getSystemClassLoader()).iterator();
+ } else {
+ final PrivilegedAction> pa =
+ () -> ServiceLoader.load(System.LoggerFinder.class,
+ ClassLoader.getSystemClassLoader()).iterator();
+ iterator = AccessController.doPrivileged(pa, null,
+ LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION,
+ READ_PERMISSION);
+ }
+ return iterator;
+ }
+
+ // Loads the LoggerFinder using ServiceLoader. If no LoggerFinder
+ // is found returns the default (possibly JUL based) implementation
+ private static System.LoggerFinder loadLoggerFinder() {
+ System.LoggerFinder result;
+ try {
+ // Iterator iterates with the access control context stored
+ // at ServiceLoader creation time.
+ final Iterator iterator =
+ findLoggerFinderProviders();
+ if (iterator.hasNext()) {
+ result = iterator.next();
+ if (iterator.hasNext() && ensureSingletonProvider()) {
+ throw new ServiceConfigurationError(
+ "More than on LoggerFinder implementation");
+ }
+ } else {
+ result = loadDefaultImplementation();
+ }
+ } catch (Error | RuntimeException x) {
+ // next caller will get the plain default impl (not linked
+ // to java.util.logging)
+ service = result = new DefaultLoggerFinder();
+ ErrorPolicy errorPolicy = configurationErrorPolicy();
+ if (errorPolicy == ErrorPolicy.ERROR) {
+ // rethrow any exception as a ServiceConfigurationError.
+ if (x instanceof Error) {
+ throw x;
+ } else {
+ throw new ServiceConfigurationError(
+ "Failed to instantiate LoggerFinder provider; Using default.", x);
+ }
+ } else if (errorPolicy != ErrorPolicy.QUIET) {
+ // if QUIET just silently use the plain default impl
+ // otherwise, log a warning, possibly adding the exception
+ // stack trace (if DEBUG is specified).
+ SimpleConsoleLogger logger =
+ new SimpleConsoleLogger("jdk.internal.logger", false);
+ logger.log(System.Logger.Level.WARNING,
+ "Failed to instantiate LoggerFinder provider; Using default.");
+ if (errorPolicy == ErrorPolicy.DEBUG) {
+ logger.log(System.Logger.Level.WARNING,
+ "Exception raised trying to instantiate LoggerFinder", x);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static System.LoggerFinder loadDefaultImplementation() {
+ final SecurityManager sm = System.getSecurityManager();
+ final Iterator iterator;
+ if (sm == null) {
+ iterator = ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator();
+ } else {
+ // We use limited do privileged here - the minimum set of
+ // permissions required to 'see' the META-INF/services resources
+ // seems to be CLASSLOADER_PERMISSION and READ_PERMISSION.
+ // Note that do privileged is required because
+ // otherwise the SecurityManager will prevent the ServiceLoader
+ // from seeing the installed provider.
+ PrivilegedAction> pa = () ->
+ ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator();
+ iterator = AccessController.doPrivileged(pa, null,
+ LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION,
+ READ_PERMISSION);
+ }
+ DefaultLoggerFinder result = null;
+ try {
+ // Iterator iterates with the access control context stored
+ // at ServiceLoader creation time.
+ if (iterator.hasNext()) {
+ result = iterator.next();
+ }
+ } catch (RuntimeException x) {
+ throw new ServiceConfigurationError(
+ "Failed to instantiate default LoggerFinder", x);
+ }
+ if (result == null) {
+ result = new DefaultLoggerFinder();
+ }
+ return result;
+ }
+
+ public static System.LoggerFinder getLoggerFinder() {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(LOGGERFINDER_PERMISSION);
+ }
+ return service();
+ }
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java
new file mode 100644
index 00000000000..8f214239d3e
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.util.Objects;
+import java.lang.System.Logger;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * An implementation of {@link Logger} that redirects all calls to a wrapped
+ instance of Logger.
+ *
+ * @param Type of the wrapped Logger: {@code Logger} or an
+ * extension of that interface.
+ */
+public class LoggerWrapper extends AbstractLoggerWrapper {
+
+ final L wrapped;
+ final PlatformLogger.Bridge platformProxy;
+
+ public LoggerWrapper(L wrapped) {
+ this(Objects.requireNonNull(wrapped), (Void)null);
+ }
+
+ LoggerWrapper(L wrapped, Void unused) {
+ this.wrapped = wrapped;
+ this.platformProxy = (wrapped instanceof PlatformLogger.Bridge) ?
+ (PlatformLogger.Bridge) wrapped : null;
+ }
+
+ @Override
+ public final L wrapped() {
+ return wrapped;
+ }
+
+ @Override
+ public final PlatformLogger.Bridge platformProxy() {
+ return platformProxy;
+ }
+
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java b/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java
new file mode 100644
index 00000000000..8f6ddf7764d
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2015, 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.logger;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.time.ZonedDateTime;
+import java.util.ResourceBundle;
+import java.util.function.Function;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.function.Supplier;
+import jdk.internal.misc.JavaLangAccess;
+import jdk.internal.misc.SharedSecrets;
+import sun.util.logging.PlatformLogger;
+import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;
+
+/**
+ * A simple console logger to emulate the behavior of JUL loggers when
+ * in the default configuration. SimpleConsoleLoggers are also used when
+ * JUL is not present and no DefaultLoggerFinder is installed.
+ */
+public class SimpleConsoleLogger extends LoggerConfiguration
+ implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge {
+
+ static final PlatformLogger.Level DEFAULT_LEVEL = PlatformLogger.Level.INFO;
+
+ final String name;
+ volatile PlatformLogger.Level level;
+ final boolean usePlatformLevel;
+ SimpleConsoleLogger(String name, boolean usePlatformLevel) {
+ this.name = name;
+ this.usePlatformLevel = usePlatformLevel;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ private Enum> logLevel(PlatformLogger.Level level) {
+ return usePlatformLevel ? level : level.systemLevel();
+ }
+
+ private Enum> logLevel(Level level) {
+ return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level;
+ }
+
+ // ---------------------------------------------------
+ // From Logger
+ // ---------------------------------------------------
+
+ @Override
+ public boolean isLoggable(Level level) {
+ return isLoggable(PlatformLogger.toPlatformLevel(level));
+ }
+
+ @Override
+ public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+ if (isLoggable(level)) {
+ if (bundle != null) {
+ key = bundle.getString(key);
+ }
+ publish(getCallerInfo(), logLevel(level), key, thrown);
+ }
+ }
+
+ @Override
+ public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+ if (isLoggable(level)) {
+ if (bundle != null) {
+ format = bundle.getString(format);
+ }
+ publish(getCallerInfo(), logLevel(level), format, params);
+ }
+ }
+
+ // ---------------------------------------------------
+ // From PlatformLogger.Bridge
+ // ---------------------------------------------------
+
+ @Override
+ public boolean isLoggable(PlatformLogger.Level level) {
+ final PlatformLogger.Level effectiveLevel = effectiveLevel();
+ return level != PlatformLogger.Level.OFF
+ && level.ordinal() >= effectiveLevel.ordinal();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return level != PlatformLogger.Level.OFF;
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(), logLevel(level), msg);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg, Throwable thrown) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(), logLevel(level), msg, thrown);
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, String msg, Object... params) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(), logLevel(level), msg, params);
+ }
+ }
+
+ private PlatformLogger.Level effectiveLevel() {
+ if (level == null) return DEFAULT_LEVEL;
+ return level;
+ }
+
+ @Override
+ public PlatformLogger.Level getPlatformLevel() {
+ return level;
+ }
+
+ @Override
+ public void setPlatformLevel(PlatformLogger.Level newLevel) {
+ level = newLevel;
+ }
+
+ @Override
+ public LoggerConfiguration getLoggerConfiguration() {
+ return this;
+ }
+
+ /**
+ * Default platform logging support - output messages to System.err -
+ * equivalent to ConsoleHandler with SimpleFormatter.
+ */
+ static PrintStream outputStream() {
+ return System.err;
+ }
+
+ // Returns the caller's class and method's name; best effort
+ // if cannot infer, return the logger's name.
+ private String getCallerInfo() {
+ String sourceClassName = null;
+ String sourceMethodName = null;
+
+ JavaLangAccess access = SharedSecrets.getJavaLangAccess();
+ Throwable throwable = new Throwable();
+ int depth = access.getStackTraceDepth(throwable);
+
+ String logClassName = "sun.util.logging.PlatformLogger";
+ String simpleLoggerClassName = "jdk.internal.logger.SimpleConsoleLogger";
+ boolean lookingForLogger = true;
+ for (int ix = 0; ix < depth; ix++) {
+ // Calling getStackTraceElement directly prevents the VM
+ // from paying the cost of building the entire stack frame.
+ final StackTraceElement frame =
+ access.getStackTraceElement(throwable, ix);
+ final String cname = frame.getClassName();
+ if (lookingForLogger) {
+ // Skip all frames until we have found the first logger frame.
+ if (cname.equals(logClassName) || cname.equals(simpleLoggerClassName)) {
+ lookingForLogger = false;
+ }
+ } else {
+ if (skipLoggingFrame(cname)) continue;
+ if (!cname.equals(logClassName) && !cname.equals(simpleLoggerClassName)) {
+ // We've found the relevant frame.
+ sourceClassName = cname;
+ sourceMethodName = frame.getMethodName();
+ break;
+ }
+ }
+ }
+
+ if (sourceClassName != null) {
+ return sourceClassName + " " + sourceMethodName;
+ } else {
+ return name;
+ }
+ }
+
+ private String getCallerInfo(String sourceClassName, String sourceMethodName) {
+ if (sourceClassName == null) return name;
+ if (sourceMethodName == null) return sourceClassName;
+ return sourceClassName + " " + sourceMethodName;
+ }
+
+ private String toString(Throwable thrown) {
+ String throwable = "";
+ if (thrown != null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ pw.println();
+ thrown.printStackTrace(pw);
+ pw.close();
+ throwable = sw.toString();
+ }
+ return throwable;
+ }
+
+ private synchronized String format(Enum> level,
+ String msg, Throwable thrown, String callerInfo) {
+
+ ZonedDateTime zdt = ZonedDateTime.now();
+ String throwable = toString(thrown);
+
+ return String.format(Formatting.formatString,
+ zdt,
+ callerInfo,
+ name,
+ level.name(),
+ msg,
+ throwable);
+ }
+
+ // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
+ private void publish(String callerInfo, Enum> level, String msg) {
+ outputStream().print(format(level, msg, null, callerInfo));
+ }
+ // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
+ private void publish(String callerInfo, Enum> level, String msg, Throwable thrown) {
+ outputStream().print(format(level, msg, thrown, callerInfo));
+ }
+ // publish accepts both PlatformLogger Levels and LoggerFinder Levels.
+ private void publish(String callerInfo, Enum> level, String msg, Object... params) {
+ msg = params == null || params.length == 0 ? msg
+ : Formatting.formatMessage(msg, params);
+ outputStream().print(format(level, msg, null, callerInfo));
+ }
+
+ public static SimpleConsoleLogger makeSimpleLogger(String name, boolean usePlatformLevel) {
+ return new SimpleConsoleLogger(name, usePlatformLevel);
+ }
+
+ public static SimpleConsoleLogger makeSimpleLogger(String name) {
+ return new SimpleConsoleLogger(name, false);
+ }
+
+ public static String getSimpleFormat(Function defaultPropertyGetter) {
+ return Formatting.getSimpleFormat(defaultPropertyGetter);
+ }
+
+ public static boolean skipLoggingFrame(String cname) {
+ return Formatting.skipLoggingFrame(cname);
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, Supplier msgSupplier) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(), logLevel(level), msgSupplier.get());
+ }
+ }
+
+ @Override
+ public void log(PlatformLogger.Level level, Throwable thrown,
+ Supplier msgSupplier) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, Supplier msgSupplier) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get());
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod,
+ String msg, Object... params) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, String msg, Throwable thrown) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
+ }
+ }
+
+ @Override
+ public void logp(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, Throwable thrown, Supplier msgSupplier) {
+ if (isLoggable(level)) {
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, ResourceBundle bundle, String key, Object... params) {
+ if (isLoggable(level)) {
+ String msg = bundle == null ? key : bundle.getString(key);
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, String sourceClass,
+ String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) {
+ if (isLoggable(level)) {
+ String msg = bundle == null ? key : bundle.getString(key);
+ publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+ String key, Object... params) {
+ if (isLoggable(level)) {
+ String msg = bundle == null ? key : bundle.getString(key);
+ publish(getCallerInfo(), logLevel(level), msg, params);
+ }
+ }
+
+ @Override
+ public void logrb(PlatformLogger.Level level, ResourceBundle bundle,
+ String key, Throwable thrown) {
+ if (isLoggable(level)) {
+ String msg = bundle == null ? key : bundle.getString(key);
+ publish(getCallerInfo(), logLevel(level), msg, thrown);
+ }
+ }
+
+ private static final class Formatting {
+ static final String DEFAULT_FORMAT =
+ "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
+ static final String FORMAT_PROP_KEY =
+ "java.util.logging.SimpleFormatter.format";
+ static final String formatString = getSimpleFormat(null);
+
+ // Make it easier to wrap Logger...
+ static private final String[] skips;
+ static {
+ String additionalPkgs = AccessController.doPrivileged(
+ (PrivilegedAction)
+ () -> System.getProperty("jdk.logger.packages"));
+ skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(",");
+
+ }
+
+ static boolean skipLoggingFrame(String cname) {
+ // skip logging/logger infrastructure
+
+ // fast escape path: all the prefixes below start with 's' or 'j' and
+ // have more than 12 characters.
+ char c = cname.length() < 12 ? 0 : cname.charAt(0);
+ if (c == 's') {
+ // skip internal machinery classes
+ if (cname.startsWith("sun.util.logging.")) return true;
+ if (cname.startsWith("sun.reflect.")) return true;
+ if (cname.startsWith("sun.rmi.runtime.Log")) return true;
+ } else if (c == 'j') {
+ // Message delayed at Bootstrap: no need to go further up.
+ if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false;
+ // skip public machinery classes
+ if (cname.startsWith("jdk.internal.logger.")) return true;
+ if (cname.startsWith("java.util.logging.")) return true;
+ if (cname.startsWith("java.lang.System$Logger")) return true;
+ if (cname.startsWith("java.lang.reflect.")) return true;
+ if (cname.startsWith("java.lang.invoke.MethodHandle")) return true;
+ if (cname.startsWith("java.lang.invoke.LambdaForm")) return true;
+ if (cname.startsWith("java.security.AccessController")) return true;
+ }
+
+ // check additional prefixes if any are specified.
+ if (skips.length > 0) {
+ for (int i=0; i defaultPropertyGetter) {
+ // Using a lambda here causes
+ // jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java
+ // to fail - because that test has a testcase which somehow references
+ // PlatformLogger and counts the number of generated lambda classes
+ // So we explicitely use new PrivilegedAction here.
+ String format =
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public String run() {
+ return System.getProperty(FORMAT_PROP_KEY);
+ }
+ });
+ if (format == null && defaultPropertyGetter != null) {
+ format = defaultPropertyGetter.apply(FORMAT_PROP_KEY);
+ }
+ if (format != null) {
+ try {
+ // validate the user-defined format string
+ String.format(format, ZonedDateTime.now(), "", "", "", "", "");
+ } catch (IllegalArgumentException e) {
+ // illegal syntax; fall back to the default format
+ format = DEFAULT_FORMAT;
+ }
+ } else {
+ format = DEFAULT_FORMAT;
+ }
+ return format;
+ }
+
+
+ // Copied from java.util.logging.Formatter.formatMessage
+ static String formatMessage(String format, Object... parameters) {
+ // Do the formatting.
+ try {
+ if (parameters == null || parameters.length == 0) {
+ // No parameters. Just return format string.
+ return format;
+ }
+ // Is it a java.text style format?
+ // Ideally we could match with
+ // Pattern.compile("\\{\\d").matcher(format).find())
+ // However the cost is 14% higher, so we cheaply check for
+ //
+ boolean isJavaTestFormat = false;
+ final int len = format.length();
+ for (int i=0; i= '0' && d <= '9') {
+ isJavaTestFormat = true;
+ break;
+ }
+ }
+ }
+ if (isJavaTestFormat) {
+ return java.text.MessageFormat.format(format, parameters);
+ }
+ return format;
+ } catch (Exception ex) {
+ // Formatting failed: use format string.
+ return format;
+ }
+ }
+ }
+}
diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java b/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java
new file mode 100644
index 00000000000..28c622d4cde
--- /dev/null
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+/**
+ * [JDK INTERNAL]
+ * The {@code jdk.internal.logger} package defines an internal provider
+ * whose default naive implementation is replaced by the {@code java.logging}
+ * module when the {@code java.logging} module is present.
+ *
+ * Default Implementation
+ *
+ * The JDK default implementation of the System.LoggerFinder will attempt to
+ * load an installed instance of the {@link jdk.internal.logger.DefaultLoggerFinder}
+ * defined in this package.
+ * When the {@code java.util.logging} package is present, this will usually
+ * resolve to an instance of {@link sun.util.logging.internal.LoggingProviderImpl} -
+ * which provides an implementation of the Logger whose backend is a
+ * {@link java.util.logging.Logger java.util.logging.Logger}.
+ * Configuration can thus be performed by direct access to the regular
+ * {@code java.util.logging} APIs,
+ * using {@link java.util.logging.Logger java.util.logging.Logger} and
+ * {@link java.util.logging.LogManager} to access and configure the backend
+ * Loggers.
+ *
+ * If however {@code java.util.logging} is not linked with the application, then
+ * the default implementation will return a simple logger that will print out
+ * all log messages of INFO level and above to the console ({@code System.err}),
+ * as implemented by the base {@link jdk.internal.logger.DefaultLoggerFinder} class.
+ *
+ * Message Levels and Mapping to java.util.logging
+ *
+ * The {@link java.lang.System.LoggerFinder} class documentation describe how
+ * {@linkplain java.lang.System.Logger.Level System.Logger levels} are mapped
+ * to {@linkplain java.util.logging.Level JUL levels} when {@code
+ * java.util.logging} is the backend.
+ *
+ * @see jdk.internal.logger.DefaultLoggerFinder
+ * @see sun.util.logging.internal.LoggingProviderImpl
+ * @see java.lang.System.LoggerFinder
+ * @see java.lang.System.Logger
+ * @see sun.util.logging.PlatformLogger.Bridge
+ * @see sun.util.logging.internal
+ *
+ * @since 1.9
+ */
+package jdk.internal.logger;
diff --git a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
index 530dc167c00..f84371da80e 100644
--- a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
+++ b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
@@ -977,4 +977,69 @@ public class AlgorithmId implements Serializable, DerEncoder {
}
return null;
}
+
+ /**
+ * Checks if a signature algorithm matches a key algorithm, i.e. a
+ * signature can be initialized with a key.
+ *
+ * @param kAlg must not be null
+ * @param sAlg must not be null
+ * @throws IllegalArgumentException if they do not match
+ */
+ public static void checkKeyAndSigAlgMatch(String kAlg, String sAlg) {
+ String sAlgUp = sAlg.toUpperCase(Locale.US);
+ if ((sAlgUp.endsWith("WITHRSA") && !kAlg.equalsIgnoreCase("RSA")) ||
+ (sAlgUp.endsWith("WITHECDSA") && !kAlg.equalsIgnoreCase("EC")) ||
+ (sAlgUp.endsWith("WITHDSA") && !kAlg.equalsIgnoreCase("DSA"))) {
+ throw new IllegalArgumentException(
+ "key algorithm not compatible with signature algorithm");
+ }
+ }
+
+ /**
+ * Returns the default signature algorithm for a private key. The digest
+ * part might evolve with time. Remember to update the spec of
+ * {@link jdk.security.jarsigner.JarSigner.Builder#getDefaultSignatureAlgorithm(PrivateKey)}
+ * if updated.
+ *
+ * @param k cannot be null
+ * @return the default alg, might be null if unsupported
+ */
+ public static String getDefaultSigAlgForKey(PrivateKey k) {
+ switch (k.getAlgorithm().toUpperCase()) {
+ case "EC":
+ return ecStrength(KeyUtil.getKeySize(k))
+ + "withECDSA";
+ case "DSA":
+ return ifcFfcStrength(KeyUtil.getKeySize(k))
+ + "withDSA";
+ case "RSA":
+ return ifcFfcStrength(KeyUtil.getKeySize(k))
+ + "withRSA";
+ default:
+ return null;
+ }
+ }
+
+ // Values from SP800-57 part 1 rev 3 tables 2 and three
+ private static String ecStrength (int bitLength) {
+ if (bitLength >= 512) { // 256 bits of strength
+ return "SHA512";
+ } else if (bitLength >= 384) { // 192 bits of strength
+ return "SHA384";
+ } else { // 128 bits of strength and less
+ return "SHA256";
+ }
+ }
+
+ // same values for RSA and DSA
+ private static String ifcFfcStrength (int bitLength) {
+ if (bitLength > 7680) { // 256 bits
+ return "SHA512";
+ } else if (bitLength > 3072) { // 192 bits
+ return "SHA384";
+ } else { // 128 bits and less
+ return "SHA256";
+ }
+ }
}
diff --git a/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java b/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java
deleted file mode 100644
index c044a5a260e..00000000000
--- a/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2009, 2013, 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 sun.util.logging;
-
-/**
- * A proxy interface for the java.util.logging support.
- *
- * @see sun.util.logging.LoggingSupport
- */
-public interface LoggingProxy {
- // Methods to bridge java.util.logging.Logger methods
- public Object getLogger(String name);
-
- public Object getLevel(Object logger);
-
- public void setLevel(Object logger, Object newLevel);
-
- public boolean isLoggable(Object logger, Object level);
-
- public void log(Object logger, Object level, String msg);
-
- public void log(Object logger, Object level, String msg, Throwable t);
-
- public void log(Object logger, Object level, String msg, Object... params);
-
- // Methods to bridge java.util.logging.LoggingMXBean methods
- public java.util.List getLoggerNames();
-
- public String getLoggerLevel(String loggerName);
-
- public void setLoggerLevel(String loggerName, String levelName);
-
- public String getParentLoggerName(String loggerName);
-
- // Methods to bridge Level.parse() and Level.getName() method
- public Object parseLevel(String levelName);
-
- public String getLevelName(Object level);
-
- public int getLevelValue(Object level);
-
- // return the logging property
- public String getProperty(String key);
-}
diff --git a/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java b/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java
deleted file mode 100644
index 3fb06be8541..00000000000
--- a/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (c) 2009, 2015, 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 sun.util.logging;
-
-import java.lang.reflect.Field;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.ZonedDateTime;
-
-/**
- * Internal API to support JRE implementation to detect if the java.util.logging
- * support is available but with no dependency on the java.util.logging
- * classes. This LoggingSupport class provides several static methods to
- * access the java.util.logging functionality that requires the caller
- * to ensure that the logging support is {@linkplain #isAvailable available}
- * before invoking it.
- *
- * @see sun.util.logging.PlatformLogger if you want to log messages even
- * if the logging support is not available
- */
-public class LoggingSupport {
- private LoggingSupport() { }
-
- private static final LoggingProxy proxy =
- AccessController.doPrivileged(new PrivilegedAction() {
- public LoggingProxy run() {
- try {
- // create a LoggingProxyImpl instance when
- // java.util.logging classes exist
- Class> c = Class.forName("java.util.logging.LoggingProxyImpl", true, null);
- Field f = c.getDeclaredField("INSTANCE");
- f.setAccessible(true);
- return (LoggingProxy) f.get(null);
- } catch (ClassNotFoundException cnf) {
- return null;
- } catch (NoSuchFieldException e) {
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- }});
-
- /**
- * Returns true if java.util.logging support is available.
- */
- public static boolean isAvailable() {
- return proxy != null;
- }
-
- private static void ensureAvailable() {
- if (proxy == null)
- throw new AssertionError("Should not here");
- }
-
- public static java.util.List getLoggerNames() {
- ensureAvailable();
- return proxy.getLoggerNames();
- }
- public static String getLoggerLevel(String loggerName) {
- ensureAvailable();
- return proxy.getLoggerLevel(loggerName);
- }
-
- public static void setLoggerLevel(String loggerName, String levelName) {
- ensureAvailable();
- proxy.setLoggerLevel(loggerName, levelName);
- }
-
- public static String getParentLoggerName(String loggerName) {
- ensureAvailable();
- return proxy.getParentLoggerName(loggerName);
- }
-
- public static Object getLogger(String name) {
- ensureAvailable();
- return proxy.getLogger(name);
- }
-
- public static Object getLevel(Object logger) {
- ensureAvailable();
- return proxy.getLevel(logger);
- }
-
- public static void setLevel(Object logger, Object newLevel) {
- ensureAvailable();
- proxy.setLevel(logger, newLevel);
- }
-
- public static boolean isLoggable(Object logger, Object level) {
- ensureAvailable();
- return proxy.isLoggable(logger,level);
- }
-
- public static void log(Object logger, Object level, String msg) {
- ensureAvailable();
- proxy.log(logger, level, msg);
- }
-
- public static void log(Object logger, Object level, String msg, Throwable t) {
- ensureAvailable();
- proxy.log(logger, level, msg, t);
- }
-
- public static void log(Object logger, Object level, String msg, Object... params) {
- ensureAvailable();
- proxy.log(logger, level, msg, params);
- }
-
- public static Object parseLevel(String levelName) {
- ensureAvailable();
- return proxy.parseLevel(levelName);
- }
-
- public static String getLevelName(Object level) {
- ensureAvailable();
- return proxy.getLevelName(level);
- }
-
- public static int getLevelValue(Object level) {
- ensureAvailable();
- return proxy.getLevelValue(level);
- }
-
- // Since JDK 9, logging uses java.time to get more precise time stamps.
- // It is possible to configure the simple format to print nano seconds (.%1$tN)
- // by specifying:
- // java.util.logging.SimpleFormatter.format=%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n
- // in the logging configuration
- private static final String DEFAULT_FORMAT =
- "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
-
- private static final String FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format";
- public static String getSimpleFormat() {
- return getSimpleFormat(true);
- }
-
- // useProxy if true will cause initialization of
- // java.util.logging and read its configuration
- static String getSimpleFormat(boolean useProxy) {
- String format =
- AccessController.doPrivileged(
- new PrivilegedAction() {
- public String run() {
- return System.getProperty(FORMAT_PROP_KEY);
- }
- });
-
- if (useProxy && proxy != null && format == null) {
- format = proxy.getProperty(FORMAT_PROP_KEY);
- }
-
- if (format != null) {
- try {
- // validate the user-defined format string
- String.format(format, ZonedDateTime.now(), "", "", "", "", "");
- } catch (IllegalArgumentException e) {
- // illegal syntax; fall back to the default format
- format = DEFAULT_FORMAT;
- }
- } else {
- format = DEFAULT_FORMAT;
- }
- return format;
- }
-
-}
diff --git a/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java b/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java
index 66de672d02c..655dc96a299 100644
--- a/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java
+++ b/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java
@@ -27,20 +27,13 @@
package sun.util.logging;
import java.lang.ref.WeakReference;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
-import jdk.internal.misc.JavaLangAccess;
-import jdk.internal.misc.SharedSecrets;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import jdk.internal.logger.LazyLoggers;
+import jdk.internal.logger.LoggerWrapper;
/**
* Platform logger provides an API for the JRE components to log
@@ -56,18 +49,28 @@ import jdk.internal.misc.SharedSecrets;
* the stack frame information issuing the log message.
*
* When the logging facility is enabled (at startup or runtime),
- * the java.util.logging.Logger will be created for each platform
+ * the backend logger will be created for each platform
* logger and all log messages will be forwarded to the Logger
* to handle.
*
+ * The PlatformLogger uses an underlying PlatformLogger.Bridge instance
+ * obtained by calling {@link PlatformLogger.Bridge#convert PlatformLogger.Bridge.convert(}
+ * {@link jdk.internal.logger.LazyLoggers#getLazyLogger(java.lang.String, java.lang.Class)
+ * jdk.internal.logger.LazyLoggers#getLazyLogger(name, PlatformLogger.class))}.
+ *
* Logging facility is "enabled" when one of the following
* conditions is met:
- * 1) a system property "java.util.logging.config.class" or
- * "java.util.logging.config.file" is set
- * 2) java.util.logging.LogManager or java.util.logging.Logger
- * is referenced that will trigger the logging initialization.
+ * 1) ServiceLoader.load({@link java.lang.System.LoggerFinder LoggerFinder.class},
+ * ClassLoader.getSystemClassLoader()).iterator().hasNext().
+ * 2) ServiceLoader.loadInstalled({@link jdk.internal.logger.DefaultLoggerFinder}).iterator().hasNext(),
+ * and 2.1) a system property "java.util.logging.config.class" or
+ * "java.util.logging.config.file" is set
+ * or 2.2) java.util.logging.LogManager or java.util.logging.Logger
+ * is referenced that will trigger the logging initialization.
*
* Default logging configuration:
+ *
+ * No LoggerFinder service implementation declared
* global logging level = INFO
* handlers = java.util.logging.ConsoleHandler
* java.util.logging.ConsoleHandler.level = INFO
@@ -84,71 +87,84 @@ import jdk.internal.misc.SharedSecrets;
* The platform loggers are designed for JDK developers use and
* this limitation can be workaround with setting
* -Djava.util.logging.config.file system property.
+ *
+ * Calling PlatformLogger.setLevel will not work when there is a custom
+ * LoggerFinder installed - and as a consequence {@link #setLevel setLevel}
+ * is now deprecated.
*
* @since 1.7
*/
public class PlatformLogger {
- // The integer values must match that of {@code java.util.logging.Level}
- // objects.
- private static final int OFF = Integer.MAX_VALUE;
- private static final int SEVERE = 1000;
- private static final int WARNING = 900;
- private static final int INFO = 800;
- private static final int CONFIG = 700;
- private static final int FINE = 500;
- private static final int FINER = 400;
- private static final int FINEST = 300;
- private static final int ALL = Integer.MIN_VALUE;
-
/**
* PlatformLogger logging levels.
*/
public static enum Level {
// The name and value must match that of {@code java.util.logging.Level}s.
// Declare in ascending order of the given value for binary search.
- ALL,
- FINEST,
- FINER,
- FINE,
- CONFIG,
- INFO,
- WARNING,
- SEVERE,
- OFF;
+ ALL(System.Logger.Level.ALL),
+ FINEST(System.Logger.Level.TRACE),
+ FINER(System.Logger.Level.TRACE),
+ FINE(System.Logger.Level.DEBUG),
+ CONFIG(System.Logger.Level.DEBUG),
+ INFO(System.Logger.Level.INFO),
+ WARNING(System.Logger.Level.WARNING),
+ SEVERE(System.Logger.Level.ERROR),
+ OFF(System.Logger.Level.OFF);
- /**
- * Associated java.util.logging.Level lazily initialized in
- * JavaLoggerProxy's static initializer only once
- * when java.util.logging is available and enabled.
- * Only accessed by JavaLoggerProxy.
- */
- /* java.util.logging.Level */ Object javaLevel;
+ final System.Logger.Level systemLevel;
+ Level(System.Logger.Level systemLevel) {
+ this.systemLevel = systemLevel;
+ }
+
+ // The integer values must match that of {@code java.util.logging.Level}
+ // objects.
+ private static final int SEVERITY_OFF = Integer.MAX_VALUE;
+ private static final int SEVERITY_SEVERE = 1000;
+ private static final int SEVERITY_WARNING = 900;
+ private static final int SEVERITY_INFO = 800;
+ private static final int SEVERITY_CONFIG = 700;
+ private static final int SEVERITY_FINE = 500;
+ private static final int SEVERITY_FINER = 400;
+ private static final int SEVERITY_FINEST = 300;
+ private static final int SEVERITY_ALL = Integer.MIN_VALUE;
// ascending order for binary search matching the list of enum constants
private static final int[] LEVEL_VALUES = new int[] {
- PlatformLogger.ALL, PlatformLogger.FINEST, PlatformLogger.FINER,
- PlatformLogger.FINE, PlatformLogger.CONFIG, PlatformLogger.INFO,
- PlatformLogger.WARNING, PlatformLogger.SEVERE, PlatformLogger.OFF
+ SEVERITY_ALL, SEVERITY_FINEST, SEVERITY_FINER,
+ SEVERITY_FINE, SEVERITY_CONFIG, SEVERITY_INFO,
+ SEVERITY_WARNING, SEVERITY_SEVERE, SEVERITY_OFF
};
+ public System.Logger.Level systemLevel() {
+ return systemLevel;
+ }
+
public int intValue() {
return LEVEL_VALUES[this.ordinal()];
}
- static Level valueOf(int level) {
+ /**
+ * Maps a severity value to an effective logger level.
+ * @param level The severity of the messages that should be
+ * logged with a logger set to the returned level.
+ * @return The effective logger level, which is the nearest Level value
+ * whose severity is greater or equal to the given level.
+ * For level > SEVERE (OFF excluded), return SEVERE.
+ */
+ public static Level valueOf(int level) {
switch (level) {
// ordering per the highest occurrences in the jdk source
// finest, fine, finer, info first
- case PlatformLogger.FINEST : return Level.FINEST;
- case PlatformLogger.FINE : return Level.FINE;
- case PlatformLogger.FINER : return Level.FINER;
- case PlatformLogger.INFO : return Level.INFO;
- case PlatformLogger.WARNING : return Level.WARNING;
- case PlatformLogger.CONFIG : return Level.CONFIG;
- case PlatformLogger.SEVERE : return Level.SEVERE;
- case PlatformLogger.OFF : return Level.OFF;
- case PlatformLogger.ALL : return Level.ALL;
+ case SEVERITY_FINEST : return Level.FINEST;
+ case SEVERITY_FINE : return Level.FINE;
+ case SEVERITY_FINER : return Level.FINER;
+ case SEVERITY_INFO : return Level.INFO;
+ case SEVERITY_WARNING : return Level.WARNING;
+ case SEVERITY_CONFIG : return Level.CONFIG;
+ case SEVERITY_SEVERE : return Level.SEVERE;
+ case SEVERITY_OFF : return Level.OFF;
+ case SEVERITY_ALL : return Level.ALL;
}
// return the nearest Level value >= the given level,
// for level > SEVERE, return SEVERE and exclude OFF
@@ -157,39 +173,110 @@ public class PlatformLogger {
}
}
- private static final Level DEFAULT_LEVEL = Level.INFO;
- private static boolean loggingEnabled;
- static {
- loggingEnabled = AccessController.doPrivileged(
- new PrivilegedAction<>() {
- public Boolean run() {
- String cname = System.getProperty("java.util.logging.config.class");
- String fname = System.getProperty("java.util.logging.config.file");
- return (cname != null || fname != null);
- }
- });
+ /**
+ *
+ * The PlatformLogger.Bridge interface is implemented by the System.Logger
+ * objects returned by our default JUL provider - so that JRE classes using
+ * PlatformLogger see no difference when JUL is the actual backend.
+ *
+ * PlatformLogger is now only a thin adaptation layer over the same
+ * loggers than returned by java.lang.System.getLogger(String name).
+ *
+ * The recommendation for JRE classes going forward is to use
+ * java.lang.System.getLogger(String name), which will
+ * use Lazy Loggers when possible and necessary.
+ *
+ */
+ public static interface Bridge {
- // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations
- // less probable. Don't initialize JavaLoggerProxy class since
- // java.util.logging may not be enabled.
- try {
- Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy",
- false,
- PlatformLogger.class.getClassLoader());
- Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy",
- false, // do not invoke class initializer
- PlatformLogger.class.getClassLoader());
- } catch (ClassNotFoundException ex) {
- throw new InternalError(ex);
+ /**
+ * Gets the name for this platform logger.
+ * @return the name of the platform logger.
+ */
+ public String getName();
+
+ /**
+ * Returns true if a message of the given level would actually
+ * be logged by this logger.
+ * @param level the level
+ * @return whether a message of that level would be logged
+ */
+ public boolean isLoggable(Level level);
+ public boolean isEnabled();
+
+ public void log(Level level, String msg);
+ public void log(Level level, String msg, Throwable thrown);
+ public void log(Level level, String msg, Object... params);
+ public void log(Level level, Supplier msgSupplier);
+ public void log(Level level, Throwable thrown, Supplier msgSupplier);
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg);
+ public void logp(Level level, String sourceClass, String sourceMethod,
+ Supplier msgSupplier);
+ public void logp(Level level, String sourceClass, String sourceMethod,
+ String msg, Object... params);
+ public void logp(Level level, String sourceClass, String sourceMethod,
+ String msg, Throwable thrown);
+ public void logp(Level level, String sourceClass, String sourceMethod,
+ Throwable thrown, Supplier msgSupplier);
+ public void logrb(Level level, String sourceClass, String sourceMethod,
+ ResourceBundle bundle, String msg, Object... params);
+ public void logrb(Level level, String sourceClass, String sourceMethod,
+ ResourceBundle bundle, String msg, Throwable thrown);
+ public void logrb(Level level, ResourceBundle bundle, String msg,
+ Object... params);
+ public void logrb(Level level, ResourceBundle bundle, String msg,
+ Throwable thrown);
+
+
+ public static Bridge convert(System.Logger logger) {
+ if (logger instanceof PlatformLogger.Bridge) {
+ return (Bridge) logger;
+ } else {
+ return new LoggerWrapper<>(logger);
+ }
+ }
+ }
+
+ /**
+ * The {@code PlatformLogger.ConfigurableBridge} interface is used to
+ * implement the deprecated {@link PlatformLogger#setLevel} method.
+ *
+ * PlatformLogger is now only a thin adaptation layer over the same
+ * loggers than returned by java.lang.System.getLogger(String name).
+ *
+ * The recommendation for JRE classes going forward is to use
+ * java.lang.System.getLogger(String name), which will
+ * use Lazy Loggers when possible and necessary.
+ *
+ */
+ public static interface ConfigurableBridge {
+
+ public abstract class LoggerConfiguration {
+ public abstract Level getPlatformLevel();
+ public abstract void setPlatformLevel(Level level);
+ }
+
+ public default LoggerConfiguration getLoggerConfiguration() {
+ return null;
+ }
+
+ public static LoggerConfiguration getLoggerConfiguration(PlatformLogger.Bridge logger) {
+ if (logger instanceof PlatformLogger.ConfigurableBridge) {
+ return ((ConfigurableBridge) logger).getLoggerConfiguration();
+ } else {
+ return null;
+ }
}
}
// Table of known loggers. Maps names to PlatformLoggers.
- private static Map> loggers =
+ private static final Map> loggers =
new HashMap<>();
/**
* Returns a PlatformLogger of a given name.
+ * @param name the name of the logger
+ * @return a PlatformLogger
*/
public static synchronized PlatformLogger getLogger(String name) {
PlatformLogger log = null;
@@ -198,56 +285,31 @@ public class PlatformLogger {
log = ref.get();
}
if (log == null) {
- log = new PlatformLogger(name);
+ log = new PlatformLogger(PlatformLogger.Bridge.convert(
+ // We pass PlatformLogger.class rather than the actual caller
+ // because we want PlatformLoggers to be system loggers: we
+ // won't need to resolve any resource bundles anyway.
+ // Note: Many unit tests depend on the fact that
+ // PlatformLogger.getLoggerFromFinder is not caller sensitive.
+ LazyLoggers.getLazyLogger(name, PlatformLogger.class)));
loggers.put(name, new WeakReference<>(log));
}
return log;
}
- /**
- * Initialize java.util.logging.Logger objects for all platform loggers.
- * This method is called from LogManager.readPrimordialConfiguration().
- */
- public static synchronized void redirectPlatformLoggers() {
- if (loggingEnabled || !LoggingSupport.isAvailable()) return;
-
- loggingEnabled = true;
- for (Map.Entry> entry : loggers.entrySet()) {
- WeakReference ref = entry.getValue();
- PlatformLogger plog = ref.get();
- if (plog != null) {
- plog.redirectToJavaLoggerProxy();
- }
- }
- }
-
- /**
- * Creates a new JavaLoggerProxy and redirects the platform logger to it
- */
- private void redirectToJavaLoggerProxy() {
- DefaultLoggerProxy lp = DefaultLoggerProxy.class.cast(this.loggerProxy);
- JavaLoggerProxy jlp = new JavaLoggerProxy(lp.name, lp.level);
- // the order of assignments is important
- this.javaLoggerProxy = jlp; // isLoggable checks javaLoggerProxy if set
- this.loggerProxy = jlp;
- }
-
- // DefaultLoggerProxy may be replaced with a JavaLoggerProxy object
- // when the java.util.logging facility is enabled
- private volatile LoggerProxy loggerProxy;
- // javaLoggerProxy is only set when the java.util.logging facility is enabled
- private volatile JavaLoggerProxy javaLoggerProxy;
- private PlatformLogger(String name) {
- if (loggingEnabled) {
- this.loggerProxy = this.javaLoggerProxy = new JavaLoggerProxy(name);
- } else {
- this.loggerProxy = new DefaultLoggerProxy(name);
- }
+ // The system loggerProxy returned by LazyLoggers
+ // This may be a lazy logger - see jdk.internal.logger.LazyLoggers,
+ // or may be a Logger instance (or a wrapper thereof).
+ //
+ private final PlatformLogger.Bridge loggerProxy;
+ private PlatformLogger(PlatformLogger.Bridge loggerProxy) {
+ this.loggerProxy = loggerProxy;
}
/**
* A convenience method to test if the logger is turned off.
* (i.e. its level is OFF).
+ * @return whether the logger is turned off.
*/
public boolean isEnabled() {
return loggerProxy.isEnabled();
@@ -255,22 +317,24 @@ public class PlatformLogger {
/**
* Gets the name for this platform logger.
+ * @return the name of the platform logger.
*/
public String getName() {
- return loggerProxy.name;
+ return loggerProxy.getName();
}
/**
* Returns true if a message of the given level would actually
* be logged by this logger.
+ * @param level the level
+ * @return whether a message of that level would be logged
*/
public boolean isLoggable(Level level) {
if (level == null) {
throw new NullPointerException();
}
- // performance-sensitive method: use two monomorphic call-sites
- JavaLoggerProxy jlp = javaLoggerProxy;
- return jlp != null ? jlp.isLoggable(level) : loggerProxy.isLoggable(level);
+
+ return loggerProxy.isLoggable(level);
}
/**
@@ -281,13 +345,15 @@ public class PlatformLogger {
* @return this PlatformLogger's level
*/
public Level level() {
- return loggerProxy.getLevel();
+ final ConfigurableBridge.LoggerConfiguration spi =
+ PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy);
+ return spi == null ? null : spi.getPlatformLevel();
}
/**
* Set the log level specifying which message levels will be
* logged by this logger. Message levels lower than this
- * value will be discarded. The level value {@link #OFF}
+ * value will be discarded. The level value {@link Level#OFF}
* can be used to turn off logging.
*
* If the new level is null, it means that this node should
@@ -295,366 +361,153 @@ public class PlatformLogger {
* (non-null) level value.
*
* @param newLevel the new value for the log level (may be null)
+ * @deprecated Platform Loggers should not be configured programmatically.
+ * This method will not work if a custom {@link
+ * java.lang.System.LoggerFinder} is installed.
*/
+ @Deprecated
public void setLevel(Level newLevel) {
- loggerProxy.setLevel(newLevel);
+ final ConfigurableBridge.LoggerConfiguration spi =
+ PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy);;
+ if (spi != null) {
+ spi.setPlatformLevel(newLevel);
+ }
}
/**
* Logs a SEVERE message.
+ * @param msg the message
*/
public void severe(String msg) {
- loggerProxy.doLog(Level.SEVERE, msg);
+ loggerProxy.log(Level.SEVERE, msg, (Object[])null);
}
public void severe(String msg, Throwable t) {
- loggerProxy.doLog(Level.SEVERE, msg, t);
+ loggerProxy.log(Level.SEVERE, msg, t);
}
public void severe(String msg, Object... params) {
- loggerProxy.doLog(Level.SEVERE, msg, params);
+ loggerProxy.log(Level.SEVERE, msg, params);
}
/**
* Logs a WARNING message.
+ * @param msg the message
*/
public void warning(String msg) {
- loggerProxy.doLog(Level.WARNING, msg);
+ loggerProxy.log(Level.WARNING, msg, (Object[])null);
}
public void warning(String msg, Throwable t) {
- loggerProxy.doLog(Level.WARNING, msg, t);
+ loggerProxy.log(Level.WARNING, msg, t);
}
public void warning(String msg, Object... params) {
- loggerProxy.doLog(Level.WARNING, msg, params);
+ loggerProxy.log(Level.WARNING, msg, params);
}
/**
* Logs an INFO message.
+ * @param msg the message
*/
public void info(String msg) {
- loggerProxy.doLog(Level.INFO, msg);
+ loggerProxy.log(Level.INFO, msg, (Object[])null);
}
public void info(String msg, Throwable t) {
- loggerProxy.doLog(Level.INFO, msg, t);
+ loggerProxy.log(Level.INFO, msg, t);
}
public void info(String msg, Object... params) {
- loggerProxy.doLog(Level.INFO, msg, params);
+ loggerProxy.log(Level.INFO, msg, params);
}
/**
* Logs a CONFIG message.
+ * @param msg the message
*/
public void config(String msg) {
- loggerProxy.doLog(Level.CONFIG, msg);
+ loggerProxy.log(Level.CONFIG, msg, (Object[])null);
}
public void config(String msg, Throwable t) {
- loggerProxy.doLog(Level.CONFIG, msg, t);
+ loggerProxy.log(Level.CONFIG, msg, t);
}
public void config(String msg, Object... params) {
- loggerProxy.doLog(Level.CONFIG, msg, params);
+ loggerProxy.log(Level.CONFIG, msg, params);
}
/**
* Logs a FINE message.
+ * @param msg the message
*/
public void fine(String msg) {
- loggerProxy.doLog(Level.FINE, msg);
+ loggerProxy.log(Level.FINE, msg, (Object[])null);
}
public void fine(String msg, Throwable t) {
- loggerProxy.doLog(Level.FINE, msg, t);
+ loggerProxy.log(Level.FINE, msg, t);
}
public void fine(String msg, Object... params) {
- loggerProxy.doLog(Level.FINE, msg, params);
+ loggerProxy.log(Level.FINE, msg, params);
}
/**
* Logs a FINER message.
+ * @param msg the message
*/
public void finer(String msg) {
- loggerProxy.doLog(Level.FINER, msg);
+ loggerProxy.log(Level.FINER, msg, (Object[])null);
}
public void finer(String msg, Throwable t) {
- loggerProxy.doLog(Level.FINER, msg, t);
+ loggerProxy.log(Level.FINER, msg, t);
}
public void finer(String msg, Object... params) {
- loggerProxy.doLog(Level.FINER, msg, params);
+ loggerProxy.log(Level.FINER, msg, params);
}
/**
* Logs a FINEST message.
+ * @param msg the message
*/
public void finest(String msg) {
- loggerProxy.doLog(Level.FINEST, msg);
+ loggerProxy.log(Level.FINEST, msg, (Object[])null);
}
public void finest(String msg, Throwable t) {
- loggerProxy.doLog(Level.FINEST, msg, t);
+ loggerProxy.log(Level.FINEST, msg, t);
}
public void finest(String msg, Object... params) {
- loggerProxy.doLog(Level.FINEST, msg, params);
+ loggerProxy.log(Level.FINEST, msg, params);
}
- /**
- * Abstract base class for logging support, defining the API and common field.
- */
- private abstract static class LoggerProxy {
- final String name;
+ // ------------------------------------
+ // Maps used for Level conversion
+ // ------------------------------------
- protected LoggerProxy(String name) {
- this.name = name;
- }
+ // This map is indexed by java.util.spi.Logger.Level.ordinal() and returns
+ // a PlatformLogger.Level
+ //
+ // ALL, TRACE, DEBUG, INFO, WARNING, ERROR, OFF
+ private static final Level[] spi2platformLevelMapping = {
+ Level.ALL, // mapped from ALL
+ Level.FINER, // mapped from TRACE
+ Level.FINE, // mapped from DEBUG
+ Level.INFO, // mapped from INFO
+ Level.WARNING, // mapped from WARNING
+ Level.SEVERE, // mapped from ERROR
+ Level.OFF // mapped from OFF
+ };
- abstract boolean isEnabled();
-
- abstract Level getLevel();
- abstract void setLevel(Level newLevel);
-
- abstract void doLog(Level level, String msg);
- abstract void doLog(Level level, String msg, Throwable thrown);
- abstract void doLog(Level level, String msg, Object... params);
-
- abstract boolean isLoggable(Level level);
+ public static Level toPlatformLevel(java.lang.System.Logger.Level level) {
+ if (level == null) return null;
+ assert level.ordinal() < spi2platformLevelMapping.length;
+ return spi2platformLevelMapping[level.ordinal()];
}
-
- private static final class DefaultLoggerProxy extends LoggerProxy {
- /**
- * Default platform logging support - output messages to System.err -
- * equivalent to ConsoleHandler with SimpleFormatter.
- */
- private static PrintStream outputStream() {
- return System.err;
- }
-
- volatile Level effectiveLevel; // effective level (never null)
- volatile Level level; // current level set for this node (may be null)
-
- DefaultLoggerProxy(String name) {
- super(name);
- this.effectiveLevel = deriveEffectiveLevel(null);
- this.level = null;
- }
-
- boolean isEnabled() {
- return effectiveLevel != Level.OFF;
- }
-
- Level getLevel() {
- return level;
- }
-
- void setLevel(Level newLevel) {
- Level oldLevel = level;
- if (oldLevel != newLevel) {
- level = newLevel;
- effectiveLevel = deriveEffectiveLevel(newLevel);
- }
- }
-
- void doLog(Level level, String msg) {
- if (isLoggable(level)) {
- outputStream().print(format(level, msg, null));
- }
- }
-
- void doLog(Level level, String msg, Throwable thrown) {
- if (isLoggable(level)) {
- outputStream().print(format(level, msg, thrown));
- }
- }
-
- void doLog(Level level, String msg, Object... params) {
- if (isLoggable(level)) {
- String newMsg = formatMessage(msg, params);
- outputStream().print(format(level, newMsg, null));
- }
- }
-
- boolean isLoggable(Level level) {
- Level effectiveLevel = this.effectiveLevel;
- return level.intValue() >= effectiveLevel.intValue() && effectiveLevel != Level.OFF;
- }
-
- // derive effective level (could do inheritance search like j.u.l.Logger)
- private Level deriveEffectiveLevel(Level level) {
- return level == null ? DEFAULT_LEVEL : level;
- }
-
- // Copied from java.util.logging.Formatter.formatMessage
- private String formatMessage(String format, Object... parameters) {
- // Do the formatting.
- try {
- if (parameters == null || parameters.length == 0) {
- // No parameters. Just return format string.
- return format;
- }
- // Is it a java.text style format?
- // Ideally we could match with
- // Pattern.compile("\\{\\d").matcher(format).find())
- // However the cost is 14% higher, so we cheaply check for
- // 1 of the first 4 parameters
- if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 ||
- format.indexOf("{2") >=0|| format.indexOf("{3") >=0) {
- return java.text.MessageFormat.format(format, parameters);
- }
- return format;
- } catch (Exception ex) {
- // Formatting failed: use format string.
- return format;
- }
- }
-
- private static final String formatString =
- LoggingSupport.getSimpleFormat(false); // don't check logging.properties
- private final ZoneId zoneId = ZoneId.systemDefault();
- private synchronized String format(Level level, String msg, Throwable thrown) {
- ZonedDateTime zdt = ZonedDateTime.now(zoneId);
- String throwable = "";
- if (thrown != null) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- pw.println();
- thrown.printStackTrace(pw);
- pw.close();
- throwable = sw.toString();
- }
-
- return String.format(formatString,
- zdt,
- getCallerInfo(),
- name,
- level.name(),
- msg,
- throwable);
- }
-
- // Returns the caller's class and method's name; best effort
- // if cannot infer, return the logger's name.
- private String getCallerInfo() {
- String sourceClassName = null;
- String sourceMethodName = null;
-
- JavaLangAccess access = SharedSecrets.getJavaLangAccess();
- Throwable throwable = new Throwable();
- int depth = access.getStackTraceDepth(throwable);
-
- String logClassName = "sun.util.logging.PlatformLogger";
- boolean lookingForLogger = true;
- for (int ix = 0; ix < depth; ix++) {
- // Calling getStackTraceElement directly prevents the VM
- // from paying the cost of building the entire stack frame.
- StackTraceElement frame =
- access.getStackTraceElement(throwable, ix);
- String cname = frame.getClassName();
- if (lookingForLogger) {
- // Skip all frames until we have found the first logger frame.
- if (cname.equals(logClassName)) {
- lookingForLogger = false;
- }
- } else {
- if (!cname.equals(logClassName)) {
- // We've found the relevant frame.
- sourceClassName = cname;
- sourceMethodName = frame.getMethodName();
- break;
- }
- }
- }
-
- if (sourceClassName != null) {
- return sourceClassName + " " + sourceMethodName;
- } else {
- return name;
- }
- }
- }
-
- /**
- * JavaLoggerProxy forwards all the calls to its corresponding
- * java.util.logging.Logger object.
- */
- private static final class JavaLoggerProxy extends LoggerProxy {
- // initialize javaLevel fields for mapping from Level enum -> j.u.l.Level object
- static {
- for (Level level : Level.values()) {
- level.javaLevel = LoggingSupport.parseLevel(level.name());
- }
- }
-
- private final /* java.util.logging.Logger */ Object javaLogger;
-
- JavaLoggerProxy(String name) {
- this(name, null);
- }
-
- JavaLoggerProxy(String name, Level level) {
- super(name);
- this.javaLogger = LoggingSupport.getLogger(name);
- if (level != null) {
- // level has been updated and so set the Logger's level
- LoggingSupport.setLevel(javaLogger, level.javaLevel);
- }
- }
-
- void doLog(Level level, String msg) {
- LoggingSupport.log(javaLogger, level.javaLevel, msg);
- }
-
- void doLog(Level level, String msg, Throwable t) {
- LoggingSupport.log(javaLogger, level.javaLevel, msg, t);
- }
-
- void doLog(Level level, String msg, Object... params) {
- if (!isLoggable(level)) {
- return;
- }
- // only pass String objects to the j.u.l.Logger which may
- // be created by untrusted code
- int len = (params != null) ? params.length : 0;
- Object[] sparams = new String[len];
- for (int i = 0; i < len; i++) {
- sparams [i] = String.valueOf(params[i]);
- }
- LoggingSupport.log(javaLogger, level.javaLevel, msg, sparams);
- }
-
- boolean isEnabled() {
- return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel);
- }
-
- /**
- * Returns the PlatformLogger.Level mapped from j.u.l.Level
- * set in the logger. If the j.u.l.Logger is set to a custom Level,
- * this method will return the nearest Level.
- */
- Level getLevel() {
- Object javaLevel = LoggingSupport.getLevel(javaLogger);
- if (javaLevel == null) return null;
-
- try {
- return Level.valueOf(LoggingSupport.getLevelName(javaLevel));
- } catch (IllegalArgumentException e) {
- return Level.valueOf(LoggingSupport.getLevelValue(javaLevel));
- }
- }
-
- void setLevel(Level level) {
- LoggingSupport.setLevel(javaLogger, level == null ? null : level.javaLevel);
- }
-
- boolean isLoggable(Level level) {
- return LoggingSupport.isLoggable(javaLogger, level.javaLevel);
- }
- }
}
diff --git a/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java b/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java
index 0af78546c21..3aec0a72d89 100644
--- a/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java
+++ b/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java
@@ -72,6 +72,8 @@ public final class FontUtilities {
static {
AccessController.doPrivileged(new PrivilegedAction