Merge
authorprr
Mon, 23 Nov 2015 10:00:50 -0800
changeset 34415 098d54b4051d
parent 34414 e496a8d8fc8a (current diff)
parent 34325 9752a6661f8e (diff)
child 34416 68c0d866db5d
Merge
jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java
jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java
jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java
--- a/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java	Mon Nov 23 10:00:50 2015 -0800
@@ -272,22 +272,6 @@
 
         // (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 @@
             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 @@
                 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 @@
 
                 assert(je.isDirectory() == name.endsWith("/"));
 
-                if (isClassFile(name)) {
+                if (inFile.mustProcess()) {
                     file = readClass(name, bits.getInputStream());
                 }
                 if (file == null) {
@@ -429,7 +436,7 @@
             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 @@
                 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();
--- a/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1584,7 +1584,7 @@
      * @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 @@
     }
 
     /* 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) {
--- a/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java	Mon Nov 23 10:00:50 2015 -0800
@@ -348,6 +348,19 @@
  *   {@code java.util.spi.LocaleServiceProvider}</a> for more
  *   information.</td>
  * </tr>
+ *
+ * <tr>
+ *   <td>loggerFinder</td>
+ *   <td>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.</td>
+ *   <td>See {@link java.lang.System.LoggerFinder java.lang.System.LoggerFinder}
+ *   for more information.</td>
+ * </tr>
  * </table>
  *
  * @implNote
--- a/jdk/src/java.base/share/classes/java/lang/System.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/System.java	Mon Nov 23 10:00:50 2015 -0800
@@ -30,13 +30,14 @@
 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 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 <code>System</code> class contains several useful class fields
@@ -944,6 +948,648 @@
     }
 
     /**
+     * {@code System.Logger} instances log messages that will be
+     * routed to the underlying logging framework the {@link System.LoggerFinder
+     * LoggerFinder} uses.
+     * <p>
+     * {@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.
+         * <p>
+         * 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.
+         * <br>
+         * {@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}.
+         * <p>
+         * <b>Severity values and Mapping to {@code java.util.logging.Level}.</b>
+         * <p>
+         * {@linkplain System.Logger.Level System logger levels} are mapped to
+         * {@linkplain java.util.logging.Level  java.util.logging levels}
+         * of corresponding severity.
+         * <br>The mapping is as follows:
+         * <br><br>
+         * <table border="1">
+         * <caption>System.Logger Severity Level Mapping</caption>
+         * <tr><td><b>System.Logger Levels</b></td>
+         * <td>{@link Logger.Level#ALL ALL}</td>
+         * <td>{@link Logger.Level#TRACE TRACE}</td>
+         * <td>{@link Logger.Level#DEBUG DEBUG}</td>
+         * <td>{@link Logger.Level#INFO INFO}</td>
+         * <td>{@link Logger.Level#WARNING WARNING}</td>
+         * <td>{@link Logger.Level#ERROR ERROR}</td>
+         * <td>{@link Logger.Level#OFF OFF}</td>
+         * </tr>
+         * <tr><td><b>java.util.logging Levels</b></td>
+         * <td>{@link java.util.logging.Level#ALL ALL}</td>
+         * <td>{@link java.util.logging.Level#FINER FINER}</td>
+         * <td>{@link java.util.logging.Level#FINE FINE}</td>
+         * <td>{@link java.util.logging.Level#INFO INFO}</td>
+         * <td>{@link java.util.logging.Level#WARNING WARNING}</td>
+         * <td>{@link java.util.logging.Level#SEVERE SEVERE}</td>
+         * <td>{@link java.util.logging.Level#OFF OFF}</td>
+         * </tr>
+         * </table>
+         *
+         * @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.
+         * <p>
+         * 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<String> 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.
+         * <p>
+         * 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.
+         * <p>
+         * 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<String> 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.
+         * <p>
+         * 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.
+         * <p>
+         * 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.
+     * <p>
+     * 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:
+     * <ul>
+     *    <li>First it finds any custom {@code LoggerFinder} provider
+     *        using the {@link java.util.ServiceLoader} facility with the
+     *        {@linkplain ClassLoader#getSystemClassLoader() system class
+     *        loader}.</li>
+     *    <li>If no {@code LoggerFinder} provider is found, the system default
+     *        {@code LoggerFinder} implementation will be used.</li>
+     * </ul>
+     * <p>
+     * An application can replace the logging backend
+     * <i>even when the java.logging module is present</i>, by simply providing
+     * and declaring an implementation of the {@link LoggerFinder} service.
+     * <p>
+     * <b>Default Implementation</b>
+     * <p>
+     * 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}).
+     * <p>
+     * <b>Logging Configuration</b>
+     * <p>
+     * {@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.
+     * <p>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.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <br>
+     * 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.
+     * <p>
+     * <b>Message Levels and Mapping to backend levels</b>
+     * <p>
+     * A logger finder is responsible for mapping from a {@code
+     * System.Logger.Level} to a level supported by the logging backend it uses.
+     * <br>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&lt;String&gt;} will not be
+         * localized, as it is assumed that such strings are messages which are
+         * already constructed, rather than keys in a resource bundle.
+         * <p>
+         * 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<LoggerFinder> 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<Logger>)
+                    () -> 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
      * code indicates abnormal termination.
--- a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1781,23 +1781,27 @@
         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));
         }
 
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java	Mon Nov 23 10:00:50 2015 -0800
@@ -872,13 +872,54 @@
      * @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 <em>array-spreading</em> 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 <i>target</i>,
+     * 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}.
+     * <p>
+     * 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.
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@code
+    MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class));
+    MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2);
+    Object[] ints = new Object[]{3, 9, 7, 7};
+    Comparator<Integer> cmp = (a, b) -> a - b;
+    assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0);
+    assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0);
+    assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0);
+     * }</pre></blockquote>
+     * @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
+     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
+     * @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 @@
      * 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 @@
             }
         }
         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 @@
      * @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 <em>array-collecting</em> 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
+     * <i>target</i>, 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}.
+     * <p>
+     * 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.
+     * <p>
+     * @apiNote Examples:
+     * <blockquote><pre>{@code
+    StringWriter swr = new StringWriter();
+    MethodHandle swWrite = LOOKUP.findVirtual(StringWriter.class, "write", methodType(void.class, char[].class, int.class, int.class)).bindTo(swr);
+    MethodHandle swWrite4 = swWrite.asCollector(0, char[].class, 4);
+    swWrite4.invoke('A', 'B', 'C', 'D', 1, 2);
+    assertEquals("BC", swr.toString());
+    swWrite4.invoke('P', 'Q', 'R', 'S', 0, 4);
+    assertEquals("BCPQRS", swr.toString());
+    swWrite4.invoke('W', 'X', 'Y', 'Z', 3, 1);
+    assertEquals("BCPQRSZ", swr.toString());
+     * }</pre></blockquote>
+     * @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
+     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
+     * @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 @@
      * 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 (pos < 0 || pos >= nargs) {
+            throw newIllegalArgumentException("bad collect position");
+        }
         if (nargs != 0) {
-            Class<?> lastParam = type().parameterType(nargs-1);
-            if (lastParam == arrayType)  return true;
-            if (lastParam.isAssignableFrom(arrayType))  return false;
+            Class<?> param = type().parameterType(pos);
+            if (param == arrayType)  return true;
+            if (param.isAssignableFrom(arrayType))  return false;
         }
-        throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType);
+        throw newIllegalArgumentException("array type not assignable to argument", this, arrayType);
     }
 
     /**
@@ -1178,7 +1268,7 @@
      */
     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 @@
         // cannot be cracked into MethodHandleInfo.
         assert viewAsTypeChecks(newType, strict);
         BoundMethodHandle mh = rebind();
-        assert(!((MethodHandle)mh instanceof DirectMethodHandle));
         return mh.copyWith(newType, mh.form);
     }
 
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Mon Nov 23 10:00:50 2015 -0800
@@ -27,16 +27,17 @@
 
 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 @@
         @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 @@
         }
     }
 
+    /**
+     * 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<Class<?>> targs, List<Class<?>> tvars, List<MethodHandle> init,
+                                 List<MethodHandle> step, List<MethodHandle> pred, List<MethodHandle> 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<MethodHandle> 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<Class<?>> 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 @@
                     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);
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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.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 @@
      * equivalent of a particular <em>bytecode behavior</em>.
      * (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:
      * <table border=1 cellpadding=5 summary="lookup method behaviors">
      * <tr>
      *     <th><a name="equiv"></a>lookup expression</th>
@@ -235,6 +234,10 @@
      *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
      *     <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
      * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findClass lookup.findClass("C")}</td>
+     *     <td>{@code class C { ... }}</td><td>{@code C.class;}</td>
+     * </tr>
      * </table>
      *
      * Here, the type {@code C} is the class or interface being searched for a member,
@@ -255,6 +258,10 @@
      * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand
      * for reflective objects corresponding to the given members.
      * <p>
+     * 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.
+     * <p>
      * 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 @@
      * and the Core Reflection API
      * (as found on {@link java.lang.Class Class}).
      * <p>
-     * 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 @@
      * {@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 <em>not present</em>
      * if the current lookup object does not have
      * <a href="MethodHandles.Lookup.html#privacc">private access</a>.
@@ -444,11 +453,16 @@
      *     then {@link SecurityManager#checkPackageAccess
      *     smgr.checkPackageAccess(refcPkg)} is called,
      *     where {@code refcPkg} is the package of {@code refc}.
-     * <li><b>Step 2:</b>
+     * <li><b>Step 2a:</b>
      *     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.
+     * <li><b>Step 2b:</b>
+     *     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.
      * <li><b>Step 3:</b>
      *     If the retrieved member is not public,
      *     and if {@code lookc} is not present,
@@ -458,9 +472,9 @@
      *     where {@code defcPkg} is the package of {@code defc}.
      * </ul>
      * 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.
      *
      * <h1><a name="callsens"></a>Caller sensitive methods</h1>
      * A small number of Java methods have a special property called caller sensitivity.
@@ -922,6 +936,49 @@
         }
 
         /**
+         * 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
+         *            <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @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
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @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
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @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,
          * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
@@ -995,7 +1052,7 @@
          */
         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 @@
          * @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 @@
                 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 @@
 
         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 @@
     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 @@
      */
     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 @@
                     .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 @@
         return MethodHandleImpl.makeGuardWithTest(test, target, fallback);
     }
 
-    static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) {
+    static <T> RuntimeException misMatchedTypes(String what, T t1, T t2) {
         return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2);
     }
 
@@ -3057,6 +3112,7 @@
      *          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 @@
             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.
+     * <p>
+     * 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:<ul>
+     * <li>Before the loop executes, the initialization of an iteration variable or loop invariant local.
+     * <li>When a clause executes, an update step for the iteration variable.
+     * <li>When a clause executes, a predicate execution to test for loop exit.
+     * <li>If a clause causes a loop exit, a finalizer execution to compute the loop's return value.
+     * </ul>
+     * <p>
+     * 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.
+     * <p>
+     * Each clause function, with the exception of clause initializers, is able to observe the entire loop state,
+     * because it will be passed <em>all</em> 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}.
+     * <p>
+     * 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.
+     * <p>
+     * <em>Step 0: Determine clause structure.</em><ol type="a">
+     * <li>The clause array (of type {@code MethodHandle[][]} must be non-{@code null} and contain at least one element.
+     * <li>The clause array may not contain {@code null}s or sub-arrays longer than four elements.
+     * <li>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.
+     * <li>Clauses with all {@code null}s are disregarded.
+     * <li>Each clause is treated as a four-tuple of functions, called "init", "step", "pred", and "fini".
+     * </ol>
+     * <p>
+     * <em>Step 1A: Determine iteration variables.</em><ol type="a">
+     * <li>Examine init and step function return types, pairwise, to determine each clause's iteration variable type.
+     * <li>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).
+     * <li>Form the list of return types (in clause order), omitting all occurrences of {@code void}.
+     * <li>This list of types is called the "common prefix".
+     * </ol>
+     * <p>
+     * <em>Step 1B: Determine loop parameters.</em><ol type="a">
+     * <li>Examine init function parameter lists.
+     * <li>Omitted init functions are deemed to have {@code null} parameter lists.
+     * <li>All init function parameter lists must be effectively identical.
+     * <li>The longest parameter list (which is necessarily unique) is called the "common suffix".
+     * </ol>
+     * <p>
+     * <em>Step 1C: Determine loop return type.</em><ol type="a">
+     * <li>Examine fini function return types, disregarding omitted fini functions.
+     * <li>If there are no fini functions, use {@code void} as the loop return type.
+     * <li>Otherwise, use the common return type of the fini functions; they must all be identical.
+     * </ol>
+     * <p>
+     * <em>Step 1D: Check other types.</em><ol type="a">
+     * <li>There must be at least one non-omitted pred function.
+     * <li>Every non-omitted pred function must have a {@code boolean} return type.
+     * </ol>
+     * <p>
+     * (Implementation Note: Steps 1A, 1B, 1C, 1D are logically independent of each other, and may be performed in any
+     * order.)
+     * <p>
+     * <em>Step 2: Determine parameter lists.</em><ol type="a">
+     * <li>The parameter list for the resulting loop handle will be the "common suffix".
+     * <li>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.)
+     * <li>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".
+     * <li>Every non-init, non-omitted function parameter list must be effectively identical to the common parameter
+     * sequence.
+     * </ol>
+     * <p>
+     * <em>Step 3: Fill in omitted functions.</em><ol type="a">
+     * <li>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}.)
+     * <li>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.)
+     * <li>If a pred function is omitted, the corresponding fini function must also be omitted.
+     * <li>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.)
+     * <li>If a fini function is omitted, use a constant {@code null}/zero/{@code false}/{@code void} function of the
+     * loop return type.
+     * </ol>
+     * <p>
+     * <em>Step 4: Fill in missing parameter types.</em><ol type="a">
+     * <li>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}.
+     * <li>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}.
+     * </ol>
+     * <p>
+     * <em>Final observations.</em><ol type="a">
+     * <li>After these steps, all clauses have been adjusted by supplying omitted functions and arguments.
+     * <li>All init functions have a common parameter type list, which the final loop handle will also have.
+     * <li>All fini functions have a common return type, which the final loop handle will also have.
+     * <li>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.
+     * <li>Each pair of init and step functions agrees in their return types.
+     * <li>Each non-init function will be able to observe the current values of all iteration variables, by means of the
+     * common prefix.
+     * </ol>
+     * <p>
+     * <em>Loop execution.</em><ol type="a">
+     * <li>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.
+     * <li>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).
+     * <li>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).
+     * <li>The step and pred functions are then executed, in clause order (step before pred), until a pred function
+     * returns {@code false}.
+     * <li>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.
+     * <li>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.
+     * </ol>
+     * <p>
+     * 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.
+     * <blockquote><pre>{@code
+     * V... init...(A...);
+     * boolean pred...(V..., A...);
+     * V... step...(V..., A...);
+     * R fini...(V..., A...);
+     * R loop(A... a) {
+     *   V... v... = init...(a...);
+     *   for (;;) {
+     *     for ((v, p, s, f) in (v..., pred..., step..., fini...)) {
+     *       v = s(v..., a...);
+     *       if (!p(v..., a...)) {
+     *         return f(v..., a...);
+     *       }
+     *     }
+     *   }
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@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));
+     * }</pre></blockquote>
+     *
+     * @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<MethodHandle> init = new ArrayList<>();
+        List<MethodHandle> step = new ArrayList<>();
+        List<MethodHandle> pred = new ArrayList<>();
+        List<MethodHandle> 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<Class<?>> 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<Class<?>> commonPrefix = iterationVariableTypes.stream().filter(t -> t != void.class).
+                collect(Collectors.toList());
+
+        // Step 1B: determine loop parameters.
+        final List<Class<?>> empty = new ArrayList<>();
+        final List<Class<?>> 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<Class<?>> 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<MethodHandle> finit = fillParameterTypes(init, commonSuffix);
+        List<MethodHandle> fstep = fillParameterTypes(step, commonParameterSequence);
+        List<MethodHandle> fpred = fillParameterTypes(pred, commonParameterSequence);
+        List<MethodHandle> 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<MethodHandle> fillParameterTypes(List<MethodHandle> hs, final List<Class<?>> 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}.
+     * <p>
+     * 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}.
+     * <p>
+     * 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.
+     * <blockquote><pre>{@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;
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@code
+     * // implement the zip function for lists as a loop handle
+     * List<String> initZip(Iterator<String> a, Iterator<String> b) { return new ArrayList<>(); }
+     * boolean zipPred(List<String> zip, Iterator<String> a, Iterator<String> b) { return a.hasNext() && b.hasNext(); }
+     * List<String> zipStep(List<String> zip, Iterator<String> a, Iterator<String> 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<String> a = Arrays.asList("a", "b", "c", "d");
+     * List<String> b = Arrays.asList("e", "f", "g", "h");
+     * List<String> zipped = Arrays.asList("a", "e", "b", "f", "c", "g", "d", "h");
+     * assertEquals(zipped, (List<String>) loop.invoke(a.iterator(), b.iterator()));
+     * }</pre></blockquote>
+     *
+     * <p>
+     * @implSpec The implementation of this method is equivalent to:
+     * <blockquote><pre>{@code
+     * MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) {
+     *     MethodHandle[]
+     *         checkExit = {null, null, pred, identity(init.type().returnType())},
+     *         varBody = {init, body};
+     *     return loop(checkExit, varBody);
+     * }
+     * }</pre></blockquote>
+     *
+     * @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}.
+     * <p>
+     * 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}.
+     * <p>
+     * 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.
+     * <blockquote><pre>{@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;
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@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));
+     * }</pre></blockquote>
+     *
+     * <p>
+     * @implSpec The implementation of this method is equivalent to:
+     * <blockquote><pre>{@code
+     * MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) {
+     *     MethodHandle[] clause = { init, body, pred, identity(init.type().returnType()) };
+     *     return loop(clause);
+     * }
+     * }</pre></blockquote>
+     *
+     *
+     * @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}.
+     * <p>
+     * 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}.
+     * <p>
+     * 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.
+     * <blockquote><pre>{@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;
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@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!"));
+     * }</pre></blockquote>
+     *
+     * <p>
+     * @implSpec The implementation of this method is equivalent to:
+     * <blockquote><pre>{@code
+     * MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
+     *     return countedLoop(null, iterations, init, body);  // null => constant zero
+     * }
+     * }</pre></blockquote>
+     *
+     * @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}.
+     * <p>
+     * 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}.
+     * <p>
+     * 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.
+     * <blockquote><pre>{@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;
+     * }
+     * }</pre></blockquote>
+     *
+     * <p>
+     * @implSpec The implementation of this method is equivalent to:
+     * <blockquote><pre>{@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<y of type int
+     *     MethodHandle[]
+     *         indexVar = {start, MH_increment}, // i = start; i = i+1
+     *         loopLimit = {end, null, MH_lessThan, returnVar }, // i<end
+     *         bodyClause = {init, dropArguments(body, 1, int.class)};  // v = body(i, v);
+     *     return loop(indexVar, loopLimit, bodyClause);
+     * }
+     * }</pre></blockquote>
+     *
+     * @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<T>}.
+     * 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}.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <blockquote><pre>{@code
+     * Iterator<T> iterator(A);  // defaults to Iterable::iterator
+     * V init(A);
+     * V body(T,V,A);
+     * V iteratedLoop(A a) {
+     *   Iterator<T> it = iterator(a);
+     *   V v = init(a);
+     *   for (T t : it) {
+     *     v = body(t, v, a);
+     *   }
+     *   return v;
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * The type {@code T} may be either a primitive or reference.
+     * Since type {@code Iterator<T>} 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}.
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@code
+     * // reverse a list
+     * List<String> reverseStep(String e, List<String> r) {
+     *   r.add(0, e);
+     *   return r;
+     * }
+     * List<String> 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<String> list = Arrays.asList("a", "b", "c", "d", "e");
+     * List<String> reversedList = Arrays.asList("e", "d", "c", "b", "a");
+     * assertEquals(reversedList, (List<String>) loop.invoke(list));
+     * }</pre></blockquote>
+     * <p>
+     * @implSpec The implementation of this method is equivalent to:
+     * <blockquote><pre>{@code
+     * MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) {
+     *     // assume MH_next and MH_hasNext are handles to methods of Iterator
+     *     Class<?> itype = iterator.type().returnType();
+     *     Class<?> ttype = body.type().parameterType(0);
+     *     MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, itype);
+     *     MethodHandle nextVal = MH_next.asType(MH_next.type().changeReturnType(ttype));
+     *     MethodHandle[]
+     *         iterVar = {iterator, null, MH_hasNext, returnVar}, // it = iterator(); while (it.hasNext)
+     *         bodyClause = {init, filterArgument(body, 0, nextVal)};  // v = body(t, v, a);
+     *     return loop(iterVar, bodyClause);
+     * }
+     * }</pre></blockquote>
+     *
+     * @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.
+     * <p>
+     * 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.)
+     * <p>
+     * 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:<ul>
+     * <li>a {@code Throwable}, which will carry the exception thrown by the {@code target} handle (if any); and
+     * <li>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}.
+     * </ul>
+     * <p>
+     * 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.
+     * <blockquote><pre>{@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;
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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<Class<?>> targetParamTypes = target.type().parameterList();
+        List<Class<?>> 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.
+     * <p>
+     * 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.
+     * <p>
+     * @apiNote Example:
+     * <blockquote><pre>{@code
+    import static java.lang.invoke.MethodHandles.*;
+    import static java.lang.invoke.MethodType.*;
+    ...
+    MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
+    "println", methodType(void.class, String.class))
+    .bindTo(System.out);
+    MethodHandle cat = lookup().findVirtual(String.class,
+    "concat", methodType(String.class, String.class));
+    assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+    MethodHandle catTrace = foldArguments(cat, 1, trace);
+    // also prints "jum":
+    assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+     * }</pre></blockquote>
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@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...);
+     * }
+     * }</pre></blockquote>
+     *
+     * @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<MethodHandle> init, List<Class<?>> 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<MethodHandle> pred, List<MethodHandle> 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<MethodHandle> step, List<MethodHandle> pred, List<MethodHandle> fini, List<Class<?>> 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<Class<?>> 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());
+        }
+    }
+
 }
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java	Mon Nov 23 10:00:50 2015 -0800
@@ -469,12 +469,13 @@
 
     /** 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<?> 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 @@
 
     /** 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 @@
         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));
         }
     }
 
--- a/jdk/src/java.base/share/classes/java/util/TreeMap.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/java/util/TreeMap.java	Mon Nov 23 10:00:50 2015 -0800
@@ -2581,19 +2581,17 @@
     }
 
     /**
-     * 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);
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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 <L> Type of the wrapped Logger: {@code Logger} or
+ *        an extension of that interface.
+ *
+ */
+abstract class AbstractLoggerWrapper<L extends Logger>
+    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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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<ExecutorService> executorRef;
+        private static ExecutorService getExecutor() {
+            WeakReference<ExecutorService> 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<Thread>() {
+                @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<ExecutorService> 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<ExecutorService> 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<Logger> 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<String> 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<String> 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<String> 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<String> 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<String> 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<Void>) () -> {
+                        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<String> msgSupplier) {
+            return new LogEvent(Objects.requireNonNull(bootstrap),
+                    Objects.requireNonNull(level), null, null, msgSupplier, null, null);
+        }
+        static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level,
+                               Supplier<String> 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<String> 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<Void>) () -> {
+                    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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<LoggingBackend>() {
+                    @Override
+                    public LoggingBackend run() {
+                        final Iterator<LoggerFinder> 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<DefaultLoggerFinder> 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<LazyLoggerAccessor, SimpleConsoleLogger> {
+
+        // all accesses must be synchronized on the outer BootstrapLogger.class
+        final Map<LazyLoggerAccessor, SimpleConsoleLogger> 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<LazyLoggerAccessor, SimpleConsoleLogger> drainTemporaryLoggers() {
+            if (temporaryLoggers.isEmpty()) return null;
+            if (cleared) throw new IllegalStateException("LoggerFinder already initialized");
+            final Map<LazyLoggerAccessor, SimpleConsoleLogger> accessors = new HashMap<>(temporaryLoggers);
+            temporaryLoggers.clear();
+            cleared = true;
+            return accessors;
+        }
+
+        static void resetTemporaryLoggers(Map<LazyLoggerAccessor, SimpleConsoleLogger> 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<LazyLoggerAccessor, SimpleConsoleLogger> 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<LazyLoggerAccessor, SimpleConsoleLogger>
+         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<LazyLoggerAccessor, SimpleConsoleLogger> 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();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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<String, Reference<Logger>> loggers =
+                new HashMap<>();
+        private final ReferenceQueue<Logger> queue = new ReferenceQueue<>();
+
+        synchronized Logger get(Function<String, Logger> 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<Reference<Logger>> 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);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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 <L> {@link Logger} or a subclass of {@link Logger}.
+     */
+    private static final class LazyLoggerFactories<L extends Logger> {
+
+        /**
+         * A factory method to create an SPI logger.
+         * Usually, this will be something like LazyLoggers::getSystemLogger.
+         */
+        final BiFunction<String, Class<?>, L> loggerSupplier;
+
+
+        public LazyLoggerFactories(BiFunction<String, Class<?>, L> loggerSupplier) {
+            this(Objects.requireNonNull(loggerSupplier),
+                 (Void)null);
+        }
+
+        private LazyLoggerFactories(BiFunction<String, Class<?>, 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<Class<?>> 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<Class<?>>(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 <T> 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<Logger> {
+
+        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>)LoggerFinder::getLoggerFinder);
+        }
+        return provider;
+    }
+
+    // Avoid using lambda here as lazy loggers could be created early
+    // in the bootstrap sequence...
+    private static final BiFunction<String, Class<?>, Logger> loggerSupplier =
+           new BiFunction<>() {
+        @Override
+        public Logger apply(String name, Class<?> caller) {
+            return LazyLoggers.getLoggerFromFinder(name, caller);
+        }
+    };
+
+    private static final LazyLoggerFactories<Logger> 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<Logger>)
+                    () -> {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);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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)}.
+ * <p>
+ * Note that methods that take a {@link Supplier Supplier&lt;String&gt;}
+ * or an Object are not redirected. It is assumed that a string returned
+ * by a {@code Supplier<String>} is already localized, or cannot be localized.
+ *
+ * @param <L> Type of the wrapped Logger: {@code Logger} or an
+ *        extension of the {@code Logger} interface.
+ */
+public class LocalizedLoggerWrapper<L extends Logger> extends LoggerWrapper<L> {
+
+    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<String> 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<String> msgSupplier) {
+        wrapped.log(level, msgSupplier);
+    }
+
+    @Override
+    public final void log(Level level, Supplier<String> 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);
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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("<<ALL FILES>>",
+                    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<String> 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<Boolean> ensureSingletonProvider =
+                () -> Boolean.getBoolean("jdk.logger.finder.singleton");
+        return AccessController.doPrivileged(ensureSingletonProvider);
+    }
+
+    private static Iterator<System.LoggerFinder> findLoggerFinderProviders() {
+        final Iterator<System.LoggerFinder> iterator;
+        if (System.getSecurityManager() == null) {
+            iterator = ServiceLoader.load(System.LoggerFinder.class,
+                        ClassLoader.getSystemClassLoader()).iterator();
+        } else {
+            final PrivilegedAction<Iterator<System.LoggerFinder>> 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<System.LoggerFinder> 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<DefaultLoggerFinder> 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<Iterator<DefaultLoggerFinder>> 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();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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 <L> Type of the wrapped Logger: {@code Logger} or an
+ *            extension of that interface.
+ */
+public class LoggerWrapper<L extends Logger> extends AbstractLoggerWrapper<L> {
+
+    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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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<String, String> defaultPropertyGetter) {
+        return Formatting.getSimpleFormat(defaultPropertyGetter);
+    }
+
+    public static boolean skipLoggingFrame(String cname) {
+        return Formatting.skipLoggingFrame(cname);
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Supplier<String> msgSupplier) {
+        if (isLoggable(level)) {
+            publish(getCallerInfo(), logLevel(level), msgSupplier.get());
+        }
+    }
+
+    @Override
+    public void log(PlatformLogger.Level level, Throwable thrown,
+            Supplier<String> 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<String> 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<String> 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<String>)
+                () -> 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<skips.length; i++) {
+                    if (!skips[i].isEmpty() && cname.startsWith(skips[i])) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        static String getSimpleFormat(Function<String, String> 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<String> here.
+            String format =
+                AccessController.doPrivileged(new PrivilegedAction<String>() {
+                @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<len-2; i++) {
+                    final char c = format.charAt(i);
+                    if (c == '{') {
+                        final int d = format.charAt(i+1);
+                        if (d >= '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;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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.
+ */
+
+/**
+ * <b>[JDK INTERNAL]</b>
+ * 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.
+ * <p>
+ * <b>Default Implementation</b>
+ * <p>
+ * 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.
+ * <br>
+ * 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.
+ * <p>
+ * <b>Message Levels and Mapping to java.util.logging</b>
+ * <p>
+ * 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;
--- a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java	Mon Nov 23 10:00:50 2015 -0800
@@ -977,4 +977,69 @@
         }
         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";
+        }
+    }
 }
--- a/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java	Mon Nov 23 09:58:44 2015 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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<String> 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);
-}
--- a/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java	Mon Nov 23 09:58:44 2015 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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<LoggingProxy>() {
-            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<String> 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<String>() {
-                    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;
-    }
-
-}
--- a/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -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 @@
  * 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 @@
  * The platform loggers are designed for JDK developers use and
  * this limitation can be workaround with setting
  * -Djava.util.logging.config.file system property.
+ * <br>
+ * 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 @@
         }
     }
 
-    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 {
+
+        /**
+         * 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();
 
-        // 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);
+        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<String> msgSupplier);
+        public void log(Level level, Throwable thrown, Supplier<String> msgSupplier);
+        public void logp(Level level, String sourceClass, String sourceMethod, String msg);
+        public void logp(Level level, String sourceClass, String sourceMethod,
+                         Supplier<String> 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<String> 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<String,WeakReference<PlatformLogger>> loggers =
+    private static final Map<String,WeakReference<PlatformLogger>> 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 @@
             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<String, WeakReference<PlatformLogger>> entry : loggers.entrySet()) {
-            WeakReference<PlatformLogger> 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 @@
 
     /**
      * 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 @@
      * @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.
      * <p>
      * If the new level is null, it means that this node should
@@ -295,366 +361,153 @@
      * (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);
-    }
-
-    /**
-     * Abstract base class for logging support, defining the API and common field.
-     */
-    private abstract static class LoggerProxy {
-        final String name;
-
-        protected LoggerProxy(String name) {
-            this.name = name;
-        }
-
-        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);
+        loggerProxy.log(Level.FINEST, msg, params);
     }
 
-
-    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;
-        }
+    // ------------------------------------
+    // Maps used for Level conversion
+    // ------------------------------------
 
-        // 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();
-            }
+    // 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
+    };
 
-            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;
-            }
-        }
+    public static Level toPlatformLevel(java.lang.System.Logger.Level level) {
+        if (level == null) return null;
+        assert level.ordinal() < spi2platformLevelMapping.length;
+        return spi2platformLevelMapping[level.ordinal()];
     }
 
-    /**
-     * 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);
-        }
-    }
 }
--- a/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java	Mon Nov 23 10:00:50 2015 -0800
@@ -72,6 +72,8 @@
     static {
 
         AccessController.doPrivileged(new PrivilegedAction<Object>() {
+            @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated.
+            @Override
             public Object run() {
                 String osName = System.getProperty("os.name", "unknownOS");
                 isSolaris = osName.startsWith("SunOS");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+sun.util.logging.internal.LoggingProviderImpl
--- a/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java	Mon Nov 23 10:00:50 2015 -0800
@@ -43,6 +43,7 @@
 import jdk.internal.misc.JavaAWTAccess;
 import jdk.internal.misc.SharedSecrets;
 import sun.misc.ManagedLocalsThread;
+import sun.util.logging.internal.LoggingProviderImpl;
 
 /**
  * There is a single global LogManager object that is used to
@@ -436,7 +437,8 @@
                 readConfiguration();
 
                 // Platform loggers begin to delegate to java.util.logging.Logger
-                sun.util.logging.PlatformLogger.redirectPlatformLoggers();
+                jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers();
+
             } catch (Exception ex) {
                 assert false : "Exception raised while reading logging configuration: " + ex;
             }
@@ -1481,7 +1483,7 @@
      * <p>
      * Any {@linkplain #addConfigurationListener registered configuration
      * listener} will be invoked after the properties are read.
-     * <p>
+     *
      * @apiNote This {@code readConfiguration} method should only be used for
      * initializing the configuration during LogManager initialization or
      * used with the "java.util.logging.config.class" property.
@@ -2363,7 +2365,8 @@
         }
     }
 
-    static final Permission controlPermission = new LoggingPermission("control", null);
+    static final Permission controlPermission =
+            new LoggingPermission("control", null);
 
     void checkPermission() {
         SecurityManager sm = System.getSecurityManager();
@@ -2607,4 +2610,69 @@
         if (t instanceof RuntimeException) throw (RuntimeException)t;
     }
 
+    /**
+     * This class allows the {@link LoggingProviderImpl} to demand loggers on
+     * behalf of system and application classes.
+     */
+    private static final class LoggingProviderAccess
+        implements LoggingProviderImpl.LogManagerAccess,
+                   PrivilegedAction<Void> {
+
+        private LoggingProviderAccess() {
+        }
+
+        /**
+         * Demands a logger on behalf of the given {@code caller}.
+         * <p>
+         * If a named logger suitable for the given caller is found
+         * returns it.
+         * Otherwise, creates a new logger suitable for the given caller.
+         *
+         * @param name   The logger name.
+         * @param caller The caller on which behalf the logger is created/retrieved.
+         * @return A logger for the given {@code caller}.
+         *
+         * @throws NullPointerException if {@code name} is {@code null}
+         *         or {@code caller} is {@code null}.
+         * @throws IllegalArgumentException if {@code manager} is not the default
+         *         LogManager.
+         * @throws SecurityException if a security manager is present and the
+         *         calling code doesn't have the
+         *        {@link LoggingPermission LoggingPermission("demandLogger", null)}.
+         */
+        @Override
+        public Logger demandLoggerFor(LogManager manager, String name, /* Module */ Class<?> caller) {
+            if (manager != getLogManager()) {
+                // having LogManager as parameter just ensures that the
+                // caller will have initialized the LogManager before reaching
+                // here.
+                throw new IllegalArgumentException("manager");
+            }
+            Objects.requireNonNull(name);
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(controlPermission);
+            }
+            if (caller.getClassLoader() == null) {
+                return manager.demandSystemLogger(name,
+                    Logger.SYSTEM_LOGGER_RB_NAME, caller);
+            } else {
+                return manager.demandLogger(name, null, caller);
+            }
+        }
+
+        @Override
+        public Void run() {
+            LoggingProviderImpl.setLogManagerAccess(INSTANCE);
+            return null;
+        }
+
+        static final LoggingProviderAccess INSTANCE = new LoggingProviderAccess();
+    }
+
+    static {
+        AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null,
+                                      controlPermission);
+    }
+
 }
--- a/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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
@@ -33,6 +33,7 @@
 
 import jdk.internal.misc.JavaLangAccess;
 import jdk.internal.misc.SharedSecrets;
+import static jdk.internal.logger.SimpleConsoleLogger.skipLoggingFrame;
 
 /**
  * LogRecord objects are used to pass logging requests between
@@ -637,6 +638,27 @@
     }
 
     // Private method to infer the caller's class and method names
+    //
+    // Note:
+    // For testing purposes - it is possible to customize the process
+    // by which LogRecord will infer the source class name and source method name
+    // when analyzing the call stack.
+    // <p>
+    // The system property {@code jdk.logger.packages} can define a comma separated
+    // list of strings corresponding to additional package name prefixes that
+    // should be ignored when trying to infer the source caller class name.
+    // Those stack frames whose {@linkplain StackTraceElement#getClassName()
+    // declaring class name} start with one such prefix will be ignored.
+    // <p>
+    // This is primarily useful when providing utility logging classes wrapping
+    // a logger instance, as it makes it possible to instruct LogRecord to skip
+    // those utility frames when inferring the caller source class name.
+    // <p>
+    // The {@code jdk.logger.packages} system property is consulted only once.
+    // <p>
+    // This property is not standard, implementation specific, and yet
+    // undocumented (and thus subject to changes without notice).
+    //
     private void inferCaller() {
         needToInferCaller = false;
         JavaLangAccess access = SharedSecrets.getJavaLangAccess();
@@ -658,8 +680,8 @@
                 }
             } else {
                 if (!isLoggerImpl) {
-                    // skip reflection call
-                    if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
+                    // skip logging/logger infrastructure and reflection calls
+                    if (!skipLoggingFrame(cname)) {
                        // We've found the relevant frame.
                        setSourceClassName(cname);
                        setSourceMethodName(frame.getMethodName());
@@ -675,7 +697,6 @@
     private boolean isLoggerImplFrame(String cname) {
         // the log record could be created for a platform logger
         return (cname.equals("java.util.logging.Logger") ||
-                cname.startsWith("java.util.logging.LoggingProxyImpl") ||
-                cname.startsWith("sun.util.logging."));
+                cname.startsWith("sun.util.logging.PlatformLogger"));
     }
 }
--- a/jdk/src/java.logging/share/classes/java/util/logging/Logger.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.logging/share/classes/java/util/logging/Logger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -447,8 +447,7 @@
 
     private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
         LogManager manager = LogManager.getLogManager();
-        SecurityManager sm = System.getSecurityManager();
-        if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
+        if (!SystemLoggerHelper.disableCallerCheck) {
             if (caller.getClassLoader() == null) {
                 return manager.demandSystemLogger(name, resourceBundleName, caller);
             }
@@ -1254,14 +1253,14 @@
      * with an optional list of message parameters.
      * <p>
      * If the logger is currently enabled for the given message
-     * level then a corresponding LogRecord is created and forwarded
-     * to all the registered output Handler objects.
+     * {@code level} then a corresponding {@code LogRecord} is created and
+     * forwarded to all the registered output {@code Handler} objects.
      * <p>
      * The {@code msg} string is localized using the given resource bundle.
      * If the resource bundle is {@code null}, then the {@code msg} string is not
      * localized.
      *
-     * @param   level   One of the message level identifiers, e.g., SEVERE
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
      * @param   sourceClass    Name of the class that issued the logging request
      * @param   sourceMethod   Name of the method that issued the logging request
      * @param   bundle         Resource bundle to localize {@code msg},
@@ -1285,6 +1284,36 @@
     }
 
     /**
+     * Log a message, specifying source class, method, and resource bundle,
+     * with an optional list of message parameters.
+     * <p>
+     * If the logger is currently enabled for the given message
+     * {@code level} then a corresponding {@code LogRecord} is created
+     * and forwarded to all the registered output {@code Handler} objects.
+     * <p>
+     * The {@code msg} string is localized using the given resource bundle.
+     * If the resource bundle is {@code null}, then the {@code msg} string is not
+     * localized.
+     * <p>
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
+     * @param   bundle  Resource bundle to localize {@code msg};
+     *                  can be {@code null}.
+     * @param   msg     The string message (or a key in the message catalog)
+     * @param   params  Parameters to the message (optional, may be none).
+     * @since 1.9
+     */
+    public void logrb(Level level, ResourceBundle bundle, String msg, Object... params) {
+        if (!isLoggable(level)) {
+            return;
+        }
+        LogRecord lr = new LogRecord(level, msg);
+        if (params != null && params.length != 0) {
+            lr.setParameters(params);
+        }
+        doLog(lr, bundle);
+    }
+
+    /**
      * Log a message, specifying source class, method, and resource bundle name,
      * with associated Throwable information.
      * <p>
@@ -1330,19 +1359,20 @@
      * with associated Throwable information.
      * <p>
      * If the logger is currently enabled for the given message
-     * level then the given arguments are stored in a LogRecord
+     * {@code level} then the given arguments are stored in a {@code LogRecord}
      * which is forwarded to all registered output handlers.
      * <p>
      * The {@code msg} string is localized using the given resource bundle.
      * If the resource bundle is {@code null}, then the {@code msg} string is not
      * localized.
      * <p>
-     * Note that the thrown argument is stored in the LogRecord thrown
-     * property, rather than the LogRecord parameters property.  Thus it is
-     * processed specially by output Formatters and is not treated
-     * as a formatting parameter to the LogRecord message property.
+     * Note that the {@code thrown} argument is stored in the {@code LogRecord}
+     * {@code thrown} property, rather than the {@code LogRecord}
+     * {@code parameters} property.  Thus it is
+     * processed specially by output {@code Formatter} objects and is not treated
+     * as a formatting parameter to the {@code LogRecord} {@code message} property.
      *
-     * @param   level   One of the message level identifiers, e.g., SEVERE
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
      * @param   sourceClass    Name of the class that issued the logging request
      * @param   sourceMethod   Name of the method that issued the logging request
      * @param   bundle         Resource bundle to localize {@code msg},
@@ -1363,6 +1393,42 @@
         doLog(lr, bundle);
     }
 
+    /**
+     * Log a message, specifying source class, method, and resource bundle,
+     * with associated Throwable information.
+     * <p>
+     * If the logger is currently enabled for the given message
+     * {@code level} then the given arguments are stored in a {@code LogRecord}
+     * which is forwarded to all registered output handlers.
+     * <p>
+     * The {@code msg} string is localized using the given resource bundle.
+     * If the resource bundle is {@code null}, then the {@code msg} string is not
+     * localized.
+     * <p>
+     * Note that the {@code thrown} argument is stored in the {@code LogRecord}
+     * {@code thrown} property, rather than the {@code LogRecord}
+     * {@code parameters} property.  Thus it is
+     * processed specially by output {@code Formatter} objects and is not treated
+     * as a formatting parameter to the {@code LogRecord} {@code message}
+     * property.
+     * <p>
+     * @param   level   One of the message level identifiers, e.g., {@code SEVERE}
+     * @param   bundle  Resource bundle to localize {@code msg};
+     *                  can be {@code null}.
+     * @param   msg     The string message (or a key in the message catalog)
+     * @param   thrown  Throwable associated with the log message.
+     * @since 1.9
+     */
+    public void logrb(Level level, ResourceBundle bundle, String msg,
+            Throwable thrown) {
+        if (!isLoggable(level)) {
+            return;
+        }
+        LogRecord lr = new LogRecord(level, msg);
+        lr.setThrown(thrown);
+        doLog(lr, bundle);
+    }
+
     //======================================================================
     // Start of convenience methods for logging method entries and returns.
     //======================================================================
--- a/jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java	Mon Nov 23 09:58:44 2015 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +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 java.util.logging;
-
-import sun.util.logging.LoggingProxy;
-
-/**
- * Implementation of LoggingProxy when java.util.logging classes exist.
- */
-class LoggingProxyImpl implements LoggingProxy {
-    static final LoggingProxy INSTANCE = new LoggingProxyImpl();
-
-    private LoggingProxyImpl() { }
-
-    @Override
-    public Object getLogger(String name) {
-        // always create a platform logger with the resource bundle name
-        return Logger.getPlatformLogger(name);
-    }
-
-    @Override
-    public Object getLevel(Object logger) {
-        return ((Logger) logger).getLevel();
-    }
-
-    @Override
-    public void setLevel(Object logger, Object newLevel) {
-        ((Logger) logger).setLevel((Level) newLevel);
-    }
-
-    @Override
-    public boolean isLoggable(Object logger, Object level) {
-        return ((Logger) logger).isLoggable((Level) level);
-    }
-
-    @Override
-    public void log(Object logger, Object level, String msg) {
-        ((Logger) logger).log((Level) level, msg);
-    }
-
-    @Override
-    public void log(Object logger, Object level, String msg, Throwable t) {
-        ((Logger) logger).log((Level) level, msg, t);
-    }
-
-    @Override
-    public void log(Object logger, Object level, String msg, Object... params) {
-        ((Logger) logger).log((Level) level, msg, params);
-    }
-
-    @Override
-    public java.util.List<String> getLoggerNames() {
-        return LogManager.getLoggingMXBean().getLoggerNames();
-    }
-
-    @Override
-    public String getLoggerLevel(String loggerName) {
-        return LogManager.getLoggingMXBean().getLoggerLevel(loggerName);
-    }
-
-    @Override
-    public void setLoggerLevel(String loggerName, String levelName) {
-        LogManager.getLoggingMXBean().setLoggerLevel(loggerName, levelName);
-    }
-
-    @Override
-    public String getParentLoggerName(String loggerName) {
-        return LogManager.getLoggingMXBean().getParentLoggerName(loggerName);
-    }
-
-    @Override
-    public Object parseLevel(String levelName) {
-        Level level = Level.findLevel(levelName);
-        if (level == null) {
-            throw new IllegalArgumentException("Unknown level \"" + levelName + "\"");
-        }
-        return level;
-    }
-
-    @Override
-    public String getLevelName(Object level) {
-        return ((Level) level).getLevelName();
-    }
-
-    @Override
-    public int getLevelValue(Object level) {
-        return ((Level) level).intValue();
-    }
-
-    @Override
-    public String getProperty(String key) {
-        return LogManager.getLogManager().getProperty(key);
-    }
-}
--- a/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java	Mon Nov 23 10:00:50 2015 -0800
@@ -27,10 +27,9 @@
 package java.util.logging;
 
 import java.io.*;
-import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
-import sun.util.logging.LoggingSupport;
+import jdk.internal.logger.SimpleConsoleLogger;
 
 /**
  * Print a brief summary of the {@code LogRecord} in a human readable
@@ -60,8 +59,12 @@
 public class SimpleFormatter extends Formatter {
 
     // format string for printing the log record
-    private final String format = LoggingSupport.getSimpleFormat();
-    private final ZoneId zoneId = ZoneId.systemDefault();
+    static String getLoggingProperty(String name) {
+        return LogManager.getLogManager().getProperty(name);
+    }
+
+    private final String format =
+        SimpleConsoleLogger.getSimpleFormat(SimpleFormatter::getLoggingProperty);
 
     /**
      * Format the given LogRecord.
@@ -152,7 +155,7 @@
     @Override
     public synchronized String format(LogRecord record) {
         ZonedDateTime zdt = ZonedDateTime.ofInstant(
-                record.getInstant(), zoneId);
+                record.getInstant(), ZoneId.systemDefault());
         String source;
         if (record.getSourceClassName() != null) {
             source = record.getSourceClassName();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,466 @@
+/*
+ * 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 sun.util.logging.internal;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.util.Objects;
+import java.util.logging.LogManager;
+import jdk.internal.logger.DefaultLoggerFinder;
+import java.util.logging.LoggingPermission;
+import sun.util.logging.PlatformLogger;
+import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration;
+
+/**
+ * This {@code LoggingProviderImpl} is the JDK internal implementation of the
+ * {@link jdk.internal.logger.DefaultLoggerFinder} which is used by
+ * the default implementation of the {@link Logger}
+ * when no {@link LoggerFinder} is found
+ * and {@code java.util.logging} is present.
+ * When {@code java.util.logging} is present, the {@code LoggingProviderImpl}
+ * is {@linkplain java.util.ServiceLoader#loadInstalled(Class) installed} as
+ * an internal service provider, making it possible to use {@code java.util.logging}
+ * as the backend for loggers returned by the default LoggerFinder implementation.
+ * <p>
+ * This implementation of {@link DefaultLoggerFinder} returns instances of
+ * {@link java.lang.System.Logger} which
+ * delegate to a wrapped instance of {@link java.util.logging.Logger
+ * java.util.logging.Logger}.
+ * <br>
+ * Loggers returned by this class can therefore be configured by accessing
+ * their wrapped implementation through the regular {@code java.util.logging}
+ * APIs - such as {@link java.util.logging.LogManager java.util.logging.LogManager}
+ * and {@link java.util.logging.Logger java.util.logging.Logger}.
+ *
+ * @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}.
+ * <p>
+ * To replace this default
+ * {@code java.util.logging} backend, an application is expected to install
+ * its own {@link java.lang.System.LoggerFinder}.
+ *
+ * @see java.lang.System.Logger
+ * @see java.lang.System.LoggerFinder
+ * @see sun.util.logging.PlatformLogger.Bridge
+ * @see java.lang.System
+ * @see jdk.internal.logger
+ * @see jdk.internal.logger
+ *
+ */
+public final class LoggingProviderImpl extends DefaultLoggerFinder {
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    private static final LoggingPermission LOGGING_CONTROL_PERMISSION =
+            new LoggingPermission("control", null);
+
+    /**
+     * Creates a new instance of LoggingProviderImpl.
+     * @throws SecurityException if the calling code does not have the
+     * {@code RuntimePermission("loggerFinder")}.
+     */
+    public LoggingProviderImpl() {
+    }
+
+    /**
+     * A logger that delegates to a java.util.logging.Logger delegate.
+     */
+    static final class JULWrapper extends LoggerConfiguration
+            implements System.Logger, PlatformLogger.Bridge,
+                       PlatformLogger.ConfigurableBridge {
+
+
+        private static final java.util.logging.Level[] spi2JulLevelMapping = {
+                java.util.logging.Level.ALL,     // mapped from ALL
+                java.util.logging.Level.FINER,   // mapped from TRACE
+                java.util.logging.Level.FINE,    // mapped from DEBUG
+                java.util.logging.Level.INFO,    // mapped from INFO
+                java.util.logging.Level.WARNING, // mapped from WARNING
+                java.util.logging.Level.SEVERE,  // mapped from ERROR
+                java.util.logging.Level.OFF      // mapped from OFF
+        };
+
+        private static final java.util.logging.Level[] platform2JulLevelMapping = {
+                java.util.logging.Level.ALL,     // mapped from ALL
+                java.util.logging.Level.FINEST,  // mapped from FINEST
+                java.util.logging.Level.FINER,   // mapped from FINER
+                java.util.logging.Level.FINE,    // mapped from FINE
+                java.util.logging.Level.CONFIG,  // mapped from CONFIG
+                java.util.logging.Level.INFO,    // mapped from INFO
+                java.util.logging.Level.WARNING, // mapped from WARNING
+                java.util.logging.Level.SEVERE,  // mapped from SEVERE
+                java.util.logging.Level.OFF      // mapped from OFF
+        };
+
+        private final java.util.logging.Logger julLogger;
+
+
+        private JULWrapper(java.util.logging.Logger logger) {
+            this.julLogger = logger;
+        }
+
+        @Override
+        public String getName() {
+            return julLogger.getName();
+        }
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable throwable) {
+            julLogger.log(toJUL(level), msg, throwable);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) {
+            julLogger.log(toJUL(level), format, params);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+            julLogger.log(toJUL(level), msg);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, Supplier<String> msgSuppier) {
+            julLogger.log(toJUL(level), msgSuppier);
+        }
+
+        @Override
+        public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, Supplier<String> msgSuppier) {
+            julLogger.log(toJUL(level), thrown, msgSuppier);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Throwable throwable) {
+            julLogger.logrb(toJUL(level), bundle, key, throwable);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Object... params) {
+            julLogger.logrb(toJUL(level), bundle, key, params);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                Supplier<String> msgSupplier) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msgSupplier);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                String msg, Object... params) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, params);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                String msg, Throwable thrown) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, thrown);
+        }
+
+        @Override
+        public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                Throwable thrown, Supplier<String> msgSupplier) {
+            julLogger.logp(toJUL(level), sourceClass, sourceMethod,
+                    thrown, msgSupplier);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                ResourceBundle bundle, String key, Object... params) {
+            julLogger.logrb(toJUL(level), sourceClass, sourceMethod,
+                    bundle, key, params);
+        }
+
+        @Override
+        public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod,
+                ResourceBundle bundle, String key, Throwable thrown) {
+            julLogger.logrb(toJUL(level), sourceClass, sourceMethod,
+                    bundle, key, thrown);
+        }
+
+        @Override
+        public  boolean isLoggable(sun.util.logging.PlatformLogger.Level level) {
+            return julLogger.isLoggable(toJUL(level));
+        }
+
+        // -----------------------------------------------------------------
+        // Generic methods taking a Level as parameter
+        // -----------------------------------------------------------------
+
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return julLogger.isLoggable(toJUL(level));
+        }
+
+        @Override
+        public void log(Level level, String msg) {
+            julLogger.log(toJUL(level), msg);
+        }
+
+        @Override
+        public void log(Level level,
+                        Supplier<String> msgSupplier) {
+            // We need to check for null here to satisfy the contract
+            // of System.Logger - because the underlying implementation
+            // of julLogger will check for it only if the level is
+            // loggable
+            Objects.requireNonNull(msgSupplier);
+            julLogger.log(toJUL(level), msgSupplier);
+        }
+
+        @Override
+        public void log(Level level, Object obj) {
+            // We need to check for null here to satisfy the contract
+            // of System.Logger - because the underlying implementation
+            // of julLogger will check for it only if the level is
+            // loggable
+            Objects.requireNonNull(obj);
+            julLogger.log(toJUL(level), () -> obj.toString());
+        }
+
+        @Override
+        public void log(Level level,
+                        String msg, Throwable thrown) {
+            julLogger.log(toJUL(level), msg, thrown);
+        }
+
+        @Override
+        public void log(Level level, Supplier<String> msgSupplier,
+                        Throwable thrown) {
+            // We need to check for null here to satisfy the contract
+            // of System.Logger - because the underlying implementation
+            // of julLogger will check for it only if the level is
+            // loggable
+            Objects.requireNonNull(msgSupplier);
+            julLogger.log(toJUL(level), thrown, msgSupplier);
+        }
+
+        @Override
+        public void log(Level level,
+                        String format, Object... params) {
+            julLogger.log(toJUL(level), format, params);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle,
+                        String key, Throwable thrown) {
+            julLogger.logrb(toJUL(level), bundle, key, thrown);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle,
+                        String format, Object... params) {
+            julLogger.logrb(toJUL(level), bundle, format, params);
+        }
+
+        static java.util.logging.Level toJUL(Level level) {
+            if (level == null) return null;
+            assert level.ordinal() < spi2JulLevelMapping.length;
+            return spi2JulLevelMapping[level.ordinal()];
+        }
+
+        // ---------------------------------------------------------
+        // Methods from PlatformLogger.Bridge
+        // ---------------------------------------------------------
+
+        @Override
+        public boolean isEnabled() {
+            return julLogger.getLevel() != java.util.logging.Level.OFF;
+        }
+
+        @Override
+        public PlatformLogger.Level getPlatformLevel() {
+            final java.util.logging.Level javaLevel = julLogger.getLevel();
+            if (javaLevel == null) return null;
+            try {
+                return PlatformLogger.Level.valueOf(javaLevel.getName());
+            } catch (IllegalArgumentException e) {
+                return PlatformLogger.Level.valueOf(javaLevel.intValue());
+            }
+        }
+
+        @Override
+        public void setPlatformLevel(PlatformLogger.Level level) {
+            // null is allowed here
+            julLogger.setLevel(toJUL(level));
+        }
+
+        @Override
+        public LoggerConfiguration getLoggerConfiguration() {
+            return this;
+        }
+
+        static java.util.logging.Level toJUL(PlatformLogger.Level level) {
+            // The caller will throw if null is invalid in its context.
+            // There's at least one case where a null level is valid.
+            if (level == null) return null;
+            assert level.ordinal() < platform2JulLevelMapping.length;
+            return platform2JulLevelMapping[level.ordinal()];
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return (obj instanceof JULWrapper)
+                    && obj.getClass() == this.getClass()
+                    && ((JULWrapper)obj).julLogger == this.julLogger;
+        }
+
+        @Override
+        public int hashCode() {
+            return julLogger.hashCode();
+        }
+
+        // A JULWrapper is just a stateless thin shell over a JUL logger - so
+        // for a given JUL logger, we could always return the same wrapper.
+        //
+        // This is an optimization which may - or may not - be worth the
+        // trouble: if many classes use the same logger, and if each class
+        // keeps a reference to that logger, then caching the wrapper will
+        // be worthwhile. Otherwise, if each logger is only referred once,
+        // then the cache will eat up more memory than would be necessary...
+        //
+        // Here is an example of how we could implement JULWrapper.of(...)
+        // if we wanted to create at most one wrapper instance for each logger
+        // instance:
+        //
+        //        static final WeakHashMap<JULWrapper, WeakReference<JULWrapper>>
+        //                wrappers = new WeakHashMap<>();
+        //
+        //        static JULWrapper of(java.util.logging.Logger logger) {
+        //
+        //            // First access without synchronizing
+        //            final JULWrapper candidate = new JULWrapper(logger);
+        //            WeakReference<JULWrapper> ref = wrappers.get(candidate);
+        //            JULWrapper found = ref.get();
+        //
+        //            // OK - we found it - lets return it.
+        //            if (found != null) return found;
+        //
+        //            // Not found. Need to synchronize.
+        //            synchronized (wrappers) {
+        //                ref = wrappers.get(candidate);
+        //                found = ref.get();
+        //                if (found == null) {
+        //                    wrappers.put(candidate, new WeakReference<>(candidate));
+        //                    found = candidate;
+        //                }
+        //            }
+        //            assert found != null;
+        //            return found;
+        //        }
+        //
+        // But given that it may end up eating more memory in the nominal case
+        // (where each class that does logging has its own logger with the
+        //  class name as logger name and stashes that logger away in a static
+        //  field, thus making the cache redundant - as only one wrapper will
+        //  ever be created anyway) - then we will simply return a new wrapper
+        // for each invocation of JULWrapper.of(...) - which may
+        // still prove more efficient in terms of memory consumption...
+        //
+        static JULWrapper of(java.util.logging.Logger logger) {
+            return new JULWrapper(logger);
+        }
+
+
+    }
+
+    /**
+     * Creates a java.util.logging.Logger for the given caller.
+     * @param name the logger name.
+     * @param caller the caller for which the logger should be created.
+     * @return a Logger suitable for use in the given caller.
+     */
+    private static java.util.logging.Logger demandJULLoggerFor(final String name,
+                                                            /* Module */
+                                                            final Class<?> caller) {
+        final LogManager manager = LogManager.getLogManager();
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm == null) {
+            return logManagerAccess.demandLoggerFor(manager, name, caller);
+        } else {
+            final PrivilegedAction<java.util.logging.Logger> pa =
+                    () -> logManagerAccess.demandLoggerFor(manager, name, caller);
+            return AccessController.doPrivileged(pa, null, LOGGING_CONTROL_PERMISSION);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @apiNote The logger returned by this method can be configured through
+     * its {@linkplain java.util.logging.LogManager#getLogger(String)
+     * corresponding java.util.logging.Logger backend}.
+     *
+     * @return {@inheritDoc}
+     * @throws SecurityException if the calling code doesn't have the
+     * {@code RuntimePermission("loggerFinder")}.
+     */
+    @Override
+    protected Logger demandLoggerFor(String name, /* Module */ Class<?> caller) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGERFINDER_PERMISSION);
+        }
+        return JULWrapper.of(demandJULLoggerFor(name,caller));
+    }
+
+    public static interface LogManagerAccess {
+        java.util.logging.Logger demandLoggerFor(LogManager manager,
+                String name, /* Module */ Class<?> caller);
+    }
+
+    // Hook for tests
+    public static LogManagerAccess getLogManagerAccess() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGING_CONTROL_PERMISSION);
+        }
+        // Triggers initialization of accessJulLogger if not set.
+        if (logManagerAccess == null) LogManager.getLogManager();
+        return logManagerAccess;
+    }
+
+
+    private static volatile LogManagerAccess logManagerAccess;
+    public static void setLogManagerAccess(LogManagerAccess accesLoggers) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGING_CONTROL_PERMISSION);
+        }
+        logManagerAccess = accesLoggers;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.logging/share/classes/sun/util/logging/internal/package-info.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+/**
+ * <p>
+ * <b>[JDK INTERNAL]</b>
+ * The {@code sun.util.logging.internal} package defines an internal
+ * implementation of the {@link jdk.internal.logger.DefaultLoggerFinder} which
+ * provides an extension of the {@link java.lang.System.Logger System.Logger}
+ * interface making it easy to bridge from {@link java.util.logging};
+ * the JDK default implementation of the LoggerFinder will return loggers
+ * implementing this extension when {@code java.util.logging} is present.
+ * </p>
+ * <p>
+ * When {@code java.util.logging} is present, Logger instances returned by
+ * the JDK default implementation of the LoggerFinder
+ * wrap an instance of {@link java.util.logging.Logger java.util.logging.Logger}
+ * and implement the {@link
+ * sun.util.logging.PlatformLogger.Bridge PlatformLogger.Bridge}
+ * extension, overriding all the methods defined in
+ * that extension in order to call the corresponding methods on their wrapped
+ * {@linkplain java.util.logging.Logger backend Logger} instance.
+ * <p>
+ * <br>
+ * @see java.lang.System.LoggerFinder
+ * @see java.lang.System.Logger
+ * @see sun.util.logging.PlatformLogger
+ * @see sun.util.logging.PlatformLogger.Bridge
+ * @see jdk.internal.logger
+ *
+ * @since 1.9
+ */
+package sun.util.logging.internal;
--- a/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java	Mon Nov 23 10:00:50 2015 -0800
@@ -355,36 +355,38 @@
             }
         });
 
-        /**
-         * Logging facility.
-         */
-        initMBeanList.add(new PlatformComponent<PlatformLoggingMXBean>() {
-            private final Set<String> platformLoggingMXBeanInterfaceNames
+        if (ManagementFactoryHelper.isPlatformLoggingMXBeanAvailable()) {
+            /**
+             * Logging facility.
+             */
+            initMBeanList.add(new PlatformComponent<PlatformLoggingMXBean>() {
+                private final Set<String> platformLoggingMXBeanInterfaceNames
                     = Collections.unmodifiableSet(Collections.singleton(
                             "java.lang.management.PlatformLoggingMXBean"));
 
-            @Override
-            public Set<Class<? extends PlatformLoggingMXBean>> mbeanInterfaces() {
-                return Collections.singleton(PlatformLoggingMXBean.class);
-            }
+                @Override
+                public Set<Class<? extends PlatformLoggingMXBean>> mbeanInterfaces() {
+                    return Collections.singleton(PlatformLoggingMXBean.class);
+                }
 
-            @Override
-            public Set<String> mbeanInterfaceNames() {
-                return platformLoggingMXBeanInterfaceNames;
-            }
+                @Override
+                public Set<String> mbeanInterfaceNames() {
+                    return platformLoggingMXBeanInterfaceNames;
+                }
 
-            @Override
-            public String getObjectNamePattern() {
-                return "java.util.logging:type=Logging";
-            }
+                @Override
+                public String getObjectNamePattern() {
+                    return "java.util.logging:type=Logging";
+                }
 
-            @Override
-            public Map<String, PlatformLoggingMXBean> nameToMBeanMap() {
-                return Collections.singletonMap(
+                @Override
+                public Map<String, PlatformLoggingMXBean> nameToMBeanMap() {
+                    return Collections.singletonMap(
                         "java.util.logging:type=Logging",
                         ManagementFactoryHelper.getPlatformLoggingMXBean());
-            }
-        });
+                }
+            });
+        }
 
         /**
          * Buffer pools.
--- a/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java	Mon Nov 23 10:00:50 2015 -0800
@@ -39,10 +39,14 @@
 
 import jdk.internal.misc.JavaNioAccess;
 import jdk.internal.misc.SharedSecrets;
-import sun.util.logging.LoggingSupport;
+
 import java.util.ArrayList;
 import java.util.List;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.PrivilegedAction;
+
 /**
  * ManagementFactoryHelper provides static factory methods to create
  * instances of the management interface.
@@ -141,13 +145,17 @@
     }
 
     public static PlatformLoggingMXBean getPlatformLoggingMXBean() {
-        if (LoggingSupport.isAvailable()) {
+        if (LoggingMXBeanSupport.isAvailable()) {
             return PlatformLoggingImpl.instance;
         } else {
             return null;
         }
     }
 
+    public static boolean isPlatformLoggingMXBeanAvailable() {
+        return LoggingMXBeanSupport.isAvailable();
+    }
+
     /**
      * The logging MXBean object is an instance of
      * PlatformLoggingMXBean and java.util.logging.LoggingMXBean
@@ -165,8 +173,44 @@
         extends PlatformLoggingMXBean, java.util.logging.LoggingMXBean {
     }
 
+    // This is a trick: if java.util.logging is not present then
+    // attempting to access something that implements
+    // java.util.logging.LoggingMXBean will trigger a CNFE.
+    // So we cannot directly call any static method or access any static field
+    // on PlatformLoggingImpl, as we would risk raising a CNFE.
+    // Instead we use this intermediate LoggingMXBeanSupport class to determine
+    // whether java.util.logging is present, and load the actual LoggingMXBean
+    // implementation.
+    //
+    static final class LoggingMXBeanSupport {
+        final static Object loggingImpl =
+                AccessController.doPrivileged(new PrivilegedAction<Object>() {
+            @Override
+            public Object run() {
+                try {
+                    // create a LoggingProxyImpl instance when
+                    // java.util.logging classes exist
+                    Class<?> c = Class.forName("java.util.logging.Logging", true, null);
+                    Constructor<?> cons = c.getDeclaredConstructor();
+                    cons.setAccessible(true);
+                    return cons.newInstance();
+                } catch (ClassNotFoundException cnf) {
+                    return null;
+                } catch (NoSuchMethodException | InstantiationException
+                        | IllegalAccessException | InvocationTargetException e) {
+                    throw new AssertionError(e);
+                }
+            }});
+
+        static boolean isAvailable() {
+            return loggingImpl != null;
+        }
+    }
+
     static class PlatformLoggingImpl implements LoggingMXBean
     {
+        final static java.util.logging.LoggingMXBean impl =
+                (java.util.logging.LoggingMXBean) LoggingMXBeanSupport.loggingImpl;
         final static PlatformLoggingMXBean instance = new PlatformLoggingImpl();
         final static String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging";
 
@@ -188,22 +232,22 @@
 
         @Override
         public java.util.List<String> getLoggerNames() {
-            return LoggingSupport.getLoggerNames();
+            return impl.getLoggerNames();
         }
 
         @Override
         public String getLoggerLevel(String loggerName) {
-            return LoggingSupport.getLoggerLevel(loggerName);
+            return impl.getLoggerLevel(loggerName);
         }
 
         @Override
         public void setLoggerLevel(String loggerName, String levelName) {
-            LoggingSupport.setLoggerLevel(loggerName, levelName);
+            impl.setLoggerLevel(loggerName, levelName);
         }
 
         @Override
         public String getParentLoggerName(String loggerName) {
-            return LoggingSupport.getParentLoggerName(loggerName);
+            return impl.getParentLoggerName(loggerName);
         }
     }
 
--- a/jdk/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/KeyStore.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/KeyStore.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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
@@ -310,7 +310,7 @@
             if (alias.equals(entry.getAlias()))
             {
                 X509Certificate[] certChain = entry.getCertificateChain();
-                return certChain[0];
+                return certChain.length == 0 ? null : certChain[0];
             }
         }
 
@@ -840,7 +840,7 @@
 
             // Obtain certificate factory
             if (certificateFactory == null) {
-                certificateFactory = CertificateFactory.getInstance("X.509");
+                certificateFactory = CertificateFactory.getInstance("X.509", "SUN");
             }
 
             // Generate certificate
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1286 @@
+/*
+ * 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.security.jarsigner;
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import sun.security.tools.PathList;
+import sun.security.tools.jarsigner.TimestampedSigner;
+import sun.security.util.ManifestDigester;
+import sun.security.util.SignatureFileVerifier;
+import sun.security.x509.AlgorithmId;
+
+import java.io.*;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.*;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * An immutable utility class to sign a jar file.
+ * <p>
+ * A caller creates a {@code JarSigner.Builder} object, (optionally) sets
+ * some parameters, and calls {@link JarSigner.Builder#build build} to create
+ * a {@code JarSigner} object. This {@code JarSigner} object can then
+ * be used to sign a jar file.
+ * <p>
+ * Unless otherwise stated, calling a method of {@code JarSigner} or
+ * {@code JarSigner.Builder} with a null argument will throw
+ * a {@link NullPointerException}.
+ * <p>
+ * Example:
+ * <pre>
+ * JarSigner signer = new JarSigner.Builder(key, certPath)
+ *         .digestAlgorithm("SHA-1")
+ *         .signatureAlgorithm("SHA1withDSA")
+ *         .build();
+ * try (ZipFile in = new ZipFile(inputFile);
+ *         FileOutputStream out = new FileOutputStream(outputFile)) {
+ *     signer.sign(in, out);
+ * }
+ * </pre>
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public final class JarSigner {
+
+    /**
+     * A mutable builder class that can create an immutable {@code JarSigner}
+     * from various signing-related parameters.
+     *
+     * @since 1.9
+     */
+    @jdk.Exported
+    public static class Builder {
+
+        // Signer materials:
+        final PrivateKey privateKey;
+        final X509Certificate[] certChain;
+
+        // JarSigner options:
+        // Support multiple digestalg internally. Can be null, but not empty
+        String[] digestalg;
+        String sigalg;
+        // Precisely should be one provider for each digestalg, maybe later
+        Provider digestProvider;
+        Provider sigProvider;
+        URI tsaUrl;
+        String signerName;
+        BiConsumer<String,String> handler;
+
+        // Implementation-specific properties:
+        String tSAPolicyID;
+        String tSADigestAlg;
+        boolean signManifest = true;
+        boolean externalSF = true;
+        String altSignerPath;
+        String altSigner;
+
+        /**
+         * Creates a {@code JarSigner.Builder} object with
+         * a {@link KeyStore.PrivateKeyEntry} object.
+         *
+         * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.
+         */
+        public Builder(KeyStore.PrivateKeyEntry entry) {
+            this.privateKey = entry.getPrivateKey();
+            try {
+                // called internally, no need to clone
+                Certificate[] certs = entry.getCertificateChain();
+                this.certChain = Arrays.copyOf(certs, certs.length,
+                        X509Certificate[].class);
+            } catch (ArrayStoreException ase) {
+                // Wrong type, not X509Certificate. Won't document.
+                throw new IllegalArgumentException(
+                        "Entry does not contain X509Certificate");
+            }
+        }
+
+        /**
+         * Creates a {@code JarSigner.Builder} object with a private key and
+         * a certification path.
+         *
+         * @param privateKey the private key of the signer.
+         * @param certPath the certification path of the signer.
+         * @throws IllegalArgumentException if {@code certPath} is empty, or
+         *      the {@code privateKey} algorithm does not match the algorithm
+         *      of the {@code PublicKey} in the end entity certificate
+         *      (the first certificate in {@code certPath}).
+         */
+        public Builder(PrivateKey privateKey, CertPath certPath) {
+            List<? extends Certificate> certs = certPath.getCertificates();
+            if (certs.isEmpty()) {
+                throw new IllegalArgumentException("certPath cannot be empty");
+            }
+            if (!privateKey.getAlgorithm().equals
+                    (certs.get(0).getPublicKey().getAlgorithm())) {
+                throw new IllegalArgumentException
+                        ("private key algorithm does not match " +
+                                "algorithm of public key in end entity " +
+                                "certificate (the 1st in certPath)");
+            }
+            this.privateKey = privateKey;
+            try {
+                this.certChain = certs.toArray(new X509Certificate[certs.size()]);
+            } catch (ArrayStoreException ase) {
+                // Wrong type, not X509Certificate.
+                throw new IllegalArgumentException(
+                        "Entry does not contain X509Certificate");
+            }
+        }
+
+        /**
+         * Sets the digest algorithm. If no digest algorithm is specified,
+         * the default algorithm returned by {@link #getDefaultDigestAlgorithm}
+         * will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code MessageDigest} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+         */
+        public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException {
+            MessageDigest.getInstance(Objects.requireNonNull(algorithm));
+            this.digestalg = new String[]{algorithm};
+            this.digestProvider = null;
+            return this;
+        }
+
+        /**
+         * Sets the digest algorithm from the specified provider.
+         * If no digest algorithm is specified, the default algorithm
+         * returned by {@link #getDefaultDigestAlgorithm} will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code MessageDigest} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @param provider the provider.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not
+         *      available in the specified provider.
+         */
+        public Builder digestAlgorithm(String algorithm, Provider provider)
+                throws NoSuchAlgorithmException {
+            MessageDigest.getInstance(
+                    Objects.requireNonNull(algorithm),
+                    Objects.requireNonNull(provider));
+            this.digestalg = new String[]{algorithm};
+            this.digestProvider = provider;
+            return this;
+        }
+
+        /**
+         * Sets the signature algorithm. If no signature algorithm
+         * is specified, the default signature algorithm returned by
+         * {@link #getDefaultSignatureAlgorithm} for the private key
+         * will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code Signature} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#Signature">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+         * @throws IllegalArgumentException if {@code algorithm} is not
+         *      compatible with the algorithm of the signer's private key.
+         */
+        public Builder signatureAlgorithm(String algorithm)
+                throws NoSuchAlgorithmException {
+            // Check availability
+            Signature.getInstance(Objects.requireNonNull(algorithm));
+            AlgorithmId.checkKeyAndSigAlgMatch(
+                    privateKey.getAlgorithm(), algorithm);
+            this.sigalg = algorithm;
+            this.sigProvider = null;
+            return this;
+        }
+
+        /**
+         * Sets the signature algorithm from the specified provider. If no
+         * signature algorithm is specified, the default signature algorithm
+         * returned by {@link #getDefaultSignatureAlgorithm} for the private
+         * key will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code Signature} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#Signature">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @param provider  the provider.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not
+         *      available in the specified provider.
+         * @throws IllegalArgumentException if {@code algorithm} is not
+         *      compatible with the algorithm of the signer's private key.
+         */
+        public Builder signatureAlgorithm(String algorithm, Provider provider)
+                throws NoSuchAlgorithmException {
+            // Check availability
+            Signature.getInstance(
+                    Objects.requireNonNull(algorithm),
+                    Objects.requireNonNull(provider));
+            AlgorithmId.checkKeyAndSigAlgMatch(
+                    privateKey.getAlgorithm(), algorithm);
+            this.sigalg = algorithm;
+            this.sigProvider = provider;
+            return this;
+        }
+
+        /**
+         * Sets the URI of the Time Stamping Authority (TSA).
+         *
+         * @param uri the URI.
+         * @return the {@code JarSigner.Builder} itself.
+         */
+        public Builder tsa(URI uri) {
+            this.tsaUrl = Objects.requireNonNull(uri);
+            return this;
+        }
+
+        /**
+         * Sets the signer name. The name will be used as the base name for
+         * the signature files. All lowercase characters will be converted to
+         * uppercase for signature file names. If a signer name is not
+         * specified, the string "SIGNER" will be used.
+         *
+         * @param name the signer name.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws IllegalArgumentException if {@code name} is empty or has
+         *      a size bigger than 8, or it contains characters not from the
+         *      set "a-zA-Z0-9_-".
+         */
+        public Builder signerName(String name) {
+            if (name.isEmpty() || name.length() > 8) {
+                throw new IllegalArgumentException("Name too long");
+            }
+
+            name = name.toUpperCase(Locale.ENGLISH);
+
+            for (int j = 0; j < name.length(); j++) {
+                char c = name.charAt(j);
+                if (!
+                        ((c >= 'A' && c <= 'Z') ||
+                                (c >= '0' && c <= '9') ||
+                                (c == '-') ||
+                                (c == '_'))) {
+                    throw new IllegalArgumentException(
+                            "Invalid characters in name");
+                }
+            }
+            this.signerName = name;
+            return this;
+        }
+
+        /**
+         * Sets en event handler that will be triggered when a {@link JarEntry}
+         * is to be added, signed, or updated during the signing process.
+         * <p>
+         * The handler can be used to display signing progress. The first
+         * argument of the handler can be "adding", "signing", or "updating",
+         * and the second argument is the name of the {@link JarEntry}
+         * being processed.
+         *
+         * @param handler the event handler.
+         * @return the {@code JarSigner.Builder} itself.
+         */
+        public Builder eventHandler(BiConsumer<String,String> handler) {
+            this.handler = Objects.requireNonNull(handler);
+            return this;
+        }
+
+        /**
+         * Sets an additional implementation-specific property indicated by
+         * the specified key.
+         *
+         * @implNote This implementation supports the following properties:
+         * <ul>
+         * <li>"tsaDigestAlg": algorithm of digest data in the timestamping
+         * request. The default value is the same as the result of
+         * {@link #getDefaultDigestAlgorithm}.
+         * <li>"tsaPolicyId": TSAPolicyID for Timestamping Authority.
+         * No default value.
+         * <li>"internalsf": "true" if the .SF file is included inside the
+         * signature block, "false" otherwise. Default "false".
+         * <li>"sectionsonly": "true" if the .SF file only contains the hash
+         * value for each section of the manifest and not for the whole
+         * manifest, "false" otherwise. Default "false".
+         * </ul>
+         * All property names are case-insensitive.
+         *
+         * @param key the name of the property.
+         * @param value the value of the property.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws UnsupportedOperationException if the key is not supported
+         *      by this implementation.
+         * @throws IllegalArgumentException if the value is not accepted as
+         *      a legal value for this key.
+         */
+        public Builder setProperty(String key, String value) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(value);
+            switch (key.toLowerCase(Locale.US)) {
+                case "tsadigestalg":
+                    try {
+                        MessageDigest.getInstance(value);
+                    } catch (NoSuchAlgorithmException nsae) {
+                        throw new IllegalArgumentException(
+                                "Invalid tsadigestalg", nsae);
+                    }
+                    this.tSADigestAlg = value;
+                    break;
+                case "tsapolicyid":
+                    this.tSAPolicyID = value;
+                    break;
+                case "internalsf":
+                    switch (value) {
+                        case "true":
+                            externalSF = false;
+                            break;
+                        case "false":
+                            externalSF = true;
+                            break;
+                        default:
+                            throw new IllegalArgumentException(
+                                "Invalid internalsf value");
+                    }
+                    break;
+                case "sectionsonly":
+                    switch (value) {
+                        case "true":
+                            signManifest = false;
+                            break;
+                        case "false":
+                            signManifest = true;
+                            break;
+                        default:
+                            throw new IllegalArgumentException(
+                                "Invalid signManifest value");
+                    }
+                    break;
+                case "altsignerpath":
+                    altSignerPath = value;
+                    break;
+                case "altsigner":
+                    altSigner = value;
+                    break;
+                default:
+                    throw new UnsupportedOperationException(
+                            "Unsupported key " + key);
+            }
+            return this;
+        }
+
+        /**
+         * Gets the default digest algorithm.
+         *
+         * @implNote This implementation returns "SHA-256". The value may
+         * change in the future.
+         *
+         * @return the default digest algorithm.
+         */
+        public static String getDefaultDigestAlgorithm() {
+            return "SHA-256";
+        }
+
+        /**
+         * Gets the default signature algorithm for a private key.
+         * For example, SHA256withRSA for a 2048-bit RSA key, and
+         * SHA384withECDSA for a 384-bit EC key.
+         *
+         * @implNote This implementation makes use of comparable strengths
+         * as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.3.
+         * Specifically, if a DSA or RSA key with a key size greater than 7680
+         * bits, or an EC key with a key size greater than or equal to 512 bits,
+         * SHA-512 will be used as the hash function for the signature.
+         * If a DSA or RSA key has a key size greater than 3072 bits, or an
+         * EC key has a key size greater than or equal to 384 bits, SHA-384 will
+         * be used. Otherwise, SHA-256 will be used. The value may
+         * change in the future.
+         *
+         * @param key the private key.
+         * @return the default signature algorithm. Returns null if a default
+         *      signature algorithm cannot be found. In this case,
+         *      {@link #signatureAlgorithm} must be called to specify a
+         *      signature algorithm. Otherwise, the {@link #build} method
+         *      will throw an {@link IllegalArgumentException}.
+         */
+        public static String getDefaultSignatureAlgorithm(PrivateKey key) {
+            return AlgorithmId.getDefaultSigAlgForKey(Objects.requireNonNull(key));
+        }
+
+        /**
+         * Builds a {@code JarSigner} object from the parameters set by the
+         * setter methods.
+         * <p>
+         * This method does not modify internal state of this {@code Builder}
+         * object and can be called multiple times to generate multiple
+         * {@code JarSigner} objects. After this method is called, calling
+         * any method on this {@code Builder} will have no effect on
+         * the newly built {@code JarSigner} object.
+         *
+         * @return the {@code JarSigner} object.
+         * @throws IllegalArgumentException if a signature algorithm is not
+         *      set and cannot be derived from the private key using the
+         *      {@link #getDefaultSignatureAlgorithm} method.
+         */
+        public JarSigner build() {
+            return new JarSigner(this);
+        }
+    }
+
+    private static final String META_INF = "META-INF/";
+
+    // All fields in Builder are duplicated here as final. Those not
+    // provided but has a default value will be filled with default value.
+
+    // Precisely, a final array field can still be modified if only
+    // reference is copied, no clone is done because we are concerned about
+    // casual change instead of malicious attack.
+
+    // Signer materials:
+    private final PrivateKey privateKey;
+    private final X509Certificate[] certChain;
+
+    // JarSigner options:
+    private final String[] digestalg;
+    private final String sigalg;
+    private final Provider digestProvider;
+    private final Provider sigProvider;
+    private final URI tsaUrl;
+    private final String signerName;
+    private final BiConsumer<String,String> handler;
+
+    // Implementation-specific properties:
+    private final String tSAPolicyID;
+    private final String tSADigestAlg;
+    private final boolean signManifest; // "sign" the whole manifest
+    private final boolean externalSF; // leave the .SF out of the PKCS7 block
+    private final String altSignerPath;
+    private final String altSigner;
+
+    private JarSigner(JarSigner.Builder builder) {
+
+        this.privateKey = builder.privateKey;
+        this.certChain = builder.certChain;
+        if (builder.digestalg != null) {
+            // No need to clone because builder only accepts one alg now
+            this.digestalg = builder.digestalg;
+        } else {
+            this.digestalg = new String[] {
+                    Builder.getDefaultDigestAlgorithm() };
+        }
+        this.digestProvider = builder.digestProvider;
+        if (builder.sigalg != null) {
+            this.sigalg = builder.sigalg;
+        } else {
+            this.sigalg = JarSigner.Builder
+                    .getDefaultSignatureAlgorithm(privateKey);
+            if (this.sigalg == null) {
+                throw new IllegalArgumentException(
+                        "No signature alg for " + privateKey.getAlgorithm());
+            }
+        }
+        this.sigProvider = builder.sigProvider;
+        this.tsaUrl = builder.tsaUrl;
+
+        if (builder.signerName == null) {
+            this.signerName = "SIGNER";
+        } else {
+            this.signerName = builder.signerName;
+        }
+        this.handler = builder.handler;
+
+        if (builder.tSADigestAlg != null) {
+            this.tSADigestAlg = builder.tSADigestAlg;
+        } else {
+            this.tSADigestAlg = Builder.getDefaultDigestAlgorithm();
+        }
+        this.tSAPolicyID = builder.tSAPolicyID;
+        this.signManifest = builder.signManifest;
+        this.externalSF = builder.externalSF;
+        this.altSigner = builder.altSigner;
+        this.altSignerPath = builder.altSignerPath;
+    }
+
+    /**
+     * Signs a file into an {@link OutputStream}. This method will not close
+     * {@code file} or {@code os}.
+     *
+     * @param file the file to sign.
+     * @param os the output stream.
+     * @throws JarSignerException if the signing fails.
+     */
+    public void sign(ZipFile file, OutputStream os) {
+        try {
+            sign0(Objects.requireNonNull(file),
+                    Objects.requireNonNull(os));
+        } catch (SocketTimeoutException | CertificateException e) {
+            // CertificateException is thrown when the received cert from TSA
+            // has no id-kp-timeStamping in its Extended Key Usages extension.
+            throw new JarSignerException("Error applying timestamp", e);
+        } catch (IOException ioe) {
+            throw new JarSignerException("I/O error", ioe);
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new JarSignerException("Error in signer materials", e);
+        } catch (SignatureException se) {
+            throw new JarSignerException("Error creating signature", se);
+        }
+    }
+
+    /**
+     * Returns the digest algorithm for this {@code JarSigner}.
+     * <p>
+     * The return value is never null.
+     *
+     * @return the digest algorithm.
+     */
+    public String getDigestAlgorithm() {
+        return digestalg[0];
+    }
+
+    /**
+     * Returns the signature algorithm for this {@code JarSigner}.
+     * <p>
+     * The return value is never null.
+     *
+     * @return the signature algorithm.
+     */
+    public String getSignatureAlgorithm() {
+        return sigalg;
+    }
+
+    /**
+     * Returns the URI of the Time Stamping Authority (TSA).
+     *
+     * @return the URI of the TSA.
+     */
+    public URI getTsa() {
+        return tsaUrl;
+    }
+
+    /**
+     * Returns the signer name of this {@code JarSigner}.
+     * <p>
+     * The return value is never null.
+     *
+     * @return the signer name.
+     */
+    public String getSignerName() {
+        return signerName;
+    }
+
+    /**
+     * Returns the value of an additional implementation-specific property
+     * indicated by the specified key. If a property is not set but has a
+     * default value, the default value will be returned.
+     *
+     * @implNote See {@link JarSigner.Builder#setProperty} for a list of
+     * properties this implementation supports. All property names are
+     * case-insensitive.
+     *
+     * @param key the name of the property.
+     * @return the value for the property.
+     * @throws UnsupportedOperationException if the key is not supported
+     *      by this implementation.
+     */
+    public String getProperty(String key) {
+        Objects.requireNonNull(key);
+        switch (key.toLowerCase(Locale.US)) {
+            case "tsadigestalg":
+                return tSADigestAlg;
+            case "tsapolicyid":
+                return tSAPolicyID;
+            case "internalsf":
+                return Boolean.toString(!externalSF);
+            case "sectionsonly":
+                return Boolean.toString(!signManifest);
+            case "altsignerpath":
+                return altSignerPath;
+            case "altsigner":
+                return altSigner;
+            default:
+                throw new UnsupportedOperationException(
+                        "Unsupported key " + key);
+        }
+    }
+
+    private void sign0(ZipFile zipFile, OutputStream os)
+            throws IOException, CertificateException, NoSuchAlgorithmException,
+            SignatureException, InvalidKeyException {
+        MessageDigest[] digests;
+        try {
+            digests = new MessageDigest[digestalg.length];
+            for (int i = 0; i < digestalg.length; i++) {
+                if (digestProvider == null) {
+                    digests[i] = MessageDigest.getInstance(digestalg[i]);
+                } else {
+                    digests[i] = MessageDigest.getInstance(
+                            digestalg[i], digestProvider);
+                }
+            }
+        } catch (NoSuchAlgorithmException asae) {
+            // Should not happen. User provided alg were checked, and default
+            // alg should always be available.
+            throw new AssertionError(asae);
+        }
+
+        PrintStream ps = new PrintStream(os);
+        ZipOutputStream zos = new ZipOutputStream(ps);
+
+        Manifest manifest = new Manifest();
+        Map<String, Attributes> mfEntries = manifest.getEntries();
+
+        // The Attributes of manifest before updating
+        Attributes oldAttr = null;
+
+        boolean mfModified = false;
+        boolean mfCreated = false;
+        byte[] mfRawBytes = null;
+
+        // Check if manifest exists
+        ZipEntry mfFile;
+        if ((mfFile = getManifestFile(zipFile)) != null) {
+            // Manifest exists. Read its raw bytes.
+            mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes();
+            manifest.read(new ByteArrayInputStream(mfRawBytes));
+            oldAttr = (Attributes) (manifest.getMainAttributes().clone());
+        } else {
+            // Create new manifest
+            Attributes mattr = manifest.getMainAttributes();
+            mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
+                    "1.0");
+            String javaVendor = System.getProperty("java.vendor");
+            String jdkVersion = System.getProperty("java.version");
+            mattr.putValue("Created-By", jdkVersion + " (" + javaVendor
+                    + ")");
+            mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+            mfCreated = true;
+        }
+
+        /*
+         * For each entry in jar
+         * (except for signature-related META-INF entries),
+         * do the following:
+         *
+         * - if entry is not contained in manifest, add it to manifest;
+         * - if entry is contained in manifest, calculate its hash and
+         *   compare it with the one in the manifest; if they are
+         *   different, replace the hash in the manifest with the newly
+         *   generated one. (This may invalidate existing signatures!)
+         */
+        Vector<ZipEntry> mfFiles = new Vector<>();
+
+        boolean wasSigned = false;
+
+        for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
+             enum_.hasMoreElements(); ) {
+            ZipEntry ze = enum_.nextElement();
+
+            if (ze.getName().startsWith(META_INF)) {
+                // Store META-INF files in vector, so they can be written
+                // out first
+                mfFiles.addElement(ze);
+
+                if (SignatureFileVerifier.isBlockOrSF(
+                        ze.getName().toUpperCase(Locale.ENGLISH))) {
+                    wasSigned = true;
+                }
+
+                if (SignatureFileVerifier.isSigningRelated(ze.getName())) {
+                    // ignore signature-related and manifest files
+                    continue;
+                }
+            }
+
+            if (manifest.getAttributes(ze.getName()) != null) {
+                // jar entry is contained in manifest, check and
+                // possibly update its digest attributes
+                if (updateDigests(ze, zipFile, digests,
+                        manifest)) {
+                    mfModified = true;
+                }
+            } else if (!ze.isDirectory()) {
+                // Add entry to manifest
+                Attributes attrs = getDigestAttributes(ze, zipFile, digests);
+                mfEntries.put(ze.getName(), attrs);
+                mfModified = true;
+            }
+        }
+
+        // Recalculate the manifest raw bytes if necessary
+        if (mfModified) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            manifest.write(baos);
+            if (wasSigned) {
+                byte[] newBytes = baos.toByteArray();
+                if (mfRawBytes != null
+                        && oldAttr.equals(manifest.getMainAttributes())) {
+
+                    /*
+                     * Note:
+                     *
+                     * The Attributes object is based on HashMap and can handle
+                     * continuation columns. Therefore, even if the contents are
+                     * not changed (in a Map view), the bytes that it write()
+                     * may be different from the original bytes that it read()
+                     * from. Since the signature on the main attributes is based
+                     * on raw bytes, we must retain the exact bytes.
+                     */
+
+                    int newPos = findHeaderEnd(newBytes);
+                    int oldPos = findHeaderEnd(mfRawBytes);
+
+                    if (newPos == oldPos) {
+                        System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
+                    } else {
+                        // cat oldHead newTail > newBytes
+                        byte[] lastBytes = new byte[oldPos +
+                                newBytes.length - newPos];
+                        System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
+                        System.arraycopy(newBytes, newPos, lastBytes, oldPos,
+                                newBytes.length - newPos);
+                        newBytes = lastBytes;
+                    }
+                }
+                mfRawBytes = newBytes;
+            } else {
+                mfRawBytes = baos.toByteArray();
+            }
+        }
+
+        // Write out the manifest
+        if (mfModified) {
+            // manifest file has new length
+            mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+        }
+        if (handler != null) {
+            if (mfCreated) {
+                handler.accept("adding", mfFile.getName());
+            } else if (mfModified) {
+                handler.accept("updating", mfFile.getName());
+            }
+        }
+
+        zos.putNextEntry(mfFile);
+        zos.write(mfRawBytes);
+
+        // Calculate SignatureFile (".SF") and SignatureBlockFile
+        ManifestDigester manDig = new ManifestDigester(mfRawBytes);
+        SignatureFile sf = new SignatureFile(digests, manifest, manDig,
+                signerName, signManifest);
+
+        byte[] block;
+
+        Signature signer;
+        if (sigProvider == null ) {
+            signer = Signature.getInstance(sigalg);
+        } else {
+            signer = Signature.getInstance(sigalg, sigProvider);
+        }
+        signer.initSign(privateKey);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        sf.write(baos);
+
+        byte[] content = baos.toByteArray();
+
+        signer.update(content);
+        byte[] signature = signer.sign();
+
+        @SuppressWarnings("deprecation")
+        ContentSigner signingMechanism = null;
+        if (altSigner != null) {
+            signingMechanism = loadSigningMechanism(altSigner,
+                    altSignerPath);
+        }
+
+        @SuppressWarnings("deprecation")
+        ContentSignerParameters params =
+                new JarSignerParameters(null, tsaUrl, tSAPolicyID,
+                        tSADigestAlg, signature,
+                        signer.getAlgorithm(), certChain, content, zipFile);
+        block = sf.generateBlock(params, externalSF, signingMechanism);
+
+        String sfFilename = sf.getMetaName();
+        String bkFilename = sf.getBlockName(privateKey);
+
+        ZipEntry sfFile = new ZipEntry(sfFilename);
+        ZipEntry bkFile = new ZipEntry(bkFilename);
+
+        long time = System.currentTimeMillis();
+        sfFile.setTime(time);
+        bkFile.setTime(time);
+
+        // signature file
+        zos.putNextEntry(sfFile);
+        sf.write(zos);
+
+        if (handler != null) {
+            if (zipFile.getEntry(sfFilename) != null) {
+                handler.accept("updating", sfFilename);
+            } else {
+                handler.accept("adding", sfFilename);
+            }
+        }
+
+        // signature block file
+        zos.putNextEntry(bkFile);
+        zos.write(block);
+
+        if (handler != null) {
+            if (zipFile.getEntry(bkFilename) != null) {
+                handler.accept("updating", bkFilename);
+            } else {
+                handler.accept("adding", bkFilename);
+            }
+        }
+
+        // Write out all other META-INF files that we stored in the
+        // vector
+        for (int i = 0; i < mfFiles.size(); i++) {
+            ZipEntry ze = mfFiles.elementAt(i);
+            if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
+                    && !ze.getName().equalsIgnoreCase(sfFilename)
+                    && !ze.getName().equalsIgnoreCase(bkFilename)) {
+                if (handler != null) {
+                    if (manifest.getAttributes(ze.getName()) != null) {
+                        handler.accept("signing", ze.getName());
+                    } else if (!ze.isDirectory()) {
+                        handler.accept("adding", ze.getName());
+                    }
+                }
+                writeEntry(zipFile, zos, ze);
+            }
+        }
+
+        // Write out all other files
+        for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
+             enum_.hasMoreElements(); ) {
+            ZipEntry ze = enum_.nextElement();
+
+            if (!ze.getName().startsWith(META_INF)) {
+                if (handler != null) {
+                    if (manifest.getAttributes(ze.getName()) != null) {
+                        handler.accept("signing", ze.getName());
+                    } else {
+                        handler.accept("adding", ze.getName());
+                    }
+                }
+                writeEntry(zipFile, zos, ze);
+            }
+        }
+        zipFile.close();
+        zos.close();
+    }
+
+    private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
+            throws IOException {
+        ZipEntry ze2 = new ZipEntry(ze.getName());
+        ze2.setMethod(ze.getMethod());
+        ze2.setTime(ze.getTime());
+        ze2.setComment(ze.getComment());
+        ze2.setExtra(ze.getExtra());
+        if (ze.getMethod() == ZipEntry.STORED) {
+            ze2.setSize(ze.getSize());
+            ze2.setCrc(ze.getCrc());
+        }
+        os.putNextEntry(ze2);
+        writeBytes(zf, ze, os);
+    }
+
+    private void writeBytes
+            (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
+        try (InputStream is = zf.getInputStream(ze)) {
+            is.transferTo(os);
+        }
+    }
+
+    private boolean updateDigests(ZipEntry ze, ZipFile zf,
+                                  MessageDigest[] digests,
+                                  Manifest mf) throws IOException {
+        boolean update = false;
+
+        Attributes attrs = mf.getAttributes(ze.getName());
+        String[] base64Digests = getDigests(ze, zf, digests);
+
+        for (int i = 0; i < digests.length; i++) {
+            // The entry name to be written into attrs
+            String name = null;
+            try {
+                // Find if the digest already exists. An algorithm could have
+                // different names. For example, last time it was SHA, and this
+                // time it's SHA-1.
+                AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
+                for (Object key : attrs.keySet()) {
+                    if (key instanceof Attributes.Name) {
+                        String n = key.toString();
+                        if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
+                            String tmp = n.substring(0, n.length() - 7);
+                            if (AlgorithmId.get(tmp).equals(aid)) {
+                                name = n;
+                                break;
+                            }
+                        }
+                    }
+                }
+            } catch (NoSuchAlgorithmException nsae) {
+                // Ignored. Writing new digest entry.
+            }
+
+            if (name == null) {
+                name = digests[i].getAlgorithm() + "-Digest";
+                attrs.putValue(name, base64Digests[i]);
+                update = true;
+            } else {
+                // compare digests, and replace the one in the manifest
+                // if they are different
+                String mfDigest = attrs.getValue(name);
+                if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
+                    attrs.putValue(name, base64Digests[i]);
+                    update = true;
+                }
+            }
+        }
+        return update;
+    }
+
+    private Attributes getDigestAttributes(
+            ZipEntry ze, ZipFile zf, MessageDigest[] digests)
+            throws IOException {
+
+        String[] base64Digests = getDigests(ze, zf, digests);
+        Attributes attrs = new Attributes();
+
+        for (int i = 0; i < digests.length; i++) {
+            attrs.putValue(digests[i].getAlgorithm() + "-Digest",
+                    base64Digests[i]);
+        }
+        return attrs;
+    }
+
+    /*
+     * Returns manifest entry from given jar file, or null if given jar file
+     * does not have a manifest entry.
+     */
+    private ZipEntry getManifestFile(ZipFile zf) {
+        ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
+        if (ze == null) {
+            // Check all entries for matching name
+            Enumeration<? extends ZipEntry> enum_ = zf.entries();
+            while (enum_.hasMoreElements() && ze == null) {
+                ze = enum_.nextElement();
+                if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
+                        (ze.getName())) {
+                    ze = null;
+                }
+            }
+        }
+        return ze;
+    }
+
+    private String[] getDigests(
+            ZipEntry ze, ZipFile zf, MessageDigest[] digests)
+            throws IOException {
+
+        int n, i;
+        try (InputStream is = zf.getInputStream(ze)) {
+            long left = ze.getSize();
+            byte[] buffer = new byte[8192];
+            while ((left > 0)
+                    && (n = is.read(buffer, 0, buffer.length)) != -1) {
+                for (i = 0; i < digests.length; i++) {
+                    digests[i].update(buffer, 0, n);
+                }
+                left -= n;
+            }
+        }
+
+        // complete the digests
+        String[] base64Digests = new String[digests.length];
+        for (i = 0; i < digests.length; i++) {
+            base64Digests[i] = Base64.getEncoder()
+                    .encodeToString(digests[i].digest());
+        }
+        return base64Digests;
+    }
+
+    @SuppressWarnings("fallthrough")
+    private int findHeaderEnd(byte[] bs) {
+        // Initial state true to deal with empty header
+        boolean newline = true;     // just met a newline
+        int len = bs.length;
+        for (int i = 0; i < len; i++) {
+            switch (bs[i]) {
+                case '\r':
+                    if (i < len - 1 && bs[i + 1] == '\n') i++;
+                    // fallthrough
+                case '\n':
+                    if (newline) return i + 1;    //+1 to get length
+                    newline = true;
+                    break;
+                default:
+                    newline = false;
+            }
+        }
+        // If header end is not found, it means the MANIFEST.MF has only
+        // the main attributes section and it does not end with 2 newlines.
+        // Returns the whole length so that it can be completely replaced.
+        return len;
+    }
+
+    /*
+     * Try to load the specified signing mechanism.
+     * The URL class loader is used.
+     */
+    @SuppressWarnings("deprecation")
+    private ContentSigner loadSigningMechanism(String signerClassName,
+                                               String signerClassPath) {
+
+        // construct class loader
+        String cpString;   // make sure env.class.path defaults to dot
+
+        // do prepends to get correct ordering
+        cpString = PathList.appendPath(
+                System.getProperty("env.class.path"), null);
+        cpString = PathList.appendPath(
+                System.getProperty("java.class.path"), cpString);
+        cpString = PathList.appendPath(signerClassPath, cpString);
+        URL[] urls = PathList.pathToURLs(cpString);
+        ClassLoader appClassLoader = new URLClassLoader(urls);
+
+        try {
+            // attempt to find signer
+            Class<?> signerClass = appClassLoader.loadClass(signerClassName);
+            Object signer = signerClass.newInstance();
+            return (ContentSigner) signer;
+        } catch (ClassNotFoundException|InstantiationException|
+                IllegalAccessException|ClassCastException e) {
+            throw new IllegalArgumentException(
+                    "Invalid altSigner or altSignerPath", e);
+        }
+    }
+
+    static class SignatureFile {
+
+        /**
+         * SignatureFile
+         */
+        Manifest sf;
+
+        /**
+         * .SF base name
+         */
+        String baseName;
+
+        public SignatureFile(MessageDigest digests[],
+                             Manifest mf,
+                             ManifestDigester md,
+                             String baseName,
+                             boolean signManifest) {
+
+            this.baseName = baseName;
+
+            String version = System.getProperty("java.version");
+            String javaVendor = System.getProperty("java.vendor");
+
+            sf = new Manifest();
+            Attributes mattr = sf.getMainAttributes();
+
+            mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
+            mattr.putValue("Created-By", version + " (" + javaVendor + ")");
+
+            if (signManifest) {
+                for (MessageDigest digest: digests) {
+                    mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest",
+                            Base64.getEncoder().encodeToString(
+                                    md.manifestDigest(digest)));
+                }
+            }
+
+            // create digest of the manifest main attributes
+            ManifestDigester.Entry mde =
+                    md.get(ManifestDigester.MF_MAIN_ATTRS, false);
+            if (mde != null) {
+                for (MessageDigest digest: digests) {
+                    mattr.putValue(digest.getAlgorithm() +
+                                    "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
+                            Base64.getEncoder().encodeToString(
+                                    mde.digest(digest)));
+                }
+            } else {
+                throw new IllegalStateException
+                        ("ManifestDigester failed to create " +
+                                "Manifest-Main-Attribute entry");
+            }
+
+            // go through the manifest entries and create the digests
+            Map<String, Attributes> entries = sf.getEntries();
+            for (String name: mf.getEntries().keySet()) {
+                mde = md.get(name, false);
+                if (mde != null) {
+                    Attributes attr = new Attributes();
+                    for (MessageDigest digest: digests) {
+                        attr.putValue(digest.getAlgorithm() + "-Digest",
+                                Base64.getEncoder().encodeToString(
+                                        mde.digest(digest)));
+                    }
+                    entries.put(name, attr);
+                }
+            }
+        }
+
+        // Write .SF file
+        public void write(OutputStream out) throws IOException {
+            sf.write(out);
+        }
+
+        // get .SF file name
+        public String getMetaName() {
+            return "META-INF/" + baseName + ".SF";
+        }
+
+        // get .DSA (or .DSA, .EC) file name
+        public String getBlockName(PrivateKey privateKey) {
+            String keyAlgorithm = privateKey.getAlgorithm();
+            return "META-INF/" + baseName + "." + keyAlgorithm;
+        }
+
+        // Generates the PKCS#7 content of block file
+        @SuppressWarnings("deprecation")
+        public byte[] generateBlock(ContentSignerParameters params,
+                                    boolean externalSF,
+                                    ContentSigner signingMechanism)
+                throws NoSuchAlgorithmException,
+                       IOException, CertificateException {
+
+            if (signingMechanism == null) {
+                signingMechanism = new TimestampedSigner();
+            }
+            return signingMechanism.generateSignedData(
+                    params,
+                    externalSF,
+                    params.getTimestampingAuthority() != null
+                        || params.getTimestampingAuthorityCertificate() != null);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    class JarSignerParameters implements ContentSignerParameters {
+
+        private String[] args;
+        private URI tsa;
+        private byte[] signature;
+        private String signatureAlgorithm;
+        private X509Certificate[] signerCertificateChain;
+        private byte[] content;
+        private ZipFile source;
+        private String tSAPolicyID;
+        private String tSADigestAlg;
+
+        JarSignerParameters(String[] args, URI tsa,
+                            String tSAPolicyID, String tSADigestAlg,
+                            byte[] signature, String signatureAlgorithm,
+                            X509Certificate[] signerCertificateChain,
+                            byte[] content, ZipFile source) {
+
+            Objects.requireNonNull(signature);
+            Objects.requireNonNull(signatureAlgorithm);
+            Objects.requireNonNull(signerCertificateChain);
+
+            this.args = args;
+            this.tsa = tsa;
+            this.tSAPolicyID = tSAPolicyID;
+            this.tSADigestAlg = tSADigestAlg;
+            this.signature = signature;
+            this.signatureAlgorithm = signatureAlgorithm;
+            this.signerCertificateChain = signerCertificateChain;
+            this.content = content;
+            this.source = source;
+        }
+
+        public String[] getCommandLine() {
+            return args;
+        }
+
+        public URI getTimestampingAuthority() {
+            return tsa;
+        }
+
+        public X509Certificate getTimestampingAuthorityCertificate() {
+            // We don't use this param. Always provide tsaURI.
+            return null;
+        }
+
+        public String getTSAPolicyID() {
+            return tSAPolicyID;
+        }
+
+        public String getTSADigestAlg() {
+            return tSADigestAlg;
+        }
+
+        public byte[] getSignature() {
+            return signature;
+        }
+
+        public String getSignatureAlgorithm() {
+            return signatureAlgorithm;
+        }
+
+        public X509Certificate[] getSignerCertificateChain() {
+            return signerCertificateChain;
+        }
+
+        public byte[] getContent() {
+            return content;
+        }
+
+        public ZipFile getSource() {
+            return source;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,56 @@
+/*
+ * 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.security.jarsigner;
+
+/**
+ * This exception is thrown when {@link JarSigner#sign} fails.
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public class JarSignerException extends RuntimeException {
+
+    private static final long serialVersionUID = -4732217075689309530L;
+
+    /**
+     * Constructs a new {@code JarSignerException} with the specified detail
+     * message and cause.
+     * <p>
+     * Note that the detail message associated with
+     * {@code cause} is <i>not</i> automatically incorporated in
+     * this {@code JarSignerException}'s detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *      by the {@link #getMessage()} method).
+     * @param cause the cause (which is saved for later retrieval by the
+     *      {@link #getCause()} method).  (A {@code null} value is permitted,
+     *      and indicates that the cause is nonexistent or unknown.)
+     */
+    public JarSignerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
+
--- a/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Mon Nov 23 10:00:50 2015 -0800
@@ -29,22 +29,16 @@
 import java.util.*;
 import java.util.zip.*;
 import java.util.jar.*;
-import java.math.BigInteger;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.text.Collator;
 import java.text.MessageFormat;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.security.cert.CertificateException;
 import java.security.*;
-import java.lang.reflect.Constructor;
 
-import com.sun.jarsigner.ContentSigner;
-import com.sun.jarsigner.ContentSignerParameters;
 import java.net.SocketTimeoutException;
 import java.net.URL;
-import java.net.URLClassLoader;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
 import java.security.cert.CertificateExpiredException;
@@ -53,11 +47,12 @@
 import java.security.cert.PKIXParameters;
 import java.security.cert.TrustAnchor;
 import java.util.Map.Entry;
+
+import jdk.security.jarsigner.JarSigner;
+import jdk.security.jarsigner.JarSignerException;
 import sun.security.tools.KeyStoreUtil;
-import sun.security.tools.PathList;
 import sun.security.x509.*;
 import sun.security.util.*;
-import java.util.Base64;
 
 
 /**
@@ -88,10 +83,6 @@
         collator.setStrength(Collator.PRIMARY);
     }
 
-    private static final String META_INF = "META-INF/";
-
-    private static final Class<?>[] PARAM_STRING = { String.class };
-
     private static final String NONE = "NONE";
     private static final String P11KEYSTORE = "PKCS11";
 
@@ -133,13 +124,13 @@
     char[] keypass; // private key password
     String sigfile; // name of .SF file
     String sigalg; // name of signature algorithm
-    String digestalg = "SHA-256"; // name of digest algorithm
+    String digestalg; // name of digest algorithm
     String signedjar; // output filename
     String tsaUrl; // location of the Timestamping Authority
     String tsaAlias; // alias for the Timestamping Authority's certificate
     String altCertChain; // file to read alternative cert chain from
     String tSAPolicyID;
-    String tSADigestAlg = "SHA-256";
+    String tSADigestAlg;
     boolean verify = false; // verify the jar
     String verbose = null; // verbose output when signing/verifying
     boolean showcerts = false; // show certs when verifying
@@ -149,9 +140,6 @@
     boolean strict = false;  // treat warnings as error
 
     // read zip entry raw bytes
-    private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
-    private byte[] buffer = new byte[8192];
-    private ContentSigner signingMechanism = null;
     private String altSignerClass = null;
     private String altSignerClasspath = null;
     private ZipFile zipFile = null;
@@ -216,6 +204,9 @@
                     if ((keystore != null) || (storepass != null)) {
                         System.out.println(rb.getString("jarsigner.error.") +
                                         e.getMessage());
+                        if (debug) {
+                            e.printStackTrace();
+                        }
                         System.exit(1);
                     }
                 }
@@ -229,12 +220,7 @@
                 loadKeyStore(keystore, true);
                 getAliasInfo(alias);
 
-                // load the alternative signing mechanism
-                if (altSignerClass != null) {
-                    signingMechanism = loadSigningMechanism(altSignerClass,
-                        altSignerClasspath);
-                }
-                signJar(jarfile, alias, args);
+                signJar(jarfile, alias);
             }
         } catch (Exception e) {
             System.out.println(rb.getString("jarsigner.error.") + e);
@@ -626,8 +612,7 @@
                 InputStream is = null;
                 try {
                     is = jf.getInputStream(je);
-                    int n;
-                    while ((n = is.read(buffer, 0, buffer.length)) != -1) {
+                    while (is.read(buffer, 0, buffer.length) != -1) {
                         // we just read. this will throw a SecurityException
                         // if  a signature/digest check fails.
                     }
@@ -1035,7 +1020,6 @@
             return cacheForInKS.get(signer);
         }
 
-        boolean found = false;
         int result = 0;
         List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
         for (Certificate c : certs) {
@@ -1058,7 +1042,6 @@
                     }
                     if (alias != null) {
                         storeHash.put(c, "(" + alias + ")");
-                        found = true;
                         result |= IN_KEYSTORE;
                     }
                 }
@@ -1090,7 +1073,7 @@
         return output;
     }
 
-    void signJar(String jarName, String alias, String[] args)
+    void signJar(String jarName, String alias)
         throws Exception {
         boolean aliasUsed = false;
         X509Certificate tsaCert = null;
@@ -1110,17 +1093,17 @@
         for (int j = 0; j < sigfile.length(); j++) {
             char c = sigfile.charAt(j);
             if (!
-                ((c>= 'A' && c<= 'Z') ||
-                (c>= '0' && c<= '9') ||
-                (c == '-') ||
-                (c == '_'))) {
+                    ((c>= 'A' && c<= 'Z') ||
+                            (c>= '0' && c<= '9') ||
+                            (c == '-') ||
+                            (c == '_'))) {
                 if (aliasUsed) {
                     // convert illegal characters from the alias to be _'s
                     c = '_';
                 } else {
-                 throw new
-                   RuntimeException(rb.getString
-                        ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
+                    throw new
+                            RuntimeException(rb.getString
+                            ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
                 }
             }
             tmpSigFile.append(c);
@@ -1149,275 +1132,88 @@
             error(rb.getString("unable.to.create.")+tmpJarName, ioe);
         }
 
-        PrintStream ps = new PrintStream(fos);
-        ZipOutputStream zos = new ZipOutputStream(ps);
-
-        /* First guess at what they might be - we don't xclude RSA ones. */
-        String sfFilename = (META_INF + sigfile + ".SF").toUpperCase(Locale.ENGLISH);
-        String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase(Locale.ENGLISH);
-
-        Manifest manifest = new Manifest();
-        Map<String,Attributes> mfEntries = manifest.getEntries();
-
-        // The Attributes of manifest before updating
-        Attributes oldAttr = null;
-
-        boolean mfModified = false;
-        boolean mfCreated = false;
-        byte[] mfRawBytes = null;
-
-        try {
-            MessageDigest digests[] = { MessageDigest.getInstance(digestalg) };
+        CertPath cp = CertificateFactory.getInstance("X.509")
+                .generateCertPath(Arrays.asList(certChain));
+        JarSigner.Builder builder = new JarSigner.Builder(privateKey, cp);
 
-            // Check if manifest exists
-            ZipEntry mfFile;
-            if ((mfFile = getManifestFile(zipFile)) != null) {
-                // Manifest exists. Read its raw bytes.
-                mfRawBytes = getBytes(zipFile, mfFile);
-                manifest.read(new ByteArrayInputStream(mfRawBytes));
-                oldAttr = (Attributes)(manifest.getMainAttributes().clone());
-            } else {
-                // Create new manifest
-                Attributes mattr = manifest.getMainAttributes();
-                mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
-                               "1.0");
-                String javaVendor = System.getProperty("java.vendor");
-                String jdkVersion = System.getProperty("java.version");
-                mattr.putValue("Created-By", jdkVersion + " (" +javaVendor
-                               + ")");
-                mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
-                mfCreated = true;
-            }
+        if (verbose != null) {
+            builder.eventHandler((action, file) -> {
+                System.out.println(rb.getString("." + action + ".") + file);
+            });
+        }
+
+        if (digestalg != null) {
+            builder.digestAlgorithm(digestalg);
+        }
+        if (sigalg != null) {
+            builder.signatureAlgorithm(sigalg);
+        }
 
-            /*
-             * For each entry in jar
-             * (except for signature-related META-INF entries),
-             * do the following:
-             *
-             * - if entry is not contained in manifest, add it to manifest;
-             * - if entry is contained in manifest, calculate its hash and
-             *   compare it with the one in the manifest; if they are
-             *   different, replace the hash in the manifest with the newly
-             *   generated one. (This may invalidate existing signatures!)
-             */
-            Vector<ZipEntry> mfFiles = new Vector<>();
+        URI tsaURI = null;
 
-            boolean wasSigned = false;
-
-            for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
-                        enum_.hasMoreElements();) {
-                ZipEntry ze = enum_.nextElement();
-
-                if (ze.getName().startsWith(META_INF)) {
-                    // Store META-INF files in vector, so they can be written
-                    // out first
-                    mfFiles.addElement(ze);
+        if (tsaUrl != null) {
+            tsaURI = new URI(tsaUrl);
+        } else if (tsaAlias != null) {
+            tsaCert = getTsaCert(tsaAlias);
+            tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
+        }
 
-                    if (SignatureFileVerifier.isBlockOrSF(
-                            ze.getName().toUpperCase(Locale.ENGLISH))) {
-                        wasSigned = true;
-                    }
-
-                    if (signatureRelated(ze.getName())) {
-                        // ignore signature-related and manifest files
-                        continue;
-                    }
-                }
-
-                if (manifest.getAttributes(ze.getName()) != null) {
-                    // jar entry is contained in manifest, check and
-                    // possibly update its digest attributes
-                    if (updateDigests(ze, zipFile, digests,
-                                      manifest) == true) {
-                        mfModified = true;
-                    }
-                } else if (!ze.isDirectory()) {
-                    // Add entry to manifest
-                    Attributes attrs = getDigestAttributes(ze, zipFile,
-                                                           digests);
-                    mfEntries.put(ze.getName(), attrs);
-                    mfModified = true;
+        if (tsaURI != null) {
+            if (verbose != null) {
+                System.out.println(
+                        rb.getString("requesting.a.signature.timestamp"));
+                if (tsaUrl != null) {
+                    System.out.println(rb.getString("TSA.location.") + tsaUrl);
+                } else if (tsaCert != null) {
+                    System.out.println(rb.getString("TSA.certificate.") +
+                            printCert("", tsaCert, false, null, false));
                 }
             }
-
-            // Recalculate the manifest raw bytes if necessary
-            if (mfModified) {
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                manifest.write(baos);
-                if (wasSigned) {
-                    byte[] newBytes = baos.toByteArray();
-                    if (mfRawBytes != null
-                            && oldAttr.equals(manifest.getMainAttributes())) {
-
-                        /*
-                         * Note:
-                         *
-                         * The Attributes object is based on HashMap and can handle
-                         * continuation columns. Therefore, even if the contents are
-                         * not changed (in a Map view), the bytes that it write()
-                         * may be different from the original bytes that it read()
-                         * from. Since the signature on the main attributes is based
-                         * on raw bytes, we must retain the exact bytes.
-                         */
-
-                        int newPos = findHeaderEnd(newBytes);
-                        int oldPos = findHeaderEnd(mfRawBytes);
-
-                        if (newPos == oldPos) {
-                            System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
-                        } else {
-                            // cat oldHead newTail > newBytes
-                            byte[] lastBytes = new byte[oldPos +
-                                    newBytes.length - newPos];
-                            System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
-                            System.arraycopy(newBytes, newPos, lastBytes, oldPos,
-                                    newBytes.length - newPos);
-                            newBytes = lastBytes;
-                        }
-                    }
-                    mfRawBytes = newBytes;
-                } else {
-                    mfRawBytes = baos.toByteArray();
-                }
+            builder.tsa(tsaURI);
+            if (tSADigestAlg != null) {
+                builder.setProperty("tsaDigestAlg", tSADigestAlg);
             }
 
-            // Write out the manifest
-            if (mfModified) {
-                // manifest file has new length
-                mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+            if (tSAPolicyID != null) {
+                builder.setProperty("tsaPolicyId", tSAPolicyID);
             }
+        } else {
+            noTimestamp = true;
+        }
+
+        if (altSignerClass != null) {
+            builder.setProperty("altSigner", altSignerClass);
             if (verbose != null) {
-                if (mfCreated) {
-                    System.out.println(rb.getString(".adding.") +
-                                        mfFile.getName());
-                } else if (mfModified) {
-                    System.out.println(rb.getString(".updating.") +
-                                        mfFile.getName());
-                }
+                System.out.println(
+                        rb.getString("using.an.alternative.signing.mechanism"));
             }
-            zos.putNextEntry(mfFile);
-            zos.write(mfRawBytes);
+        }
 
-            // Calculate SignatureFile (".SF") and SignatureBlockFile
-            ManifestDigester manDig = new ManifestDigester(mfRawBytes);
-            SignatureFile sf = new SignatureFile(digests, manifest, manDig,
-                                                 sigfile, signManifest);
+        if (altSignerClasspath != null) {
+            builder.setProperty("altSignerPath", altSignerClasspath);
+        }
 
-            if (tsaAlias != null) {
-                tsaCert = getTsaCert(tsaAlias);
-            }
+        builder.signerName(sigfile);
 
-            if (tsaUrl == null && tsaCert == null) {
-                noTimestamp = true;
-            }
-
-            SignatureFile.Block block = null;
+        builder.setProperty("sectionsOnly", Boolean.toString(!signManifest));
+        builder.setProperty("internalSF", Boolean.toString(!externalSF));
 
-            try {
-                block =
-                    sf.generateBlock(privateKey, sigalg, certChain,
-                        externalSF, tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg,
-                        signingMechanism, args, zipFile);
-            } catch (SocketTimeoutException e) {
+        try {
+            builder.build().sign(zipFile, fos);
+        } catch (JarSignerException e) {
+            Throwable cause = e.getCause();
+            if (cause != null && cause instanceof SocketTimeoutException) {
                 // Provide a helpful message when TSA is beyond a firewall
                 error(rb.getString("unable.to.sign.jar.") +
-                rb.getString("no.response.from.the.Timestamping.Authority.") +
-                "\n  -J-Dhttp.proxyHost=<hostname>" +
-                "\n  -J-Dhttp.proxyPort=<portnumber>\n" +
-                rb.getString("or") +
-                "\n  -J-Dhttps.proxyHost=<hostname> " +
-                "\n  -J-Dhttps.proxyPort=<portnumber> ", e);
-            }
-
-            sfFilename = sf.getMetaName();
-            bkFilename = block.getMetaName();
-
-            ZipEntry sfFile = new ZipEntry(sfFilename);
-            ZipEntry bkFile = new ZipEntry(bkFilename);
-
-            long time = System.currentTimeMillis();
-            sfFile.setTime(time);
-            bkFile.setTime(time);
-
-            // signature file
-            zos.putNextEntry(sfFile);
-            sf.write(zos);
-            if (verbose != null) {
-                if (zipFile.getEntry(sfFilename) != null) {
-                    System.out.println(rb.getString(".updating.") +
-                                sfFilename);
-                } else {
-                    System.out.println(rb.getString(".adding.") +
-                                sfFilename);
-                }
+                        rb.getString("no.response.from.the.Timestamping.Authority.") +
+                        "\n  -J-Dhttp.proxyHost=<hostname>" +
+                        "\n  -J-Dhttp.proxyPort=<portnumber>\n" +
+                        rb.getString("or") +
+                        "\n  -J-Dhttps.proxyHost=<hostname> " +
+                        "\n  -J-Dhttps.proxyPort=<portnumber> ", e);
+            } else {
+                error(rb.getString("unable.to.sign.jar.")+e.getCause(), e.getCause());
             }
-
-            if (verbose != null) {
-                if (tsaUrl != null || tsaCert != null) {
-                    System.out.println(
-                        rb.getString("requesting.a.signature.timestamp"));
-                }
-                if (tsaUrl != null) {
-                    System.out.println(rb.getString("TSA.location.") + tsaUrl);
-                }
-                if (tsaCert != null) {
-                    URI tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
-                    if (tsaURI != null) {
-                        System.out.println(rb.getString("TSA.location.") +
-                            tsaURI);
-                    }
-                    System.out.println(rb.getString("TSA.certificate.") +
-                        printCert("", tsaCert, false, null, false));
-                }
-                if (signingMechanism != null) {
-                    System.out.println(
-                        rb.getString("using.an.alternative.signing.mechanism"));
-                }
-            }
-
-            // signature block file
-            zos.putNextEntry(bkFile);
-            block.write(zos);
-            if (verbose != null) {
-                if (zipFile.getEntry(bkFilename) != null) {
-                    System.out.println(rb.getString(".updating.") +
-                        bkFilename);
-                } else {
-                    System.out.println(rb.getString(".adding.") +
-                        bkFilename);
-                }
-            }
-
-            // Write out all other META-INF files that we stored in the
-            // vector
-            for (int i=0; i<mfFiles.size(); i++) {
-                ZipEntry ze = mfFiles.elementAt(i);
-                if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
-                    && !ze.getName().equalsIgnoreCase(sfFilename)
-                    && !ze.getName().equalsIgnoreCase(bkFilename)) {
-                    writeEntry(zipFile, zos, ze);
-                }
-            }
-
-            // Write out all other files
-            for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
-                        enum_.hasMoreElements();) {
-                ZipEntry ze = enum_.nextElement();
-
-                if (!ze.getName().startsWith(META_INF)) {
-                    if (verbose != null) {
-                        if (manifest.getAttributes(ze.getName()) != null)
-                          System.out.println(rb.getString(".signing.") +
-                                ze.getName());
-                        else
-                          System.out.println(rb.getString(".adding.") +
-                                ze.getName());
-                    }
-                    writeEntry(zipFile, zos, ze);
-                }
-            }
-        } catch(IOException ioe) {
-            error(rb.getString("unable.to.sign.jar.")+ioe, ioe);
         } finally {
             // close the resouces
             if (zipFile != null) {
@@ -1425,8 +1221,8 @@
                 zipFile = null;
             }
 
-            if (zos != null) {
-                zos.close();
+            if (fos != null) {
+                fos.close();
             }
         }
 
@@ -1527,35 +1323,6 @@
     }
 
     /**
-     * Find the length of header inside bs. The header is a multiple (>=0)
-     * lines of attributes plus an empty line. The empty line is included
-     * in the header.
-     */
-    @SuppressWarnings("fallthrough")
-    private int findHeaderEnd(byte[] bs) {
-        // Initial state true to deal with empty header
-        boolean newline = true;     // just met a newline
-        int len = bs.length;
-        for (int i=0; i<len; i++) {
-            switch (bs[i]) {
-                case '\r':
-                    if (i < len - 1 && bs[i+1] == '\n') i++;
-                    // fallthrough
-                case '\n':
-                    if (newline) return i+1;    //+1 to get length
-                    newline = true;
-                    break;
-                default:
-                    newline = false;
-            }
-        }
-        // If header end is not found, it means the MANIFEST.MF has only
-        // the main attributes section and it does not end with 2 newlines.
-        // Returns the whole length so that it can be completely replaced.
-        return len;
-    }
-
-    /**
      * signature-related files include:
      * . META-INF/MANIFEST.MF
      * . META-INF/SIG-*
@@ -1619,45 +1386,6 @@
         return result;
     }
 
-    private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
-    throws IOException
-    {
-        ZipEntry ze2 = new ZipEntry(ze.getName());
-        ze2.setMethod(ze.getMethod());
-        ze2.setTime(ze.getTime());
-        ze2.setComment(ze.getComment());
-        ze2.setExtra(ze.getExtra());
-        if (ze.getMethod() == ZipEntry.STORED) {
-            ze2.setSize(ze.getSize());
-            ze2.setCrc(ze.getCrc());
-        }
-        os.putNextEntry(ze2);
-        writeBytes(zf, ze, os);
-    }
-
-    /**
-     * Writes all the bytes for a given entry to the specified output stream.
-     */
-    private synchronized void writeBytes
-        (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
-        int n;
-
-        InputStream is = null;
-        try {
-            is = zf.getInputStream(ze);
-            long left = ze.getSize();
-
-            while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
-                os.write(buffer, 0, n);
-                left -= n;
-            }
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-        }
-    }
-
     void loadKeyStore(String keyStoreName, boolean prompt) {
 
         if (!nullStream && keyStoreName == null) {
@@ -1958,15 +1686,13 @@
         }
     }
 
-    void error(String message)
-    {
+    void error(String message) {
         System.out.println(rb.getString("jarsigner.")+message);
         System.exit(1);
     }
 
 
-    void error(String message, Exception e)
-    {
+    void error(String message, Throwable e) {
         System.out.println(rb.getString("jarsigner.")+message);
         if (debug) {
             e.printStackTrace();
@@ -1990,8 +1716,7 @@
         }
     }
 
-    char[] getPass(String prompt)
-    {
+    char[] getPass(String prompt) {
         System.err.print(prompt);
         System.err.flush();
         try {
@@ -2008,569 +1733,4 @@
         // this shouldn't happen
         return null;
     }
-
-    /*
-     * Reads all the bytes for a given zip entry.
-     */
-    private synchronized byte[] getBytes(ZipFile zf,
-                                         ZipEntry ze) throws IOException {
-        int n;
-
-        InputStream is = null;
-        try {
-            is = zf.getInputStream(ze);
-            baos.reset();
-            long left = ze.getSize();
-
-            while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
-                baos.write(buffer, 0, n);
-                left -= n;
-            }
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-        }
-
-        return baos.toByteArray();
-    }
-
-    /*
-     * Returns manifest entry from given jar file, or null if given jar file
-     * does not have a manifest entry.
-     */
-    private ZipEntry getManifestFile(ZipFile zf) {
-        ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
-        if (ze == null) {
-            // Check all entries for matching name
-            Enumeration<? extends ZipEntry> enum_ = zf.entries();
-            while (enum_.hasMoreElements() && ze == null) {
-                ze = enum_.nextElement();
-                if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
-                    (ze.getName())) {
-                    ze = null;
-                }
-            }
-        }
-        return ze;
-    }
-
-    /*
-     * Computes the digests of a zip entry, and returns them as an array
-     * of base64-encoded strings.
-     */
-    private synchronized String[] getDigests(ZipEntry ze, ZipFile zf,
-                                             MessageDigest[] digests)
-        throws IOException {
-
-        int n, i;
-        InputStream is = null;
-        try {
-            is = zf.getInputStream(ze);
-            long left = ze.getSize();
-            while((left > 0)
-                && (n = is.read(buffer, 0, buffer.length)) != -1) {
-                for (i=0; i<digests.length; i++) {
-                    digests[i].update(buffer, 0, n);
-                }
-                left -= n;
-            }
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-        }
-
-        // complete the digests
-        String[] base64Digests = new String[digests.length];
-        for (i=0; i<digests.length; i++) {
-            base64Digests[i] = Base64.getEncoder().encodeToString(digests[i].digest());
-        }
-        return base64Digests;
-    }
-
-    /*
-     * Computes the digests of a zip entry, and returns them as a list of
-     * attributes
-     */
-    private Attributes getDigestAttributes(ZipEntry ze, ZipFile zf,
-                                           MessageDigest[] digests)
-        throws IOException {
-
-        String[] base64Digests = getDigests(ze, zf, digests);
-        Attributes attrs = new Attributes();
-
-        for (int i=0; i<digests.length; i++) {
-            attrs.putValue(digests[i].getAlgorithm()+"-Digest",
-                           base64Digests[i]);
-        }
-        return attrs;
-    }
-
-    /*
-     * Updates the digest attributes of a manifest entry, by adding or
-     * replacing digest values.
-     * A digest value is added if the manifest entry does not contain a digest
-     * for that particular algorithm.
-     * A digest value is replaced if it is obsolete.
-     *
-     * Returns true if the manifest entry has been changed, and false
-     * otherwise.
-     */
-    private boolean updateDigests(ZipEntry ze, ZipFile zf,
-                                  MessageDigest[] digests,
-                                  Manifest mf) throws IOException {
-        boolean update = false;
-
-        Attributes attrs = mf.getAttributes(ze.getName());
-        String[] base64Digests = getDigests(ze, zf, digests);
-
-        for (int i=0; i<digests.length; i++) {
-            // The entry name to be written into attrs
-            String name = null;
-            try {
-                // Find if the digest already exists
-                AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
-                for (Object key: attrs.keySet()) {
-                    if (key instanceof Attributes.Name) {
-                        String n = ((Attributes.Name)key).toString();
-                        if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
-                            String tmp = n.substring(0, n.length() - 7);
-                            if (AlgorithmId.get(tmp).equals(aid)) {
-                                name = n;
-                                break;
-                            }
-                        }
-                    }
-                }
-            } catch (NoSuchAlgorithmException nsae) {
-                // Ignored. Writing new digest entry.
-            }
-
-            if (name == null) {
-                name = digests[i].getAlgorithm()+"-Digest";
-                attrs.putValue(name, base64Digests[i]);
-                update=true;
-            } else {
-                // compare digests, and replace the one in the manifest
-                // if they are different
-                String mfDigest = attrs.getValue(name);
-                if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
-                    attrs.putValue(name, base64Digests[i]);
-                    update=true;
-                }
-            }
-        }
-        return update;
-    }
-
-    /*
-     * Try to load the specified signing mechanism.
-     * The URL class loader is used.
-     */
-    private ContentSigner loadSigningMechanism(String signerClassName,
-        String signerClassPath) throws Exception {
-
-        // construct class loader
-        String cpString = null;   // make sure env.class.path defaults to dot
-
-        // do prepends to get correct ordering
-        cpString = PathList.appendPath(System.getProperty("env.class.path"), cpString);
-        cpString = PathList.appendPath(System.getProperty("java.class.path"), cpString);
-        cpString = PathList.appendPath(signerClassPath, cpString);
-        URL[] urls = PathList.pathToURLs(cpString);
-        ClassLoader appClassLoader = new URLClassLoader(urls);
-
-        // attempt to find signer
-        Class<?> signerClass = appClassLoader.loadClass(signerClassName);
-
-        // Check that it implements ContentSigner
-        Object signer = signerClass.newInstance();
-        if (!(signer instanceof ContentSigner)) {
-            MessageFormat form = new MessageFormat(
-                rb.getString("signerClass.is.not.a.signing.mechanism"));
-            Object[] source = {signerClass.getName()};
-            throw new IllegalArgumentException(form.format(source));
-        }
-        return (ContentSigner)signer;
-    }
 }
-
-class SignatureFile {
-
-    /** SignatureFile */
-    Manifest sf;
-
-    /** .SF base name */
-    String baseName;
-
-    public SignatureFile(MessageDigest digests[],
-                         Manifest mf,
-                         ManifestDigester md,
-                         String baseName,
-                         boolean signManifest)
-
-    {
-        this.baseName = baseName;
-
-        String version = System.getProperty("java.version");
-        String javaVendor = System.getProperty("java.vendor");
-
-        sf = new Manifest();
-        Attributes mattr = sf.getMainAttributes();
-
-        mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
-        mattr.putValue("Created-By", version + " (" + javaVendor + ")");
-
-        if (signManifest) {
-            // sign the whole manifest
-            for (int i=0; i < digests.length; i++) {
-                mattr.putValue(digests[i].getAlgorithm()+"-Digest-Manifest",
-                               Base64.getEncoder().encodeToString(md.manifestDigest(digests[i])));
-            }
-        }
-
-        // create digest of the manifest main attributes
-        ManifestDigester.Entry mde =
-                md.get(ManifestDigester.MF_MAIN_ATTRS, false);
-        if (mde != null) {
-            for (int i=0; i < digests.length; i++) {
-                mattr.putValue(digests[i].getAlgorithm() +
-                        "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
-                        Base64.getEncoder().encodeToString(mde.digest(digests[i])));
-            }
-        } else {
-            throw new IllegalStateException
-                ("ManifestDigester failed to create " +
-                "Manifest-Main-Attribute entry");
-        }
-
-        /* go through the manifest entries and create the digests */
-
-        Map<String,Attributes> entries = sf.getEntries();
-        Iterator<Map.Entry<String,Attributes>> mit =
-                                mf.getEntries().entrySet().iterator();
-        while(mit.hasNext()) {
-            Map.Entry<String,Attributes> e = mit.next();
-            String name = e.getKey();
-            mde = md.get(name, false);
-            if (mde != null) {
-                Attributes attr = new Attributes();
-                for (int i=0; i < digests.length; i++) {
-                    attr.putValue(digests[i].getAlgorithm()+"-Digest",
-                                  Base64.getEncoder().encodeToString(mde.digest(digests[i])));
-                }
-                entries.put(name, attr);
-            }
-        }
-    }
-
-    /**
-     * Writes the SignatureFile to the specified OutputStream.
-     *
-     * @param out the output stream
-     * @exception IOException if an I/O error has occurred
-     */
-
-    public void write(OutputStream out) throws IOException
-    {
-        sf.write(out);
-    }
-
-    /**
-     * get .SF file name
-     */
-    public String getMetaName()
-    {
-        return "META-INF/"+ baseName + ".SF";
-    }
-
-    /**
-     * get base file name
-     */
-    public String getBaseName()
-    {
-        return baseName;
-    }
-
-    /*
-     * Generate a signed data block.
-     * If a URL or a certificate (containing a URL) for a Timestamping
-     * Authority is supplied then a signature timestamp is generated and
-     * inserted into the signed data block.
-     *
-     * @param sigalg signature algorithm to use, or null to use default
-     * @param tsaUrl The location of the Timestamping Authority. If null
-     *               then no timestamp is requested.
-     * @param tsaCert The certificate for the Timestamping Authority. If null
-     *               then no timestamp is requested.
-     * @param signingMechanism The signing mechanism to use.
-     * @param args The command-line arguments to jarsigner.
-     * @param zipFile The original source Zip file.
-     */
-    @SuppressWarnings("deprecation")
-    public Block generateBlock(PrivateKey privateKey,
-                               String sigalg,
-                               X509Certificate[] certChain,
-                               boolean externalSF, String tsaUrl,
-                               X509Certificate tsaCert,
-                               String tSAPolicyID,
-                               String tSADigestAlg,
-                               ContentSigner signingMechanism,
-                               String[] args, ZipFile zipFile)
-        throws NoSuchAlgorithmException, InvalidKeyException, IOException,
-            SignatureException, CertificateException
-    {
-        return new Block(this, privateKey, sigalg, certChain, externalSF,
-                tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg, signingMechanism, args, zipFile);
-    }
-
-
-    public static class Block {
-
-        private byte[] block;
-        private String blockFileName;
-
-        /*
-         * Construct a new signature block.
-         */
-        @SuppressWarnings("deprecation")
-        Block(SignatureFile sfg, PrivateKey privateKey, String sigalg,
-            X509Certificate[] certChain, boolean externalSF, String tsaUrl,
-            X509Certificate tsaCert, String tSAPolicyID, String tSADigestAlg,
-            ContentSigner signingMechanism, String[] args, ZipFile zipFile)
-            throws NoSuchAlgorithmException, InvalidKeyException, IOException,
-            SignatureException, CertificateException {
-
-            Principal issuerName = certChain[0].getIssuerDN();
-            if (!(issuerName instanceof X500Name)) {
-                // must extract the original encoded form of DN for subsequent
-                // name comparison checks (converting to a String and back to
-                // an encoded DN could cause the types of String attribute
-                // values to be changed)
-                X509CertInfo tbsCert = new
-                    X509CertInfo(certChain[0].getTBSCertificate());
-                issuerName = (Principal)
-                    tbsCert.get(X509CertInfo.ISSUER + "." +
-                                X509CertInfo.DN_NAME);
-                }
-            BigInteger serial = certChain[0].getSerialNumber();
-
-            String signatureAlgorithm;
-            String keyAlgorithm = privateKey.getAlgorithm();
-            /*
-             * If no signature algorithm was specified, we choose a
-             * default that is compatible with the private key algorithm.
-             */
-            if (sigalg == null) {
-
-                if (keyAlgorithm.equalsIgnoreCase("DSA"))
-                    signatureAlgorithm = "SHA256withDSA";
-                else if (keyAlgorithm.equalsIgnoreCase("RSA"))
-                    signatureAlgorithm = "SHA256withRSA";
-                else if (keyAlgorithm.equalsIgnoreCase("EC"))
-                    signatureAlgorithm = "SHA256withECDSA";
-                else
-                    throw new RuntimeException("private key is not a DSA or "
-                                               + "RSA key");
-            } else {
-                signatureAlgorithm = sigalg;
-            }
-
-            // check common invalid key/signature algorithm combinations
-            String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.ENGLISH);
-            if ((sigAlgUpperCase.endsWith("WITHRSA") &&
-                !keyAlgorithm.equalsIgnoreCase("RSA")) ||
-                (sigAlgUpperCase.endsWith("WITHECDSA") &&
-                !keyAlgorithm.equalsIgnoreCase("EC")) ||
-                (sigAlgUpperCase.endsWith("WITHDSA") &&
-                !keyAlgorithm.equalsIgnoreCase("DSA"))) {
-                throw new SignatureException
-                    ("private key algorithm is not compatible with signature algorithm");
-            }
-
-            blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm;
-
-            AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm);
-            AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm);
-
-            Signature sig = Signature.getInstance(signatureAlgorithm);
-            sig.initSign(privateKey);
-
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            sfg.write(baos);
-
-            byte[] content = baos.toByteArray();
-
-            sig.update(content);
-            byte[] signature = sig.sign();
-
-            // Timestamp the signature and generate the signature block file
-            if (signingMechanism == null) {
-                signingMechanism = new TimestampedSigner();
-            }
-            URI tsaUri = null;
-            try {
-                if (tsaUrl != null) {
-                    tsaUri = new URI(tsaUrl);
-                }
-            } catch (URISyntaxException e) {
-                throw new IOException(e);
-            }
-
-            // Assemble parameters for the signing mechanism
-            ContentSignerParameters params =
-                new JarSignerParameters(args, tsaUri, tsaCert, tSAPolicyID,
-                        tSADigestAlg, signature,
-                    signatureAlgorithm, certChain, content, zipFile);
-
-            // Generate the signature block
-            block = signingMechanism.generateSignedData(
-                    params, externalSF, (tsaUrl != null || tsaCert != null));
-        }
-
-        /*
-         * get block file name.
-         */
-        public String getMetaName()
-        {
-            return blockFileName;
-        }
-
-        /**
-         * Writes the block file to the specified OutputStream.
-         *
-         * @param out the output stream
-         * @exception IOException if an I/O error has occurred
-         */
-
-        public void write(OutputStream out) throws IOException
-        {
-            out.write(block);
-        }
-    }
-}
-
-
-/*
- * This object encapsulates the parameters used to perform content signing.
- */
-@SuppressWarnings("deprecation")
-class JarSignerParameters implements ContentSignerParameters {
-
-    private String[] args;
-    private URI tsa;
-    private X509Certificate tsaCertificate;
-    private byte[] signature;
-    private String signatureAlgorithm;
-    private X509Certificate[] signerCertificateChain;
-    private byte[] content;
-    private ZipFile source;
-    private String tSAPolicyID;
-    private String tSADigestAlg;
-
-    /**
-     * Create a new object.
-     */
-    JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
-        String tSAPolicyID, String tSADigestAlg,
-        byte[] signature, String signatureAlgorithm,
-        X509Certificate[] signerCertificateChain, byte[] content,
-        ZipFile source) {
-
-        if (signature == null || signatureAlgorithm == null ||
-            signerCertificateChain == null || tSADigestAlg == null) {
-            throw new NullPointerException();
-        }
-        this.args = args;
-        this.tsa = tsa;
-        this.tsaCertificate = tsaCertificate;
-        this.tSAPolicyID = tSAPolicyID;
-        this.tSADigestAlg = tSADigestAlg;
-        this.signature = signature;
-        this.signatureAlgorithm = signatureAlgorithm;
-        this.signerCertificateChain = signerCertificateChain;
-        this.content = content;
-        this.source = source;
-    }
-
-    /**
-     * Retrieves the command-line arguments.
-     *
-     * @return The command-line arguments. May be null.
-     */
-    public String[] getCommandLine() {
-        return args;
-    }
-
-    /**
-     * Retrieves the identifier for a Timestamping Authority (TSA).
-     *
-     * @return The TSA identifier. May be null.
-     */
-    public URI getTimestampingAuthority() {
-        return tsa;
-    }
-
-    /**
-     * Retrieves the certificate for a Timestamping Authority (TSA).
-     *
-     * @return The TSA certificate. May be null.
-     */
-    public X509Certificate getTimestampingAuthorityCertificate() {
-        return tsaCertificate;
-    }
-
-    public String getTSAPolicyID() {
-        return tSAPolicyID;
-    }
-
-    public String getTSADigestAlg() {
-        return tSADigestAlg;
-    }
-
-    /**
-     * Retrieves the signature.
-     *
-     * @return The non-null signature bytes.
-     */
-    public byte[] getSignature() {
-        return signature;
-    }
-
-    /**
-     * Retrieves the name of the signature algorithm.
-     *
-     * @return The non-null string name of the signature algorithm.
-     */
-    public String getSignatureAlgorithm() {
-        return signatureAlgorithm;
-    }
-
-    /**
-     * Retrieves the signer's X.509 certificate chain.
-     *
-     * @return The non-null array of X.509 public-key certificates.
-     */
-    public X509Certificate[] getSignerCertificateChain() {
-        return signerCertificateChain;
-    }
-
-    /**
-     * Retrieves the content that was signed.
-     *
-     * @return The content bytes. May be null.
-     */
-    public byte[] getContent() {
-        return content;
-    }
-
-    /**
-     * Retrieves the original source ZIP file before it was signed.
-     *
-     * @return The original ZIP file. May be null.
-     */
-    public ZipFile getSource() {
-        return source;
-    }
-}
--- a/jdk/test/ProblemList.txt	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/ProblemList.txt	Mon Nov 23 10:00:50 2015 -0800
@@ -217,6 +217,9 @@
 
 # jdk_security
 
+# 8143377
+sun/security/pkcs/pkcs8/PKCS8Test.java                          solaris-all
+
 # 7157786
 sun/security/pkcs11/ec/TestKeyFactory.java                      generic-all
 
--- a/jdk/test/com/sun/jdi/DoubleAgentTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/com/sun/jdi/DoubleAgentTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -31,7 +31,7 @@
  *
  * @library /lib/testlibrary
  * @modules java.management
- * @build jdk.testlibarary.*
+ * @build jdk.testlibrary.*
  * @build DoubleAgentTest Exit0
  * @run driver DoubleAgentTest
  */
--- a/jdk/test/com/sun/jdi/SuspendNoFlagTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/com/sun/jdi/SuspendNoFlagTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -29,7 +29,7 @@
  * @summary Test for JDWP: -agentlib:jdwp=suspend=n hanging
  * @library /lib/testlibrary
  * @modules java.management
- * @build jdk.testlibarary.*
+ * @build jdk.testlibrary.*
  * @compile -g HelloWorld.java
  * @run driver SuspendNoFlagTest
  */
--- a/jdk/test/com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java	Mon Nov 23 10:00:50 2015 -0800
@@ -41,9 +41,9 @@
  * @library /test/lib/share/classes
  * @build jdk.testlibrary.*
  * @build jdk.test.lib.hprof.*
- * @build jdk.test.lib.hprof.module.*
+ * @build jdk.test.lib.hprof.model.*
  * @build jdk.test.lib.hprof.parser.*
- * @build jdk.test.lib.hprof.utils.*
+ * @build jdk.test.lib.hprof.util.*
  * @run main DumpHeap
  */
 public class DumpHeap {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/Level/LoggerLevelTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.System.Logger.Level;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+/**
+ * @test
+ * @bug 8140364
+ * @summary Tests System.Logger.Level names and severity.
+ * @author danielfuchs
+ */
+public class LoggerLevelTest {
+    public static void main(String[] args) {
+        Set<Level> untested = EnumSet.allOf(Level.class);
+        testLevel(untested, Level.ALL, java.util.logging.Level.ALL);
+        testLevel(untested, Level.TRACE, java.util.logging.Level.FINER);
+        testLevel(untested, Level.DEBUG, java.util.logging.Level.FINE);
+        testLevel(untested, Level.INFO, java.util.logging.Level.INFO);
+        testLevel(untested, Level.WARNING, java.util.logging.Level.WARNING);
+        testLevel(untested, Level.ERROR, java.util.logging.Level.SEVERE);
+        testLevel(untested, Level.OFF, java.util.logging.Level.OFF);
+        if (!untested.isEmpty()) {
+            throw new RuntimeException("Some level values were not tested: " + untested);
+        }
+    }
+
+    private static void testLevel(Set<Level> untested, Level systemLevel, java.util.logging.Level julLevel) {
+        untested.remove(systemLevel);
+        assertEquals(systemLevel.getName(), systemLevel.name(),
+                "System.Logger.Level." + systemLevel.name() + ".getName()");
+        assertEquals(systemLevel.getSeverity(), julLevel.intValue(),
+                "System.Logger.Level." + systemLevel.name() + ".getSeverity");
+    }
+
+    private static void assertEquals(Object actual, Object expected, String what) {
+        if (!Objects.equals(actual, expected)) {
+            throw new RuntimeException("Bad value for " + what
+                    + "\n\t expected: " + expected
+                    + "\n\t   actual: " + actual);
+        } else {
+            System.out.println("Got expected value for " + what + ": " + actual);
+        }
+    }
+
+    private static void assertEquals(int actual, int expected, String what) {
+        if (!Objects.equals(actual, expected)) {
+            throw new RuntimeException("Bad value for " + what
+                    + "\n\t expected: " + toString(expected)
+                    + "\n\t   actual: " + toString(actual));
+        } else {
+            System.out.println("Got expected value for " + what + ": " + toString(actual));
+        }
+    }
+
+    private static String toString(int value) {
+        switch (value) {
+            case Integer.MAX_VALUE: return "Integer.MAX_VALUE";
+            case Integer.MIN_VALUE: return "Integer.MIN_VALUE";
+            default:
+                return Integer.toString(value);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/custom/AccessSystemLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/custom/CustomLoggerTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,728 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests loggers returned by System.getLogger with a naive implementation
+ *          of LoggerFinder, and in particular the default body of
+ *          System.Logger methods.
+ * @build CustomLoggerTest AccessSystemLogger
+ * @run driver AccessSystemLogger
+ * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOSECURITY
+ * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOPERMISSIONS
+ * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class CustomLoggerTest {
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+
+    public static class BaseLoggerFinder extends LoggerFinder {
+        final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+        public Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+        // changing this to true requires changing the logic in the
+        // test in order to load this class with a protection domain
+        // that has the CONTROL_PERMISSION (e.g. by using a custom
+        // system class loader.
+        final boolean doChecks = false;
+
+        public static final class LogEvent {
+
+            public LogEvent() {
+                this(sequencer.getAndIncrement());
+            }
+
+            LogEvent(long sequenceNumber) {
+                this.sequenceNumber = sequenceNumber;
+            }
+
+            long sequenceNumber;
+            boolean isLoggable;
+            String loggerName;
+            Level level;
+            ResourceBundle bundle;
+            Throwable thrown;
+            Object[] args;
+            Supplier<String> supplier;
+            String msg;
+
+            Object[] toArray() {
+                return new Object[] {
+                    sequenceNumber,
+                    isLoggable,
+                    loggerName,
+                    level,
+                    bundle,
+                    thrown,
+                    args,
+                    supplier,
+                    msg,
+                };
+            }
+
+            @Override
+            public String toString() {
+                return Arrays.deepToString(toArray());
+            }
+
+
+
+            @Override
+            public boolean equals(Object obj) {
+                return obj instanceof LogEvent
+                        && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(toArray());
+            }
+
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = null;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Object... params) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = null;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(long sequenceNumber,
+                    boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Supplier<String> supplier,
+                    Throwable thrown, Object... params) {
+                LogEvent evt = new LogEvent(sequenceNumber);
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = supplier;
+                evt.msg = key;
+                evt.isLoggable = isLoggable;
+                return evt;
+            }
+
+        }
+
+        public class LoggerImpl implements Logger {
+            private final String name;
+            private Level level = Level.INFO;
+
+            public LoggerImpl(String name) {
+                this.name = name;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Level level) {
+                return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown));
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+                log(LogEvent.of(isLoggable(level), name, level, bundle, format, params));
+            }
+
+            void log(LogEvent event) {
+                eventQueue.add(event);
+            }
+        }
+
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            // We should check the permission to obey the API contract, but
+            // what happens if we don't?
+            // This is the main difference compared with what we test in
+            // java/lang/System/LoggerFinder/BaseLoggerFinderTest
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null && doChecks) {
+                sm.checkPermission(SimplePolicy.LOGGERFINDER_PERMISSION);
+            }
+
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            }
+        }
+    }
+
+    static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        // 1. Obtain destination loggers directly from the LoggerFinder
+        //   - LoggerFinder.getLogger("foo", type)
+        BaseLoggerFinder provider =
+                BaseLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+        BaseLoggerFinder.LoggerImpl appSink =
+                BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", CustomLoggerTest.class));
+        BaseLoggerFinder.LoggerImpl sysSink =
+                BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    test(provider, true, appSink, sysSink);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    test(provider, false, appSink, sysSink);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(provider, true, appSink, sysSink);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(BaseLoggerFinder provider, boolean hasRequiredPermissions,
+            BaseLoggerFinder.LoggerImpl appSink, BaseLoggerFinder.LoggerImpl sysSink) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        // 1. Test loggers returned by:
+        //   - System.getLogger("foo")
+        //   - and AccessSystemLogger.getLogger("foo")
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");");
+
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = accessSystemLogger.getLogger("foo");
+            loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger1)) {
+            throw new RuntimeException("app logger in system map");
+        }
+        if (provider.user.contains(sysLogger1)) {
+            throw new RuntimeException("sys logger in appplication map");
+        }
+        if (provider.system.contains(sysLogger1)) {
+            // sysLogger should be a a LazyLoggerWrapper
+            throw new RuntimeException("sys logger is in system map (should be wrapped)");
+        }
+
+
+        // 2. Test loggers returned by:
+        //   - System.getLogger(\"foo\", loggerBundle)
+        //   - and AccessSystemLogger.getLogger(\"foo\", loggerBundle)
+        Logger appLogger2 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle);
+            loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (appLogger2 == appSink) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (sysLogger2 == sysSink) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger in system map");
+        }
+        if (provider.user.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger  in appplication map");
+        }
+        if (provider.user.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger in appplication map");
+        }
+        if (provider.system.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger not in system map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying BaseLoggerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(BaseLoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            BaseLoggerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger));
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooMsg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            format, null, (Throwable)null, new Object[] {arg1, arg2});
+                logger.log(messageLevel, format, arg1, arg2);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            format, null, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                BaseLoggerFinder.LogEvent expected =
+                        BaseLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                BaseLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl) {
+            this.allowControl = allowControl;
+            permissions = new Permissions();
+
+            // these are used for configuring the test itself...
+            allPermissions = new Permissions();
+            allPermissions.add(LOGGERFINDER_PERMISSION);
+
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            if (allowControl.get().get()) return allPermissions.implies(permission);
+            return permissions.implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(allowControl.get().get()
+                    ? allPermissions : permissions).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(allowControl.get().get()
+                    ? allPermissions : permissions).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+CustomLoggerTest$BaseLoggerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/default/AccessSystemLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+import java.util.logging.LogManager;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    public java.util.logging.Logger demandSystemLogger(String name) {
+        return java.util.logging.Logger.getLogger(name);
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,726 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests default loggers returned by System.getLogger, and in
+ *          particular the implementation of the the System.Logger method
+ *          performed by the default binding.
+ *
+ * @build DefaultLoggerTest AccessSystemLogger
+ * @run driver AccessSystemLogger
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOSECURITY
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOPERMISSIONS
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class DefaultLoggerTest {
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        java.util.logging.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                isLoggable,
+                loggerName,
+                level,
+                bundle,
+                thrown,
+                args,
+                msg,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    DefaultLoggerTest.class.getName(),
+                    "testLogger", level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+    }
+
+    static java.util.logging.Level mapToJul(Level level) {
+        switch (level) {
+            case ALL: return java.util.logging.Level.ALL;
+            case TRACE: return java.util.logging.Level.FINER;
+            case DEBUG: return java.util.logging.Level.FINE;
+            case INFO: return java.util.logging.Level.INFO;
+            case WARNING: return java.util.logging.Level.WARNING;
+            case ERROR: return java.util.logging.Level.SEVERE;
+            case OFF: return java.util.logging.Level.OFF;
+        }
+        throw new InternalError("No such level: " + level);
+    }
+
+    static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) {
+        boolean before = allowAll.get().get();
+        try {
+            allowAll.get().set(true);
+            sink.setLevel(loggerLevel);
+        } finally {
+            allowAll.get().set(before);
+        }
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    record.getLevel(),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        // 1. Obtain destination loggers directly from the LoggerFinder
+        //   - LoggerFinder.getLogger("foo", type)
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    test(true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    test(false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        // 1. Test loggers returned by:
+        //   - System.getLogger("foo")
+        //   - and AccessSystemLogger.getLogger("foo")
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = accessSystemLogger.getLogger("foo");
+            loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");");
+
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        // 2. Test loggers returned by:
+        //   - System.getLogger(\"foo\", loggerBundle)
+        //   - and AccessSystemLogger.getLogger(\"foo\", loggerBundle)
+        Logger appLogger2 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle);
+            loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            throw new RuntimeException("unexpected exception: " + acx, acx);
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        final java.util.logging.Logger appSink;
+        final java.util.logging.Logger sysSink;
+        final java.util.logging.Handler appHandler;
+        final java.util.logging.Handler sysHandler;
+        final  LoggerFinder provider;
+        allowAll.get().set(true);
+        try {
+            appSink = java.util.logging.Logger.getLogger("foo");
+            sysSink = accessSystemLogger.demandSystemLogger("foo");
+            appSink.addHandler(appHandler = new MyHandler());
+            sysSink.addHandler(sysHandler = new MyHandler());
+            appSink.setUseParentHandlers(false);
+            sysSink.setUseParentHandlers(false);
+            provider = LoggerFinder.getLoggerFinder();
+        } finally {
+            allowAll.get().set(false);
+        }
+        try {
+            testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
+            testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
+            testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
+            testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
+        } finally {
+            allowAll.get().set(true);
+            try {
+                appSink.removeHandler(appHandler);
+                sysSink.removeHandler(sysHandler);
+                sysSink.setLevel(null);
+                appSink.setLevel(null);
+            } finally {
+                allowAll.get().set(false);
+            }
+        }
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying BaseLoggerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            java.util.logging.Logger sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger));
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, mapToJul(messageLevel), (ResourceBundle)null,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual = eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), loggerBundle,
+                            msg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, mapToJul(messageLevel), (ResourceBundle)null,
+                            fooSupplier.get(),
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), loggerBundle,
+                            format, (Throwable)null, new Object[] {arg1, arg2});
+                logger.log(messageLevel, format, arg1, arg2);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), loggerBundle,
+                            msg, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, mapToJul(messageLevel), (ResourceBundle)null,
+                            fooSupplier.get(),
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), bundle,
+                            format, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            setLevel(sink, mapToJul(loggerLevel));
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, mapToJul(messageLevel), bundle,
+                            msg, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final Permissions controlPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+
+            // these are used for configuring the test itself...
+            controlPermissions = new Permissions();
+            controlPermissions.add(LOGGERFINDER_PERMISSION);
+            allPermissions = new Permissions();
+            allPermissions.add(new java.security.AllPermission());
+
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            if (allowAll.get().get()) return allPermissions.implies(permission);
+            if (allowControl.get().get()) return controlPermissions.implies(permission);
+            return permissions.implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(allowAll.get().get()
+                    ? allPermissions : allowControl.get().get()
+                    ? controlPermissions : permissions).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(allowAll.get().get()
+                    ? allPermissions : allowControl.get().get()
+                    ? controlPermissions : permissions).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,592 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.ResourceBundle;
+import java.util.function.Consumer;
+import java.lang.System.Logger.Level;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.function.Supplier;
+
+/**
+ * @test
+ * @bug 8140364
+ * @summary Tests the default body of the System.Logger interface.
+ * @author danielfuchs
+ */
+public class LoggerInterfaceTest {
+
+    public static class LoggerImpl implements System.Logger {
+
+        public static class LogEvent implements Cloneable {
+            Level level;
+            ResourceBundle bundle;
+            String msg;
+            Throwable thrown;
+            Object[] params;
+            StackTraceElement[] callStack;
+
+            @Override
+            protected LogEvent clone() {
+                try {
+                    return (LogEvent)super.clone();
+                } catch (CloneNotSupportedException x) {
+                    throw new RuntimeException(x);
+                }
+            }
+
+
+        }
+
+        public static class LogEventBuilder {
+            private LogEvent event = new LogEvent();
+            public LogEventBuilder level(Level level) {
+                event.level = level;
+                return this;
+            }
+            public LogEventBuilder stack(StackTraceElement... stack) {
+                event.callStack = stack;
+                return this;
+            }
+            public LogEventBuilder bundle(ResourceBundle bundle) {
+                event.bundle = bundle;
+                return this;
+            }
+            public LogEventBuilder msg(String msg) {
+                event.msg = msg;
+                return this;
+            }
+            public LogEventBuilder thrown(Throwable thrown) {
+                event.thrown = thrown;
+                return this;
+            }
+            public LogEventBuilder params(Object... params) {
+                event.params = params;
+                return this;
+            }
+            public LogEvent build() {
+                return event.clone();
+            }
+
+            public LogEventBuilder clear() {
+                event = new LogEvent();
+                return this;
+            }
+
+        }
+
+        Level level = Level.WARNING;
+        Consumer<LogEvent> consumer;
+        final LogEventBuilder builder = new LogEventBuilder();
+
+        @Override
+        public String getName() {
+            return "noname";
+        }
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return level.getSeverity() >= this.level.getSeverity();
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+            builder.clear().level(level).bundle(bundle).msg(msg).thrown(thrown)
+                    .stack(new Exception().getStackTrace());
+            consumer.accept(builder.build());
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+            builder.clear().level(level).bundle(bundle).msg(format).params(params)
+                    .stack(new Exception().getStackTrace());
+            consumer.accept(builder.build());
+        }
+
+    }
+
+    static class Throwing {
+        @Override
+        public String toString() {
+            throw new RuntimeException("should not have been called");
+        }
+    }
+    static class NotTrowing {
+        private final String toString;
+        private int count = 0;
+        public NotTrowing(String toString) {
+            this.toString = toString;
+        }
+
+        @Override
+        public String toString() {
+            return toString + "[" + (++count) + "]";
+        }
+    }
+
+    public static void main(String[] args) {
+        final LoggerImpl loggerImpl = new LoggerImpl();
+        final System.Logger logger = loggerImpl;
+        final Queue<LoggerImpl.LogEvent> events = new LinkedList<>();
+        loggerImpl.consumer = (x) -> events.add(x);
+
+        System.out.println("\nlogger.isLoggable(Level)");
+        assertTrue(logger.isLoggable(Level.WARNING), "logger.isLoggable(Level.WARNING)","  ");
+        assertFalse(logger.isLoggable(Level.INFO), "logger.isLoggable(Level.INFO)", "  ");
+
+
+        System.out.println("\nlogger.log(Level, Object)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null}, {"baz"}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing();
+                String par1 = msg == null ? "(Object)null"
+                        : logged ? "new NotTrowing(\""+ msg+"\")" : "new Throwing()";
+                System.out.println("  logger.log(" + l + ", " +  par1 + ")");
+                try {
+                    logger.log(l, obj);
+                    if (obj == null) {
+                        throw new RuntimeException("Expected NullPointerException not thrown for"
+                                  + " logger.log(" + l + ", " +  par1 + ")");
+                    }
+                } catch (NullPointerException x) {
+                    if (obj == null) {
+                        System.out.println("    Got expected exception: " + x);
+                        continue;
+                    } else {
+                        throw x;
+                    }
+                }
+                LoggerImpl.LogEvent e = events.poll();
+                if (logged) {
+                    assertNonNull(e, "e", "    ");
+                    assertEquals(l, e.level, "e.level", "    ");
+                    assertToString(e.msg, msg, 1, "e.msg", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.params, null, "e.params", "    ");
+                    assertEquals(e.thrown, null, "e.thrown", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.callStack[0].getMethodName(), "log",
+                                 "e.callStack[0].getMethodName()", "    ");
+                    assertEquals(e.callStack[0].getClassName(),
+                                logger.getClass().getName(),
+                                "e.callStack[0].getClassName() ", "    ");
+                    assertEquals(e.callStack[1].getMethodName(), "log",
+                                 "e.callStack[1].getMethodName()", "    ");
+                    assertEquals(e.callStack[1].getClassName(),
+                                 System.Logger.class.getName(),
+                                 "e.callStack[1].getClassName() ", "    ");
+                    assertEquals(e.callStack[2].getMethodName(), "main",
+                                 "e.callStack[2].getMethodName()", "    ");
+                } else {
+                    assertEquals(e, null, "e", "    ");
+                }
+            }
+        }
+        System.out.println("  logger.log(" + null + ", " +
+                "new NotThrowing(\"foobar\")" + ")");
+        try {
+            logger.log(null, new NotTrowing("foobar"));
+            throw new RuntimeException("Expected NullPointerException not thrown for"
+                                      + " logger.log(" + null + ", "
+                                      + "new NotThrowing(\"foobar\")" + ")");
+        } catch (NullPointerException x) {
+            System.out.println("    Got expected exception: " + x);
+        }
+
+
+        System.out.println("\nlogger.log(Level, String)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            String par = "bar";
+            System.out.println("  logger.log(" + l + ", \"" +  par +"\");");
+            logger.log(l, par);
+            LoggerImpl.LogEvent e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(e.level, l, "e.level", "    ");
+            assertEquals(e.msg, "bar", "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.params, null, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            System.out.println("  logger.log(" + l + ", (String)null);");
+            logger.log(l, (String)null);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(e.level, l, "e.level", "    ");
+            assertEquals(e.msg, null, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.params, null, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+        }
+
+        System.out.println("\nlogger.log(Level, Supplier<String>)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null}, {"baz"}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing();
+                final Supplier<String> s = msg == null ? null : () -> obj.toString();
+                String par1 = msg == null ? "(Supplier<String>)null"
+                        : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()";
+                System.out.println("  logger.log(" + l + ", " +  par1 + ")");
+                try {
+                    logger.log(l, s);
+                    if (s == null) {
+                        throw new RuntimeException("Expected NullPointerException not thrown for"
+                                  + " logger.log(" + l + ", " +  par1 + ")");
+                    }
+                } catch (NullPointerException x) {
+                    if (s == null) {
+                        System.out.println("    Got expected exception: " + x);
+                        continue;
+                    } else {
+                        throw x;
+                    }
+                }
+                LoggerImpl.LogEvent e = events.poll();
+                if (logged) {
+                    assertNonNull(e, "e", "    ");
+                    assertEquals(l, e.level, "e.level", "    ");
+                    assertToString(e.msg, msg, 1, "e.msg", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.params, null, "e.params", "    ");
+                    assertEquals(e.thrown, null, "e.thrown", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.callStack[0].getMethodName(), "log",
+                                 "e.callStack[0].getMethodName()", "    ");
+                    assertEquals(e.callStack[0].getClassName(),
+                                 logger.getClass().getName(),
+                                 "e.callStack[0].getClassName() ", "    ");
+                    assertEquals(e.callStack[1].getMethodName(), "log",
+                                 "e.callStack[1].getMethodName()", "    ");
+                    assertEquals(e.callStack[1].getClassName(),
+                                 System.Logger.class.getName(),
+                                 "e.callStack[1].getClassName() ", "    ");
+                    assertEquals(e.callStack[2].getMethodName(), "main",
+                                 "e.callStack[2].getMethodName()", "    ");
+                } else {
+                    assertEquals(e, null, "e", "    ");
+                }
+            }
+        }
+        System.out.println("  logger.log(" + null + ", " + "() -> \"biz\"" + ")");
+        try {
+            logger.log(null, () -> "biz");
+            throw new RuntimeException("Expected NullPointerException not thrown for"
+                                      + " logger.log(" + null + ", "
+                                      + "() -> \"biz\"" + ")");
+        } catch (NullPointerException x) {
+            System.out.println("    Got expected exception: " + x);
+        }
+
+        System.out.println("\nlogger.log(Level, String, Object...)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            String par = "bam";
+            Object[] params = null;
+            System.out.println("  logger.log(" + l + ", \"" +  par +"\", null);");
+            logger.log(l, par, params);
+            LoggerImpl.LogEvent e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, "bam", "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.params, null, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                        "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            params = new Object[] {new NotTrowing("one")};
+            par = "bam {0}";
+            System.out.println("  logger.log(" + l + ", \"" +  par
+                               + "\", new NotTrowing(\"one\"));");
+            logger.log(l, par, params[0]);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, par, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertArrayEquals(e.params, params, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            params = new Object[] {new NotTrowing("fisrt"), new NotTrowing("second")};
+            par = "bam {0} {1}";
+            System.out.println("  logger.log(" + l + ", \"" +  par
+                              + "\", new NotTrowing(\"fisrt\"),"
+                              + " new NotTrowing(\"second\"));");
+            logger.log(l, par, params[0], params[1]);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, par, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertArrayEquals(e.params, params, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(),
+                         logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                         "e.callStack[2].getMethodName()", "    ");
+
+            params = new Object[] {new NotTrowing("third"), new NotTrowing("fourth")};
+            par = "bam {2}";
+            System.out.println("  logger.log(" + l + ", \"" +  par
+                              + "\", new Object[] {new NotTrowing(\"third\"),"
+                              + " new NotTrowing(\"fourth\")});");
+            logger.log(l, par, params);
+            e = events.poll();
+            assertNonNull(e, "e", "    ");
+            assertEquals(l, e.level, "e.level", "    ");
+            assertEquals(e.msg, par, "e.msg", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertArrayEquals(e.params, params, "e.params", "    ");
+            assertEquals(e.thrown, null, "e.thrown", "    ");
+            assertEquals(e.bundle, null, "e.bundle", "    ");
+            assertEquals(e.callStack[0].getMethodName(), "log",
+                         "e.callStack[0].getMethodName()", "    ");
+            assertEquals(e.callStack[0].getClassName(), logger.getClass().getName(),
+                         "e.callStack[0].getClassName() ", "    ");
+            assertEquals(e.callStack[1].getMethodName(), "log",
+                         "e.callStack[1].getMethodName()", "    ");
+            assertEquals(e.callStack[1].getClassName(),
+                         System.Logger.class.getName(),
+                         "e.callStack[1].getClassName() ", "    ");
+            assertEquals(e.callStack[2].getMethodName(), "main",
+                        "e.callStack[2].getMethodName()", "    ");
+        }
+
+        System.out.println("\nlogger.log(Level, String, Throwable)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                Throwable thrown = (Throwable)p[1];
+                String par1 = msg == null ? "(String)null" : "\"" + msg + "\"";
+                String par2 = thrown == null ? "(Throwable)null" : "new Throwable()";
+                System.out.println("  logger.log(" + l + ", " +  par1 +", " + par2 + ")");
+                logger.log(l, msg, thrown);
+                LoggerImpl.LogEvent e = events.poll();
+                assertNonNull(e, "e", "    ");
+                assertEquals(e.level, l, "e.level", "    ");
+                assertEquals(e.msg, msg, "e.msg", "    ");
+                assertEquals(e.bundle, null, "e.bundle", "    ");
+                assertEquals(e.params, null, "e.params", "    ");
+                assertEquals(e.thrown, thrown, "e.thrown", "    ");
+                assertEquals(e.bundle, null, "e.bundle", "    ");
+                assertEquals(e.callStack[0].getMethodName(),
+                             "log", "e.callStack[0].getMethodName()", "    ");
+                assertEquals(e.callStack[0].getClassName(),
+                            logger.getClass().getName(),
+                            "e.callStack[0].getClassName() ", "    ");
+                assertEquals(e.callStack[1].getMethodName(), "log",
+                             "e.callStack[1].getMethodName()", "    ");
+                assertEquals(e.callStack[1].getClassName(),
+                            System.Logger.class.getName(),
+                            "e.callStack[1].getClassName() ", "    ");
+                assertEquals(e.callStack[2].getMethodName(), "main",
+                             "e.callStack[2].getMethodName()", "    ");
+            }
+        }
+
+        System.out.println("\nlogger.log(Level, Supplier<String>, Throwable)");
+        for (Level l : Level.values()) {
+            boolean logged = l.compareTo(Level.WARNING) >= 0;
+            Object[][] cases = new Object[][] {
+                {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()}
+            };
+            for (Object[] p : cases) {
+                String msg = (String)p[0];
+                Throwable thrown = (Throwable)p[1];
+                final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing();
+                final Supplier<String> s = msg == null ? null : () -> obj.toString();
+                String par1 = msg == null ? "(Supplier<String>)null"
+                        : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()";
+                String par2 = thrown == null ? "(Throwable)null" : "new Throwable()";
+                System.out.println("  logger.log(" + l + ", " +  par1 +", " + par2 + ")");
+                try {
+                    logger.log(l, s, thrown);
+                    if (s== null) {
+                        throw new RuntimeException("Expected NullPointerException not thrown for"
+                                  + " logger.log(" + l + ", " +  par1 +", " + par2 + ")");
+                    }
+                } catch (NullPointerException x) {
+                    if (s == null) {
+                        System.out.println("    Got expected exception: " + x);
+                        continue;
+                    } else {
+                        throw x;
+                    }
+                }
+                LoggerImpl.LogEvent e = events.poll();
+                if (logged) {
+                    assertNonNull(e, "e", "    ");
+                    assertEquals(l, e.level, "e.level", "    ");
+                    assertToString(e.msg, msg, 1, "e.msg", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.params, null, "e.params", "    ");
+                    assertEquals(e.thrown, thrown, "e.thrown", "    ");
+                    assertEquals(e.bundle, null, "e.bundle", "    ");
+                    assertEquals(e.callStack[0].getMethodName(), "log",
+                                 "e.callStack[0].getMethodName()", "    ");
+                    assertEquals(e.callStack[0].getClassName(),
+                                 logger.getClass().getName(),
+                                 "e.callStack[0].getClassName() ", "    ");
+                    assertEquals(e.callStack[1].getMethodName(), "log",
+                                 "e.callStack[1].getMethodName()", "    ");
+                    assertEquals(e.callStack[1].getClassName(),
+                                 System.Logger.class.getName(),
+                                 "e.callStack[1].getClassName() ", "    ");
+                    assertEquals(e.callStack[2].getMethodName(), "main",
+                                 "e.callStack[2].getMethodName()", "    ");
+                } else {
+                    assertEquals(e, null, "e", "    ");
+                }
+            }
+        }
+        System.out.println("  logger.log(" + null + ", " + "() -> \"biz\""
+                           + ", " + "new Throwable()" + ")");
+        try {
+            logger.log(null, () -> "biz", new Throwable());
+            throw new RuntimeException("Expected NullPointerException not thrown for"
+                                      + " logger.log(" + null + ", "
+                                      + "() -> \"biz\"" + ", "
+                                      + "new Throwable()" + ")");
+        } catch (NullPointerException x) {
+            System.out.println("    Got expected exception: " + x);
+        }
+
+        System.out.println("Checking that we have no spurious events in the queue");
+        assertEquals(events.poll(), null, "events.poll()", "  ");
+    }
+
+    static void assertTrue(boolean test, String what, String prefix) {
+        if (!test) {
+            throw new RuntimeException("Expected true for " + what);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + test);
+    }
+    static void assertFalse(boolean test, String what, String prefix) {
+        if (test) {
+            throw new RuntimeException("Expected false for " + what);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + test);
+    }
+    static void assertToString(String actual, String expected, int count, String what, String prefix) {
+        assertEquals(actual, expected + "["+count+"]", what, prefix);
+    }
+    static void assertEquals(Object actual, Object expected, String what, String prefix) {
+        if (!Objects.equals(actual, expected)) {
+            throw new RuntimeException("Bad " + what + ":"
+                    + "\n\t expected: " + expected
+                    + "\n\t   actual: " + actual);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + actual);
+    }
+    static void assertArrayEquals(Object[] actual, Object[] expected, String what, String prefix) {
+        if (!Objects.deepEquals(actual, expected)) {
+            throw new RuntimeException("Bad " + what + ":"
+                    + "\n\t expected: " + expected == null ? "null" : Arrays.deepToString(expected)
+                    + "\n\t   actual: " + actual == null ? "null" : Arrays.deepToString(actual));
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + Arrays.deepToString(actual));
+    }
+    static void assertNonNull(Object actual, String what, String prefix) {
+        if (Objects.equals(actual, null)) {
+            throw new RuntimeException("Bad " + what + ":"
+                    + "\n\t expected: non null"
+                    + "\n\t   actual: " + actual);
+        }
+        System.out.println(prefix + "Got expected " + what + ": " + "non null");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+
+public  class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    @Override
+    public Logger getLogger(String name, Class<?> caller) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(LOGGERFINDER_PERMISSION);
+        }
+        PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+        ClassLoader callerLoader = AccessController.doPrivileged(pa);
+        if (callerLoader == null) {
+            return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+        } else {
+            return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,694 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.stream.Stream;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests a naive implementation of LoggerFinder, and in particular
+ *   the default body of System.Logger methods.
+ * @build AccessSystemLogger BaseLoggerFinderTest CustomSystemClassLoader BaseLoggerFinder TestLoggerFinder
+ * @run  driver AccessSystemLogger
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class BaseLoggerFinderTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+    static final Class<?> providerClass;
+    static {
+        try {
+            providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerFinder");
+        } catch (ClassNotFoundException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                //"NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        System.out.println("Using provider class: " + providerClass + "[" + providerClass.getClassLoader() + "]");
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            TestLoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases.");
+    }
+
+    public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        // 1. Test loggers returned by LoggerFinder, both for system callers
+        //    and not system callers.
+        TestLoggerFinder.LoggerImpl appLogger1 = null;
+        try {
+            appLogger1 =
+                TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class));
+            loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appLogger1 =
+                    TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class));
+                    loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+        TestLoggerFinder.LoggerImpl sysLogger1 = null;
+        try {
+            sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+            loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+                loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger1)) {
+            throw new RuntimeException("app logger in system map");
+        }
+        if (!provider.user.contains(appLogger1)) {
+            throw new RuntimeException("app logger not in appplication map");
+        }
+        if (provider.user.contains(sysLogger1)) {
+            throw new RuntimeException("sys logger in appplication map");
+        }
+        if (!provider.system.contains(sysLogger1)) {
+            throw new RuntimeException("sys logger not in system map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, appLogger1, appLogger1);
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysLogger1);
+
+        // 2. Test localized loggers returned LoggerFinder, both for system
+        //   callers and non system callers
+        Logger appLogger2 = null;
+        try {
+            appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class);
+            loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class);
+                loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+            loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for localized system logger: " + acx);
+            final boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+                loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class))");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (appLogger2 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (sysLogger2 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger in system map");
+        }
+        if (provider.user.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger  in appplication map");
+        }
+        if (provider.user.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger in appplication map");
+        }
+        if (provider.system.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger not in system map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appLogger1);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysLogger1);
+
+        // 3 Test loggers returned by:
+        //   3.1: System.getLogger("foo")
+        Logger appLogger3 = System.getLogger("foo");
+        loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")");
+        testLogger(provider, loggerDescMap, "foo", null, appLogger3, appLogger1);
+
+        //   3.2: System.getLogger("foo")
+        //        Emulate what System.getLogger() does when the caller is a
+        //        platform classes
+        Logger sysLogger3 = accessSystemLogger.getLogger("foo");
+        loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")");
+
+        if (appLogger3 == sysLogger3) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysLogger1);
+
+        // 4. Test loggers returned by:
+        //    4.1 System.getLogger("foo", loggerBundle)
+        Logger appLogger4 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)");
+        if (appLogger4 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appLogger1);
+
+        //   4.2: System.getLogger("foo", loggerBundle)
+        //        Emulate what System.getLogger() does when the caller is a
+        //        platform classes
+        Logger sysLogger4 = accessSystemLogger.getLogger("foo", loggerBundle);
+        loggerDescMap.put(sysLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+        if (appLogger4 == sysLogger4) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysLogger1);
+
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying TestProvider.LoggerImpl
+    // logger.
+    private static void testLogger(TestLoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            TestLoggerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]");
+        AtomicLong sequencer = TestLoggerFinder.sequencer;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooMsg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            format, null, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, format, foo, msg);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, loggerBundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0,
+                            name, messageLevel, (ResourceBundle)null,
+                            fooSupplier.get(), null,
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (provider.eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            format, null, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF,
+                            name, messageLevel, bundle,
+                            msg, null, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+                if (!expected.equals(actual)) {
+                    throw new RuntimeException("mismatch for " + desc
+                            + "\n\texpected=" + expected
+                            + "\n\t  actual=" + actual);
+                } else {
+                    verbose("Got expected results for "
+                            + desc + "\n\t" + expected);
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+
+        final Permissions permissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAccess) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            permissions = new Permissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    Class<?> finderClass = null;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (finderClass != null) return finderClass;
+
+            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    finderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    return finderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (name.equals("BaseLoggerFinder")) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (name.equals("BaseLoggerFinder")) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+BaseLoggerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+
+/**
+ * What our test provider needs to implement.
+ * @author danielfuchs
+ */
+public interface TestLoggerFinder {
+    public final static AtomicLong sequencer = new AtomicLong();
+    public final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+    public final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+    public final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        Logger.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        Supplier<String> supplier;
+        String msg;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                isLoggable,
+                loggerName,
+                level,
+                bundle,
+                thrown,
+                args,
+                supplier,
+                msg,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+
+
+        public static LogEvent of(boolean isLoggable, String name,
+                Logger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown) {
+            LogEvent evt = new LogEvent();
+            evt.isLoggable = isLoggable;
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = null;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.supplier = null;
+            evt.msg = key;
+            return evt;
+        }
+
+        public static LogEvent of(boolean isLoggable, String name,
+                Logger.Level level, ResourceBundle bundle,
+                String key, Object... params) {
+            LogEvent evt = new LogEvent();
+            evt.isLoggable = isLoggable;
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = null;
+            evt.supplier = null;
+            evt.msg = key;
+            return evt;
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                Logger.Level level, ResourceBundle bundle,
+                String key, Supplier<String> supplier,
+                Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.supplier = supplier;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            return evt;
+        }
+
+    }
+
+    public class LoggerImpl implements Logger {
+        final String name;
+        Logger.Level level = Logger.Level.INFO;
+
+        public LoggerImpl(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean isLoggable(Logger.Level level) {
+            return this.level != Logger.Level.OFF && this.level.getSeverity() <= level.getSeverity();
+        }
+
+        @Override
+        public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) {
+            log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown));
+        }
+
+        @Override
+        public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) {
+            log(LogEvent.of(isLoggable(level), name, level, bundle, format, params));
+        }
+
+        void log(LogEvent event) {
+            eventQueue.add(event);
+        }
+    }
+
+    public Logger getLogger(String name, Class<?> caller);
+    public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class<?> caller);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    public java.util.logging.Logger demandSystemLogger(String name) {
+        return java.util.logging.Logger.getLogger(name);
+    }
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        Path thisClass = Paths.get(classes.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        Path dest = Paths.get(bootDir.toString(),
+                AccessSystemLogger.class.getSimpleName()+".class");
+        Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,887 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests the default implementation of System.Logger, when
+ *          JUL is the default backend.
+ * @build AccessSystemLogger DefaultLoggerFinderTest
+ * @run  driver AccessSystemLogger
+ * @run  main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class DefaultLoggerFinderTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        java.util.logging.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                isLoggable,
+                loggerName,
+                level,
+                bundle,
+                thrown,
+                args,
+                msg,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    DefaultLoggerFinderTest.class.getName(),
+                    "testLogger", level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+    }
+
+    static java.util.logging.Level mapToJul(Level level) {
+        switch (level) {
+            case ALL: return java.util.logging.Level.ALL;
+            case TRACE: return java.util.logging.Level.FINER;
+            case DEBUG: return java.util.logging.Level.FINE;
+            case INFO: return java.util.logging.Level.INFO;
+            case WARNING: return java.util.logging.Level.WARNING;
+            case ERROR: return java.util.logging.Level.SEVERE;
+            case OFF: return java.util.logging.Level.OFF;
+        }
+        throw new InternalError("No such level: " + level);
+    }
+
+    static final java.util.logging.Level[] julLevels = {
+        java.util.logging.Level.ALL,
+        new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {},
+        java.util.logging.Level.FINEST,
+        new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {},
+        java.util.logging.Level.FINER,
+        new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {},
+        java.util.logging.Level.FINE,
+        new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {},
+        java.util.logging.Level.CONFIG,
+        new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {},
+        java.util.logging.Level.INFO,
+        new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {},
+        java.util.logging.Level.WARNING,
+        new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {},
+        java.util.logging.Level.SEVERE,
+        new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {},
+        java.util.logging.Level.OFF,
+    };
+
+    static final Level[] mappedLevels = {
+        Level.ALL,     // ALL
+        Level.DEBUG,   // FINER_THAN_FINEST
+        Level.DEBUG,   // FINEST
+        Level.DEBUG,   // FINER_THAN_FINER
+        Level.TRACE,   // FINER
+        Level.TRACE,   // FINER_THAN_FINE
+        Level.DEBUG,   // FINE
+        Level.DEBUG,   // FINER_THAN_CONFIG
+        Level.DEBUG,   // CONFIG
+        Level.DEBUG,   // FINER_THAN_INFO
+        Level.INFO,    // INFO
+        Level.INFO,    // FINER_THAN_WARNING
+        Level.WARNING, // WARNING
+        Level.WARNING, // FINER_THAN_SEVERE
+        Level.ERROR,   // SEVERE
+        Level.ERROR,   // FATAL
+        Level.OFF,     // OFF
+    };
+
+    final static Map<java.util.logging.Level, Level> julToSpiMap;
+    static {
+        Map<java.util.logging.Level, Level> map = new HashMap<>();
+        if (mappedLevels.length != julLevels.length) {
+            throw new ExceptionInInitializerError("Array lengths differ"
+                + "\n\tjulLevels=" + Arrays.deepToString(julLevels)
+                + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels));
+        }
+        for (int i=0; i<julLevels.length; i++) {
+            map.put(julLevels[i], mappedLevels[i]);
+        }
+        julToSpiMap = Collections.unmodifiableMap(map);
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    record.getLevel(),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowAll, allowControl));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        final java.util.logging.Logger appSink = java.util.logging.Logger.getLogger("foo");
+        final java.util.logging.Logger sysSink = accessSystemLogger.demandSystemLogger("foo");
+        appSink.addHandler(new MyHandler());
+        sysSink.addHandler(new MyHandler());
+        appSink.setUseParentHandlers(VERBOSE);
+        sysSink.setUseParentHandlers(VERBOSE);
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            LoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = LoggerFinder.getLoggerFinder();
+                    test(provider, true, appSink, sysSink);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = LoggerFinder.getLoggerFinder();
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = LoggerFinder.getLoggerFinder();
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false, appSink, sysSink);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = LoggerFinder.getLoggerFinder();
+                        test(provider, true, appSink, sysSink);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(LoggerFinder provider,
+            boolean hasRequiredPermissions,
+            java.util.logging.Logger appSink,
+            java.util.logging.Logger sysSink) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+
+        Logger appLogger1 = null;
+        try {
+            appLogger1 = provider.getLogger("foo", DefaultLoggerFinderTest.class);
+            loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appLogger1 =provider.getLogger("foo", DefaultLoggerFinderTest.class);
+                loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = provider.getLogger("foo", Thread.class);
+            loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+            boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                sysLogger1 = provider.getLogger("foo", Thread.class);
+                loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+        if (appLogger1 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        Logger appLogger2 = null;
+        try {
+            appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class);
+            loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class);
+                loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+            loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for localized system logger: " + acx);
+            boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+                loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)");
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+        if (appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (appLogger2 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (sysLogger2 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+
+        testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink);
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink);
+
+
+        Logger appLogger3 = System.getLogger("foo");
+        loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")");
+
+        testLogger(provider, loggerDescMap, "foo", null, appLogger3, appSink);
+
+        Logger appLogger4 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)");
+
+        if (appLogger4 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appSink);
+
+        Logger sysLogger3 = accessSystemLogger.getLogger("foo");
+        loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")");
+
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysSink);
+
+        Logger sysLogger4 =
+                accessSystemLogger.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
+
+        if (sysLogger4 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysSink);
+
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) {
+        boolean before = allowAll.get().get();
+        try {
+            allowAll.get().set(true);
+            sink.setLevel(loggerLevel);
+        } finally {
+            allowAll.get().set(before);
+        }
+    }
+
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying Logger Impl
+    // logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            java.util.logging.Logger sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]");
+        final java.util.logging.Level OFF = java.util.logging.Level.OFF;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, (ResourceBundle)null,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, foo);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, loggerBundle,
+                            msg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, msg);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, (ResourceBundle)null,
+                            fooSupplier.get(),
+                            (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, loggerBundle,
+                            format, (Throwable)null, new Object[] {arg1, arg2});
+                logger.log(messageLevel, format, arg1, arg2);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, loggerBundle,
+                            msg, thrown, (Object[]) null);
+                logger.log(messageLevel, msg, thrown);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, (ResourceBundle)null,
+                            fooSupplier.get(),
+                            (Throwable)thrown, (Object[])null);
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, bundle,
+                            format, (Throwable)null, new Object[] {foo, msg});
+                logger.log(messageLevel, bundle, format, foo, msg);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (Level messageLevel : Level.values()) {
+                java.util.logging.Level julLevel = mapToJul(messageLevel);
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            julLevel.intValue() >= loggerLevel.intValue(),
+                            name, julLevel, bundle,
+                            msg, thrown, (Object[]) null);
+                logger.log(messageLevel, bundle, msg, thrown);
+                if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) {
+                    if (eventQueue.poll() != null) {
+                        throw new RuntimeException("unexpected event in queue for " + desc);
+                    }
+                } else {
+                    LogEvent actual =  eventQueue.poll();
+                    if (!expected.equals(actual)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected=" + expected
+                                + "\n\t  actual=" + actual);
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n\t" + expected);
+                    }
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+
+        final Permissions permissions;
+        final Permissions withControlPermissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowAll,
+                ThreadLocal<AtomicBoolean> allowControl) {
+            this.allowAll = allowAll;
+            this.allowControl = allowControl;
+            permissions = new Permissions();
+
+            withControlPermissions = new Permissions();
+            withControlPermissions.add(LOGGERFINDER_PERMISSION);
+
+            // these are used for configuring the test itself...
+            allPermissions = new Permissions();
+            allPermissions.add(new java.security.AllPermission());
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            if (allowAll.get().get()) return allPermissions.implies(permission);
+            if (allowControl.get().get()) return withControlPermissions.implies(permission);
+            return permissions.implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(
+                    allowAll.get().get() ? allPermissions :
+                    allowControl.get().get()
+                    ? withControlPermissions : permissions).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(
+                    allowAll.get().get() ? allPermissions :
+                    allowControl.get().get()
+                    ? withControlPermissions : permissions).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    static final Class<?>[] toCopy = { AccessSystemLogger.class, CustomSystemClassLoader.class };
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        for (Class<?> c : toCopy) {
+            Path thisClass = Paths.get(classes.toString(),
+                c.getSimpleName()+".class");
+            Path dest = Paths.get(bootDir.toString(),
+                c.getSimpleName()+".class");
+            Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,768 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.stream.Stream;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
+import jdk.internal.logger.DefaultLoggerFinder;
+import jdk.internal.logger.SimpleConsoleLogger;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for the base DefaultLoggerFinder.
+ *          Tests the behavior of DefaultLoggerFinder and SimpleConsoleLogger
+ *          implementation.
+ * @modules java.base/sun.util.logging
+ *          java.base/jdk.internal.logger
+ * @build AccessSystemLogger BaseDefaultLoggerFinderTest CustomSystemClassLoader
+ * @run  driver AccessSystemLogger
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class BaseDefaultLoggerFinderTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+    static final Class<?>[] providerClass;
+    static {
+        try {
+            providerClass = new Class<?>[] {
+                ClassLoader.getSystemClassLoader().loadClass("BaseDefaultLoggerFinderTest$BaseLoggerFinder"),
+            };
+        } catch (ClassNotFoundException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    /**
+     * What our test provider needs to implement.
+     */
+    public static interface TestLoggerFinder {
+        public final static AtomicBoolean fails = new AtomicBoolean();
+        public final static AtomicReference<String> conf = new AtomicReference<>("");
+        public final static AtomicLong sequencer = new AtomicLong();
+
+
+        public Logger getLogger(String name, Class<?> caller);
+        public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class<?> caller);
+        void setLevel(Logger logger, Level level, Class<?> caller);
+        void setLevel(Logger logger, PlatformLogger.Level level, Class<?> caller);
+        PlatformLogger.Bridge asPlatformLoggerBridge(Logger logger);
+    }
+
+    public static class BaseLoggerFinder extends DefaultLoggerFinder implements TestLoggerFinder {
+
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                    new RuntimePermission("loggerFinder");
+        public BaseLoggerFinder() {
+            if (fails.get()) {
+                throw new RuntimeException("Simulate exception while loading provider");
+            }
+        }
+
+        @Override
+        public void setLevel(Logger logger, Level level, Class<?> caller) {
+            PrivilegedAction<Void> pa = () -> {
+                setLevel(logger, PlatformLogger.toPlatformLevel(level), caller);
+                return null;
+            };
+            AccessController.doPrivileged(pa);
+        }
+
+        @Override
+        public void setLevel(Logger logger, PlatformLogger.Level level, Class<?> caller) {
+            PrivilegedAction<Logger> pa = () -> demandLoggerFor(logger.getName(), caller);
+            Logger impl = AccessController.doPrivileged(pa);
+            SimpleConsoleLogger.class.cast(impl)
+                    .getLoggerConfiguration()
+                    .setPlatformLevel(level);
+        }
+
+        @Override
+        public PlatformLogger.Bridge asPlatformLoggerBridge(Logger logger) {
+            PrivilegedAction<PlatformLogger.Bridge> pa = () ->
+                PlatformLogger.Bridge.convert(logger);
+            return AccessController.doPrivileged(pa);
+        }
+
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    static TestLoggerFinder getLoggerFinder(Class<?> expectedClass) {
+        LoggerFinder provider = null;
+        try {
+            TestLoggerFinder.sequencer.incrementAndGet();
+            provider = LoggerFinder.getLoggerFinder();
+        } catch(AccessControlException a) {
+            throw a;
+        }
+        ErrorStream.errorStream.store();
+        System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName());
+        expectedClass.cast(provider);
+        return TestLoggerFinder.class.cast(provider);
+    }
+
+
+    static class ErrorStream extends PrintStream {
+
+        static AtomicBoolean forward = new AtomicBoolean();
+        ByteArrayOutputStream out;
+        String saved = "";
+        public ErrorStream(ByteArrayOutputStream out) {
+            super(out);
+            this.out = out;
+        }
+
+        @Override
+        public void write(int b) {
+            super.write(b);
+            if (forward.get()) err.write(b);
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            super.write(b);
+            if (forward.get()) err.write(b);
+        }
+
+        @Override
+        public void write(byte[] buf, int off, int len) {
+            super.write(buf, off, len);
+            if (forward.get()) err.write(buf, off, len);
+        }
+
+        public String peek() {
+            flush();
+            return out.toString();
+        }
+
+        public String drain() {
+            flush();
+            String res = out.toString();
+            out.reset();
+            return res;
+        }
+
+        public void store() {
+            flush();
+            saved = out.toString();
+            out.reset();
+        }
+
+        public void restore() {
+            out.reset();
+            try {
+                out.write(saved.getBytes());
+            } catch(IOException io) {
+                throw new UncheckedIOException(io);
+            }
+        }
+
+        static final PrintStream err = System.err;
+        static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream());
+    }
+
+    private static StringBuilder appendProperty(StringBuilder b, String name) {
+        String value = System.getProperty(name);
+        if (value == null) return b;
+        return b.append(name).append("=").append(value).append('\n');
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            args = new String[] {
+                //"NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+        }
+        Locale.setDefault(Locale.ENGLISH);
+        System.setErr(ErrorStream.errorStream);
+        //System.setProperty("jdk.logger.finder.error", "ERROR");
+        //System.setProperty("jdk.logger.finder.singleton", "true");
+        //System.setProperty("test.fails", "true");
+        TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails"));
+        StringBuilder c = new StringBuilder();
+        appendProperty(c, "jdk.logger.packages");
+        appendProperty(c, "jdk.logger.finder.error");
+        appendProperty(c, "jdk.logger.finder.singleton");
+        appendProperty(c, "test.fails");
+        TestLoggerFinder.conf.set(c.toString());
+        try {
+            test(args);
+        } finally {
+            try {
+                System.setErr(ErrorStream.err);
+            } catch (Error | RuntimeException x) {
+                x.printStackTrace(ErrorStream.err);
+            }
+        }
+    }
+
+
+    public static void test(String[] args) {
+
+        final Class<?> expectedClass = jdk.internal.logger.DefaultLoggerFinder.class;
+
+        System.out.println("Declared provider class: " + providerClass[0]
+                + "[" + providerClass[0].getClassLoader() + "]");
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            TestLoggerFinder provider;
+            ErrorStream.errorStream.restore();
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    provider = getLoggerFinder(expectedClass);
+                    if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) {
+                        throw new RuntimeException("Unexpected provider: " + provider.getClass().getName());
+                    }
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    setSecurityManager();
+                    try {
+                        provider = getLoggerFinder(expectedClass);
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = getLoggerFinder(expectedClass);
+                            if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) {
+                                throw new RuntimeException("Unexpected provider: " + provider.getClass().getName());
+                            }
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = getLoggerFinder(expectedClass);
+                        if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) {
+                            throw new RuntimeException("Unexpected provider: " + provider.getClass().getName());
+                        }
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases.");
+    }
+
+    public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+        System.Logger sysLogger = accessSystemLogger.getLogger("foo");
+        loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")");
+        System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle);
+        loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)");
+        System.Logger appLogger = System.getLogger("bar");
+        loggerDescMap.put(appLogger,"System.getLogger(\"bar\")");
+        System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle);
+        loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)");
+
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger, accessSystemLogger.getClass());
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger, accessSystemLogger.getClass());
+        testLogger(provider, loggerDescMap, "foo", null, appLogger, BaseDefaultLoggerFinderTest.class);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger, BaseDefaultLoggerFinderTest.class);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying TestProvider.LoggerImpl
+    // logger.
+    private static void testLogger(TestLoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger,
+            Class<?> caller) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]");
+        AtomicLong sequencer = TestLoggerFinder.sequencer;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                ErrorStream.errorStream.drain();
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + fooMsg)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + fooMsg
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg);
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + msgText)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + msgText
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + fooSupplier.get()
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format);
+                    String text = java.text.MessageFormat.format(msgFormat, foo, msg);
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + text
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg);
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + msgText)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + msgText +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + fooSupplier.get() +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, bundle, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg);
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + text
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        for (Level loggerLevel : Level.values()) {
+            provider.setLevel(logger, loggerLevel, caller);
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, bundle, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String textMsg = bundle.getString(msg);
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    if (!logged.contains("BaseDefaultLoggerFinderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + textMsg)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] BaseDefaultLoggerFinderTest testLogger\n"
+                                + messageLevel.getName() + " " + textMsg +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+
+        final Permissions permissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAccess) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            permissions = new Permissions();
+            permissions.add(new RuntimePermission("setIO"));
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions. The CustomSystemClassLoader class must be
+ * in the BCL, otherwise when system classes - such as
+ * ZoneDateTime try to load their resource bundle a MissingResourceBundle
+ * caused by a SecurityException may be thrown, as the CustomSystemClassLoader
+ * code base will be found in the stack called by doPrivileged.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    final List<String> finderClassNames =
+            Arrays.asList("BaseDefaultLoggerFinderTest$BaseLoggerFinder");
+    final Map<String, Class<?>> finderClasses = new HashMap<>();
+    Class<?> testLoggerFinderClass;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (finderClasses.get(name) != null) return finderClasses.get(name);
+            if (testLoggerFinderClass == null) {
+                // Hack: we  load testLoggerFinderClass to get its code source.
+                //       we can't use this.getClass() since we are in the boot.
+                testLoggerFinderClass = super.loadClass("BaseDefaultLoggerFinderTest$TestLoggerFinder");
+            }
+            URL url = testLoggerFinderClass.getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    Class<?> finderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    finderClasses.put(name, finderClass);
+                    return finderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (finderClassNames.contains(name)) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (finderClassNames.contains(name)) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+BaseDefaultLoggerFinderTest$BaseLoggerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1058 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import sun.util.logging.PlatformLogger;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.stream.Stream;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for JDK internal artifacts.
+ *   Tests a naive implementation of System.Logger, and in particular
+ *   the default mapping provided by PlatformLogger.Bridge.
+ * @modules java.base/sun.util.logging java.base/jdk.internal.logger
+ * @build CustomSystemClassLoader BaseLoggerBridgeTest
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest NOSECURITY
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest NOPERMISSIONS
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class BaseLoggerBridgeTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    // whether the implementation of Logger try to do a best
+    // effort for logp... Our base logger finder stub doesn't
+    // support logp, and thus the logp() implementation comes from
+    // LoggerWrapper - which does a best effort.
+    static final boolean BEST_EFFORT_FOR_LOGP = true;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    static final Class<?> providerClass;
+    static {
+        try {
+            providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerBridgeTest$BaseLoggerFinder");
+        } catch (ClassNotFoundException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    static final sun.util.logging.PlatformLogger.Level[] julLevels = {
+        sun.util.logging.PlatformLogger.Level.ALL,
+        sun.util.logging.PlatformLogger.Level.FINEST,
+        sun.util.logging.PlatformLogger.Level.FINER,
+        sun.util.logging.PlatformLogger.Level.FINE,
+        sun.util.logging.PlatformLogger.Level.CONFIG,
+        sun.util.logging.PlatformLogger.Level.INFO,
+        sun.util.logging.PlatformLogger.Level.WARNING,
+        sun.util.logging.PlatformLogger.Level.SEVERE,
+        sun.util.logging.PlatformLogger.Level.OFF,
+    };
+
+    static final Level[] mappedLevels = {
+        Level.ALL,     // ALL
+        Level.TRACE,   // FINEST
+        Level.TRACE,   // FINER
+        Level.DEBUG,   // FINE
+        Level.DEBUG,   // CONFIG
+        Level.INFO,    // INFO
+        Level.WARNING, // WARNING
+        Level.ERROR,   // SEVERE
+        Level.OFF,     // OFF
+    };
+
+    final static Map<sun.util.logging.PlatformLogger.Level, Level> julToSpiMap;
+    static {
+        Map<sun.util.logging.PlatformLogger.Level, Level> map = new HashMap<>();
+        if (mappedLevels.length != julLevels.length) {
+            throw new ExceptionInInitializerError("Array lengths differ"
+                + "\n\tjulLevels=" + Arrays.deepToString(julLevels)
+                + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels));
+        }
+        for (int i=0; i<julLevels.length; i++) {
+            map.put(julLevels[i], mappedLevels[i]);
+        }
+        julToSpiMap = Collections.unmodifiableMap(map);
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    public static interface TestLoggerFinder {
+        final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+        public Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+        public static final class LogEvent implements Cloneable {
+
+            public LogEvent() {
+                this(sequencer.getAndIncrement());
+            }
+
+            LogEvent(long sequenceNumber) {
+                this.sequenceNumber = sequenceNumber;
+            }
+
+            boolean callSupplier = false;
+            long sequenceNumber;
+            boolean isLoggable;
+            String loggerName;
+            Level level;
+            ResourceBundle bundle;
+            Throwable thrown;
+            Object[] args;
+            Supplier<String> supplier;
+            String msg;
+
+            Object[] toArray(boolean callSupplier) {
+                return new Object[] {
+                    sequenceNumber,
+                    isLoggable,
+                    loggerName,
+                    level,
+                    bundle,
+                    thrown,
+                    args,
+                    callSupplier && supplier != null ? supplier.get() : supplier,
+                    msg,
+                };
+            }
+
+            boolean callSupplier(Object obj) {
+                return callSupplier || ((LogEvent)obj).callSupplier;
+            }
+
+            @Override
+            public String toString() {
+                return Arrays.deepToString(toArray(false));
+            }
+
+
+
+            @Override
+            public boolean equals(Object obj) {
+                return obj instanceof LogEvent
+                        && Objects.deepEquals(toArray(callSupplier(obj)), ((LogEvent)obj).toArray(callSupplier(obj)));
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(toArray(true));
+            }
+
+            public LogEvent cloneWith(long sequenceNumber)
+                    throws CloneNotSupportedException {
+                LogEvent cloned = (LogEvent)super.clone();
+                cloned.sequenceNumber = sequenceNumber;
+                return cloned;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = null;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, Throwable thrown, Supplier<String> supplier) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = null;
+                evt.bundle = null;
+                evt.thrown = thrown;
+                evt.supplier = supplier;
+                evt.msg = null;
+                return evt;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Object... params) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = null;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(long sequenceNumber,
+                    boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Supplier<String> supplier,
+                    Throwable thrown, Object... params) {
+                LogEvent evt = new LogEvent(sequenceNumber);
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = supplier;
+                evt.msg = key;
+                evt.isLoggable = isLoggable;
+                return evt;
+            }
+
+            public static LogEvent ofp(boolean callSupplier, LogEvent evt) {
+                evt.callSupplier = callSupplier;
+                return evt;
+            }
+        }
+
+        public class LoggerImpl implements Logger {
+            private final String name;
+            private Level level = Level.INFO;
+
+            public LoggerImpl(String name) {
+                this.name = name;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Level level) {
+                return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown));
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+                log(LogEvent.of(isLoggable(level), name, level, bundle, format, params));
+            }
+
+            void log(LogEvent event) {
+                eventQueue.add(event);
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name, level, null, msgSupplier));
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name, level, thrown, msgSupplier));
+            }
+
+
+
+        }
+
+        public Logger getLogger(String name, Class<?> caller);
+        public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class<?> caller);
+    }
+
+    public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder {
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            }
+        }
+    }
+
+    static PlatformLogger.Bridge convert(Logger logger) {
+        boolean old = allowAll.get().get();
+        allowAccess.get().set(true);
+        try {
+            return PlatformLogger.Bridge.convert(logger);
+        } finally {
+            allowAccess.get().set(old);
+        }
+    }
+
+    static Logger getLogger(String name, Class<?> caller) {
+        boolean old = allowAll.get().get();
+        allowAccess.get().set(true);
+        try {
+            return jdk.internal.logger.LazyLoggers.getLogger(name, caller);
+        } finally {
+            allowAccess.get().set(old);
+        }
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            // Ugly test hack: preload the resources needed by String.format
+            //   We need to do that before setting the security manager
+            //   because our implementation of CustomSystemClassLoader
+            //   doesn't have the required permission.
+            System.out.println(String.format("debug: %s", "Setting security manager"));
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            TestLoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Object, String> loggerDescMap = new HashMap<>();
+
+
+        TestLoggerFinder.LoggerImpl appSink = null;
+        try {
+            appSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerBridgeTest.class));
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for logger: " + acx);
+            boolean old = allowControl.get().get();
+            allowControl.get().set(true);
+            try {
+                appSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerBridgeTest.class));
+            } finally {
+                allowControl.get().set(old);
+            }
+        }
+
+
+        TestLoggerFinder.LoggerImpl sysSink = null;
+        try {
+            sysSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+        }
+        if (hasRequiredPermissions && appSink == sysSink) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appSink)) {
+            throw new RuntimeException("app logger in system map");
+        }
+        if (!provider.user.contains(appSink)) {
+            throw new RuntimeException("app logger not in appplication map");
+        }
+        if (hasRequiredPermissions && provider.user.contains(sysSink)) {
+            throw new RuntimeException("sys logger in appplication map");
+        }
+        if (hasRequiredPermissions && !provider.system.contains(sysSink)) {
+            throw new RuntimeException("sys logger not in system map");
+        }
+
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "System.getLogger(\"foo\")");
+        PlatformLogger.Bridge bridge = convert(appLogger1);
+        loggerDescMap.putIfAbsent(bridge, "PlatformLogger.Bridge.convert(System.getLogger(\"foo\"))");
+        testLogger(provider, loggerDescMap, "foo", null, bridge, appSink);
+
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = getLogger("foo", Thread.class);
+            loggerDescMap.put(sysLogger1,
+                    "jdk.internal.logger.LazyLoggers.getLogger(\"foo\", Thread.class)");
+
+            if (!hasRequiredPermissions) {
+                // check that the provider would have thrown an exception
+                provider.getLogger("foo", Thread.class);
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+        }
+
+        if (hasRequiredPermissions) {
+            // if we don't have permissions sysSink will be null.
+            testLogger(provider, loggerDescMap, "foo", null,
+                PlatformLogger.Bridge.convert(sysLogger1), sysSink);
+        }
+
+        Logger appLogger2 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
+
+        if (appLogger2 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger in system map");
+        }
+        if (provider.user.contains(appLogger2)) {
+            throw new RuntimeException("localized app logger  in appplication map");
+        }
+
+        testLogger(provider, loggerDescMap, "foo", loggerBundle,
+                PlatformLogger.Bridge.convert(appLogger2), appSink);
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+            loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for localized system logger: " + acx);
+        }
+        if (hasRequiredPermissions && appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (hasRequiredPermissions && sysLogger2 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (hasRequiredPermissions && provider.user.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger in appplication map");
+        }
+        if (hasRequiredPermissions && provider.system.contains(sysLogger2)) {
+            throw new RuntimeException("localized sys logger not in system map");
+        }
+
+        if (hasRequiredPermissions) {
+            // if we don't have permissions sysSink will be null.
+            testLogger(provider, loggerDescMap, "foo", loggerBundle,
+                PlatformLogger.Bridge.convert(sysLogger2), sysSink);
+        }
+
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void checkLogEvent(TestLoggerFinder provider, String desc,
+            TestLoggerFinder.LogEvent expected) {
+        TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+        if (!Objects.equals(expected, actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void checkLogEvent(TestLoggerFinder provider, String desc,
+            TestLoggerFinder.LogEvent expected, boolean expectNotNull) {
+        TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+        if (actual == null && !expectNotNull) return;
+        if (actual != null && !expectNotNull) {
+            throw new RuntimeException("Unexpected log event found for " + desc
+                + "\n\tgot: " + actual);
+        }
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+        static Supplier<String> logpMessage(ResourceBundle bundle,
+                String className, String methodName, Supplier<String> msg) {
+            if (BEST_EFFORT_FOR_LOGP && bundle == null
+                    && (className != null || methodName != null)) {
+                final String cName = className == null ? "" :  className;
+                final String mName = methodName == null ? "" : methodName;
+                return () -> String.format("[%s %s] %s", cName, mName, msg.get());
+            } else {
+                return msg;
+            }
+        }
+
+        static String logpMessage(ResourceBundle bundle,
+                String className, String methodName, String msg) {
+            if (BEST_EFFORT_FOR_LOGP && bundle == null
+                    && (className != null || methodName != null)) {
+                final String cName = className == null ? "" :  className;
+                final String mName = methodName == null ? "" : methodName;
+                return String.format("[%s %s] %s", cName, mName, msg == null ? "" : msg);
+            } else {
+                return msg;
+            }
+        }
+
+    // Calls the methods defined on LogProducer and verify the
+    // parameters received by the underlying TestLoggerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(TestLoggerFinder provider,
+            Map<Object, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            PlatformLogger.Bridge logger,
+            TestLoggerFinder.LoggerImpl sink) {
+
+        if (loggerDescMap.get(logger) == null) {
+            throw new RuntimeException("Test bug: Missing description");
+        }
+        System.out.println("Testing " + loggerDescMap.get(logger) +" [" + logger + "]");
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        System.out.println("\tlogger.log(messageLevel, fooMsg)");
+        System.out.println("\tlogger.<level>(fooMsg)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, loggerBundle,
+                            fooMsg, null, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooMsg);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        Supplier<String> supplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+        System.out.println("\tlogger.log(messageLevel, supplier)");
+        System.out.println("\tlogger.<level>(supplier)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, (ResourceBundle) null,
+                            null, supplier, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = fooMsg;
+        System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, loggerBundle,
+                            format, null, (Throwable)null, arg1, arg2);
+                logger.log(messageLevel, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, loggerBundle,
+                            fooMsg, null, thrown, (Object[])null);
+                logger.log(messageLevel, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.log(messageLevel, thrown, supplier)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, (ResourceBundle)null,
+                            null, supplier, thrown, (Object[])null);
+                logger.log(messageLevel, thrown, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        String sourceClass = "blah.Blah";
+        String sourceMethod = "blih";
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0;
+                TestLoggerFinder.LogEvent expected =
+                    isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP?
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            isLoggable,
+                            name, expectedMessageLevel, loggerBundle,
+                            logpMessage(loggerBundle, sourceClass, sourceMethod, fooMsg),
+                            null, (Throwable)null, (Object[]) null) : null;
+                logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0;
+                TestLoggerFinder.LogEvent expected = isLoggable ?
+                    TestLoggerFinder.LogEvent.ofp(BEST_EFFORT_FOR_LOGP,
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            isLoggable,
+                            name, expectedMessageLevel, null, null,
+                            logpMessage(null, sourceClass, sourceMethod, supplier),
+                            (Throwable)null, (Object[]) null)) : null;
+                logger.logp(messageLevel, sourceClass, sourceMethod, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0;
+                TestLoggerFinder.LogEvent expected =
+                    isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP?
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, loggerBundle,
+                            logpMessage(loggerBundle, sourceClass, sourceMethod, format),
+                            null, (Throwable)null, arg1, arg2) : null;
+                logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0;
+                TestLoggerFinder.LogEvent expected =
+                    isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP ?
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, loggerBundle,
+                            logpMessage(loggerBundle, sourceClass, sourceMethod, fooMsg),
+                            null, thrown, (Object[])null) : null;
+                logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0;
+                TestLoggerFinder.LogEvent expected = isLoggable ?
+                    TestLoggerFinder.LogEvent.ofp(BEST_EFFORT_FOR_LOGP,
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, null, null,
+                            logpMessage(null, sourceClass, sourceMethod, supplier),
+                            thrown, (Object[])null)) : null;
+                logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, bundle,
+                            format, null, (Throwable)null, arg1, arg2);
+                logger.logrb(messageLevel, bundle, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, bundle,
+                            fooMsg, null, thrown, (Object[])null);
+                logger.logrb(messageLevel, bundle, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, bundle,
+                            format, null, (Throwable)null, arg1, arg2);
+                logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, bundle,
+                            fooMsg, null, thrown, (Object[])null);
+                logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+        final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging");
+
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl,
+                ThreadLocal<AtomicBoolean> allowAccess,
+                ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+            allPermissions = new PermissionsBuilder()
+                    .add(new java.security.AllPermission())
+                    .toPermissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS_LOGGER);
+                    builder.add(ACCESS_LOGGING);
+                }
+                if (allowAll.get().get()) {
+                    builder.addAll(allPermissions);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    Class<?> finderClass = null;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (finderClass != null) return finderClass;
+
+            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    finderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    return finderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (name.endsWith("$BaseLoggerFinder")) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (name.endsWith("$BaseLoggerFinder")) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+BaseLoggerBridgeTest$BaseLoggerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,732 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.security.AccessControlException;
+import java.util.stream.Stream;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for JDK internal API.
+ *   Tests a naive implementation of System.Logger, and in particular
+ *   the default mapping provided by PlatformLogger.
+ * @modules java.base/sun.util.logging
+ * @build CustomSystemClassLoader BasePlatformLoggerTest
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest NOSECURITY
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest NOPERMISSIONS
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class BasePlatformLoggerTest {
+
+    public static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    static final Class<?> providerClass;
+    static {
+        try {
+            providerClass = ClassLoader.getSystemClassLoader().loadClass("BasePlatformLoggerTest$BaseLoggerFinder");
+        } catch (ClassNotFoundException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    static final PlatformLogger.Level[] julLevels = {
+        PlatformLogger.Level.ALL,
+        PlatformLogger.Level.FINEST,
+        PlatformLogger.Level.FINER,
+        PlatformLogger.Level.FINE,
+        PlatformLogger.Level.CONFIG,
+        PlatformLogger.Level.INFO,
+        PlatformLogger.Level.WARNING,
+        PlatformLogger.Level.SEVERE,
+        PlatformLogger.Level.OFF,
+    };
+
+    static final Level[] mappedLevels = {
+        Level.ALL,     // ALL
+        Level.TRACE,   // FINEST
+        Level.TRACE,   // FINER
+        Level.DEBUG,   // FINE
+        Level.DEBUG,   // CONFIG
+        Level.INFO,    // INFO
+        Level.WARNING, // WARNING
+        Level.ERROR,   // SEVERE
+        Level.OFF,     // OFF
+    };
+
+    final static Map<PlatformLogger.Level, Level> julToSpiMap;
+    static {
+        Map<PlatformLogger.Level, Level> map = new HashMap<>();
+        if (mappedLevels.length != julLevels.length) {
+            throw new ExceptionInInitializerError("Array lengths differ"
+                + "\n\tjulLevels=" + Arrays.deepToString(julLevels)
+                + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels));
+        }
+        for (int i=0; i<julLevels.length; i++) {
+            map.put(julLevels[i], mappedLevels[i]);
+        }
+        julToSpiMap = Collections.unmodifiableMap(map);
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+
+    public static interface TestLoggerFinder  {
+        final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+        public Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+        public static final class LogEvent implements Cloneable {
+
+            public LogEvent() {
+                this(sequencer.getAndIncrement());
+            }
+
+            LogEvent(long sequenceNumber) {
+                this.sequenceNumber = sequenceNumber;
+            }
+
+            long sequenceNumber;
+            boolean isLoggable;
+            String loggerName;
+            Level level;
+            ResourceBundle bundle;
+            Throwable thrown;
+            Object[] args;
+            Supplier<String> supplier;
+            String msg;
+
+            Object[] toArray() {
+                return new Object[] {
+                    sequenceNumber,
+                    isLoggable,
+                    loggerName,
+                    level,
+                    bundle,
+                    thrown,
+                    args,
+                    supplier,
+                    msg,
+                };
+            }
+
+            @Override
+            public String toString() {
+                return Arrays.deepToString(toArray());
+            }
+
+
+
+            @Override
+            public boolean equals(Object obj) {
+                return obj instanceof LogEvent
+                        && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(toArray());
+            }
+
+            public LogEvent cloneWith(long sequenceNumber)
+                    throws CloneNotSupportedException {
+                LogEvent cloned = (LogEvent)super.clone();
+                cloned.sequenceNumber = sequenceNumber;
+                return cloned;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = null;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, Throwable thrown, Supplier<String> supplier) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = null;
+                evt.bundle = null;
+                evt.thrown = thrown;
+                evt.supplier = supplier;
+                evt.msg = null;
+                return evt;
+            }
+
+            public static LogEvent of(boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Object... params) {
+                LogEvent evt = new LogEvent();
+                evt.isLoggable = isLoggable;
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = null;
+                evt.supplier = null;
+                evt.msg = key;
+                return evt;
+            }
+
+            public static LogEvent of(long sequenceNumber,
+                    boolean isLoggable, String name,
+                    Level level, ResourceBundle bundle,
+                    String key, Supplier<String> supplier,
+                    Throwable thrown, Object... params) {
+                LogEvent evt = new LogEvent(sequenceNumber);
+                evt.loggerName = name;
+                evt.level = level;
+                evt.args = params;
+                evt.bundle = bundle;
+                evt.thrown = thrown;
+                evt.supplier = supplier;
+                evt.msg = key;
+                evt.isLoggable = isLoggable;
+                return evt;
+            }
+
+        }
+
+        public class LoggerImpl implements Logger {
+            private final String name;
+            private Level level = Level.INFO;
+
+            public LoggerImpl(String name) {
+                this.name = name;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Level level) {
+                return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown));
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+                log(LogEvent.of(isLoggable(level), name, level, bundle, format, params));
+            }
+
+            void log(LogEvent event) {
+                eventQueue.add(event);
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name, level, null, msgSupplier));
+            }
+
+            @Override
+            public void log(Level level,  Supplier<String> msgSupplier, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name, level, thrown, msgSupplier));
+            }
+        }
+
+        public Logger getLogger(String name, Class<?> caller);
+    }
+
+    public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder {
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            }
+        }
+    }
+
+    static PlatformLogger getPlatformLogger(String name) {
+        boolean old = allowAccess.get().get();
+        allowAccess.get().set(true);
+        try {
+            return PlatformLogger.getLogger(name);
+        } finally {
+            allowAccess.get().set(old);
+        }
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            TestLoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder());
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) {
+
+        final Map<PlatformLogger, String> loggerDescMap = new HashMap<>();
+
+        TestLoggerFinder.LoggerImpl appSink;
+        boolean before = allowControl.get().get();
+        try {
+            allowControl.get().set(true);
+            appSink = TestLoggerFinder.LoggerImpl.class.cast(
+                        provider.getLogger("foo", BasePlatformLoggerTest.class));
+        } finally {
+            allowControl.get().set(before);
+        }
+
+        TestLoggerFinder.LoggerImpl sysSink = null;
+        before = allowControl.get().get();
+        try {
+            allowControl.get().set(true);
+            sysSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class));
+        } finally {
+            allowControl.get().set(before);
+        }
+
+        if (hasRequiredPermissions && appSink == sysSink) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        if (provider.system.contains(appSink)) {
+            throw new RuntimeException("app logger in system map");
+        }
+        if (!provider.user.contains(appSink)) {
+            throw new RuntimeException("app logger not in appplication map");
+        }
+        if (hasRequiredPermissions && provider.user.contains(sysSink)) {
+            throw new RuntimeException("sys logger in appplication map");
+        }
+        if (hasRequiredPermissions && !provider.system.contains(sysSink)) {
+            throw new RuntimeException("sys logger not in system map");
+        }
+
+        PlatformLogger platform = getPlatformLogger("foo");
+        loggerDescMap.put(platform, "PlatformLogger.getLogger(\"foo\")");
+
+        testLogger(provider, loggerDescMap, "foo", null, platform, sysSink);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void checkLogEvent(TestLoggerFinder provider, String desc,
+            TestLoggerFinder.LogEvent expected) {
+        TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void checkLogEvent(TestLoggerFinder provider, String desc,
+            TestLoggerFinder.LogEvent expected, boolean expectNotNull) {
+        TestLoggerFinder.LogEvent actual =  provider.eventQueue.poll();
+        if (actual == null && !expectNotNull) return;
+        if (actual != null && !expectNotNull) {
+            throw new RuntimeException("Unexpected log event found for " + desc
+                + "\n\tgot: " + actual);
+        }
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    // Calls the methods defined on LogProducer and verify the
+    // parameters received by the underlying TestLoggerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(TestLoggerFinder provider,
+            Map<PlatformLogger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            PlatformLogger logger,
+            TestLoggerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger));
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        System.out.println("\tlogger.<level>(fooMsg)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (PlatformLogger.Level messageLevel :julLevels) {
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, loggerBundle,
+                            fooMsg, null, (Throwable)null, (Object[])null);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == PlatformLogger.Level.FINEST) {
+                    logger.finest(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.FINER) {
+                    logger.finer(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.FINE) {
+                    logger.fine(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.CONFIG) {
+                    logger.config(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.INFO) {
+                    logger.info(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.WARNING) {
+                    logger.warning(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.SEVERE) {
+                    logger.severe(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        System.out.println("\tlogger.<level>(msg, thrown)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (PlatformLogger.Level messageLevel :julLevels) {
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, (ResourceBundle) null,
+                            fooMsg, null, (Throwable)thrown, (Object[])null);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == PlatformLogger.Level.FINEST) {
+                    logger.finest(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.FINER) {
+                    logger.finer(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.FINE) {
+                    logger.fine(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.CONFIG) {
+                    logger.config(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.INFO) {
+                    logger.info(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.WARNING) {
+                    logger.warning(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.SEVERE) {
+                    logger.severe(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = fooMsg;
+        System.out.println("\tlogger.<level>(format, arg1, arg2)");
+        for (Level loggerLevel : Level.values()) {
+            sink.level = loggerLevel;
+            for (PlatformLogger.Level messageLevel :julLevels) {
+                Level expectedMessageLevel = julToSpiMap.get(messageLevel);
+                TestLoggerFinder.LogEvent expected =
+                        TestLoggerFinder.LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0,
+                            name, expectedMessageLevel, (ResourceBundle) null,
+                            format, null, (Throwable)null, foo, fooMsg);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(format, foo, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == PlatformLogger.Level.FINEST) {
+                    logger.finest(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.FINER) {
+                    logger.finer(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.FINE) {
+                    logger.fine(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.CONFIG) {
+                    logger.config(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.INFO) {
+                    logger.info(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.WARNING) {
+                    logger.warning(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == PlatformLogger.Level.SEVERE) {
+                    logger.severe(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                }
+            }
+        }
+
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging");
+
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl,
+                ThreadLocal<AtomicBoolean> allowAccess,
+                ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+            allPermissions = new PermissionsBuilder()
+                    .add(new java.security.AllPermission())
+                    .toPermissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS_LOGGING);
+                }
+                if (allowAll.get().get()) {
+                    builder.addAll(allPermissions);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    Class<?> finderClass = null;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (finderClass != null) return finderClass;
+
+            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    finderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    return finderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (name.endsWith("$BaseLoggerFinder")) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (name.endsWith("$BaseLoggerFinder")) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+BasePlatformLoggerTest$BaseLoggerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AllPermission;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.internal.logger.BootstrapLogger;
+import jdk.internal.logger.LazyLoggers;
+
+/*
+ * @test
+ * @bug     8140364
+ * @author  danielfuchs
+ * @summary JDK implementation specific unit test for JDK internal artifacts.
+            Tests the behavior of bootstrap loggers (and SimpleConsoleLoggers
+ *          too).
+ * @modules java.base/jdk.internal.logger
+ * @run main/othervm BootstrapLoggerTest NO_SECURITY
+ * @run main/othervm BootstrapLoggerTest SECURE
+ * @run main/othervm/timeout=120 BootstrapLoggerTest SECURE_AND_WAIT
+ */
+public class BootstrapLoggerTest {
+
+    static final Method awaitPending;
+    static final Method isAlive;
+    static final Field isBooted;
+    static final Field logManagerInitialized;
+    static {
+        try {
+            isBooted = BootstrapLogger.class.getDeclaredField("isBooted");
+            isBooted.setAccessible(true);
+            // private reflection hook that allows us to test wait until all
+            // the tasks pending in the BootstrapExecutor are finished.
+            awaitPending = BootstrapLogger.class
+                    .getDeclaredMethod("awaitPendingTasks");
+            awaitPending.setAccessible(true);
+            // private reflection hook that allows us to test whether
+            // the BootstrapExecutor is alive.
+            isAlive = BootstrapLogger.class
+                    .getDeclaredMethod("isAlive");
+            isAlive.setAccessible(true);
+            // private reflection hook that allows us to test whether the LogManager
+            // has initialized and registered with the BootstrapLogger class
+            logManagerInitialized = BootstrapLogger.class
+                    .getDeclaredField("logManagerConfigured");
+            logManagerInitialized.setAccessible(true);
+        } catch (Exception ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    static void awaitPending() {
+        try {
+            awaitPending.invoke(null);
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+            ex.printStackTrace(LogStream.err);
+        }
+    }
+
+    /**
+     * We use an instance of this class to check what the logging system has
+     * printed on System.err.
+     */
+    public static class LogStream extends OutputStream {
+
+        final static PrintStream err = System.err;
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        public LogStream() {
+            super();
+        }
+
+        @Override
+        public synchronized void write(int b) {
+            baos.write(b);
+            err.write(b);
+        }
+
+        public String drain() {
+            awaitPending();
+            synchronized(this) {
+                String txt = baos.toString();
+                baos.reset();
+                return txt;
+            }
+        }
+    }
+
+    static enum TestCase {
+        NO_SECURITY, SECURE, SECURE_AND_WAIT
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (args == null || args.length == 0) {
+            args = new String[] { TestCase.SECURE_AND_WAIT.name() };
+        }
+        if (args.length > 1) throw new RuntimeException("Only one argument allowed");
+        TestCase test = TestCase.valueOf(args[0]);
+        System.err.println("Testing: " + test);
+
+
+        // private reflection hook that allows us to simulate a non booted VM
+        final AtomicBoolean vmBooted = new AtomicBoolean(false);
+        isBooted.set(null,(BooleanSupplier) () -> vmBooted.get());
+
+        // We  replace System.err to check the messages that have been logged
+        // by the JUL ConsoleHandler and default SimpleConsoleLogger
+        // implementaion
+        final LogStream err = new LogStream();
+        System.setErr(new PrintStream(err));
+
+        if (BootstrapLogger.isBooted()) {
+            throw new RuntimeException("VM should not be booted!");
+        }
+        Logger logger = LazyLoggers.getLogger("foo.bar", Thread.class);
+
+        if (test != TestCase.NO_SECURITY) {
+            LogStream.err.println("Setting security manager");
+            Policy.setPolicy(new SimplePolicy());
+            System.setSecurityManager(new SecurityManager());
+        }
+
+        Level[] levels = {Level.INFO, Level.WARNING, Level.INFO};
+        int index = 0;
+        logger.log(levels[index], "Early message #" + (index+1)); index++;
+        logger.log(levels[index], "Early message #" + (index+1)); index++;
+        LogStream.err.println("VM Booted: " + vmBooted.get());
+        LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null));
+        logger.log(levels[index], "Early message #" + (index+1)); index++;
+        if (err.drain().contains("Early message")) {
+            // We're expecting that logger will be a LazyLogger wrapping a
+            // BootstrapLogger. The Bootstrap logger will stack the log messages
+            // it receives until the VM is booted.
+            // Since our private hook pretend that the VM is not booted yet,
+            // the logged messages shouldn't have reached System.err yet.
+            throw new RuntimeException("Early message logged while VM is not booted!");
+        }
+
+        // Now pretend that the VM is booted. Nothing should happen yet, until
+        // we try to log a new message.
+        vmBooted.getAndSet(true);
+        LogStream.err.println("VM Booted: " + vmBooted.get());
+        LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null));
+        if (!BootstrapLogger.isBooted()) {
+            throw new RuntimeException("VM should now be booted!");
+        }
+        if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+            throw new RuntimeException("LogManager shouldn't be initialized yet!");
+        }
+
+        // Logging a message should cause the BootstrapLogger to replace itself
+        // by a 'real' logger in the LazyLogger. But since the LogManager isn't
+        // initialized yet, this should be a SimpleConsoleLogger...
+        logger.log(Level.INFO, "LOG#4: VM now booted: {0}", vmBooted.get());
+        logger.log(Level.DEBUG, "LOG#5: hi!");
+        SimplePolicy.allowAll.set(Boolean.TRUE);
+        WeakReference<Thread> threadRef = null;
+        ReferenceQueue<Thread> queue = new ReferenceQueue<>();
+        try {
+            Set<Thread> set = Thread.getAllStackTraces().keySet().stream()
+                    .filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-"))
+                    .collect(Collectors.toSet());
+            set.stream().forEach(t -> LogStream.err.println("Found: " + t));
+            if (set.size() > 1) {
+                throw new RuntimeException("Too many bootsrap threads found");
+            }
+            Optional<Thread> t = set.stream().findFirst();
+            if (t.isPresent()) {
+                threadRef = new WeakReference<>(t.get(), queue);
+            }
+        } finally{
+            SimplePolicy.allowAll.set(Boolean.FALSE);
+        }
+        if (!BootstrapLogger.isBooted()) {
+            throw new RuntimeException("VM should still be booted!");
+        }
+        if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+            throw new RuntimeException("LogManager shouldn't be initialized yet!");
+        }
+
+        // Now check that the early messages we had printed before the VM was
+        // booted have appeared on System.err...
+        String afterBoot = err.drain();
+        for (int i=0; i<levels.length; i++) {
+            String m = levels[i].getName()+": Early message #"+(i+1);
+            if (!afterBoot.contains(m)) {
+                throw new RuntimeException("System.err does not contain: "+m);
+            }
+        }
+        // check that the message logged *after* the VM was booted also printed.
+        if (!afterBoot.contains("INFO: LOG#4")) {
+            throw new RuntimeException("System.err does not contain: "
+                    + "INFO: LOG#4");
+        }
+        // check that the debug message was not printed.
+        if (afterBoot.contains("LOG#5")) {
+            throw new RuntimeException("System.err contain: " + "LOG#5");
+        }
+        LogStream.err.println("VM Booted: " + vmBooted.get());
+        LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null));
+        if (!BootstrapLogger.isBooted()) {
+            throw new RuntimeException("VM should still be booted!");
+        }
+        if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+            throw new RuntimeException("LogManager shouldn't be initialized yet!");
+        }
+
+        // Now we're going to use reflection to access JUL, and change
+        // the level of the "foo" logger.
+        // We're using reflection so that the test can also run in
+        // configurations where java.util.logging is not present.
+        boolean hasJUL = false;
+        SimplePolicy.allowAll.set(Boolean.TRUE);
+        try {
+            Class<?> loggerClass = Class.forName("java.util.logging.Logger");
+            Class<?> levelClass  = Class.forName("java.util.logging.Level");
+            Class<?> handlerClass  = Class.forName("java.util.logging.Handler");
+
+            // java.util.logging.Logger.getLogger("foo")
+            //        .setLevel(java.util.logging.Level.FINEST);
+            Object fooLogger = loggerClass.getMethod("getLogger", String.class)
+                    .invoke(null, "foo");
+            loggerClass.getMethod("setLevel", levelClass)
+                    .invoke(fooLogger, levelClass.getField("FINEST").get(null));
+
+            // java.util.logging.Logger.getLogger("").getHandlers()[0]
+            //        .setLevel(java.util.logging.Level.ALL);
+            Object rootLogger = loggerClass.getMethod("getLogger", String.class)
+                    .invoke(null, "");
+            Object handlers = loggerClass.getMethod("getHandlers").
+                    invoke(rootLogger);
+            handlerClass.getMethod("setLevel", levelClass)
+                    .invoke(Array.get(handlers, 0), levelClass.getField("ALL")
+                            .get(null));
+
+            hasJUL = true;
+        } catch (ClassNotFoundException x) {
+            LogStream.err.println("JUL is not present: class " + x.getMessage()
+                    + " not found");
+            hasJUL = false;
+        } finally {
+            SimplePolicy.allowAll.set(Boolean.FALSE);
+        }
+
+        logger.log(Level.DEBUG, "hi now!");
+        String debug = err.drain();
+        if (hasJUL) {
+            if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+                throw new RuntimeException("LogManager should be initialized now!");
+            }
+            if (!debug.contains("FINE: hi now!")) {
+                throw new RuntimeException("System.err does not contain: "
+                        + "FINE: hi now!");
+            }
+        } else {
+            if (debug.contains("hi now!")) {
+                throw new RuntimeException("System.err contains: " + "hi now!");
+            }
+            if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+                throw new RuntimeException("LogManager shouldn't be initialized yet!");
+            }
+            Logger baz = System.getLogger("foo.bar.baz");
+            if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+                throw new RuntimeException("LogManager shouldn't be initialized yet!");
+            }
+        }
+        Logger bazbaz = null;
+        SimplePolicy.allowAll.set(Boolean.TRUE);
+        try {
+            bazbaz = java.lang.System.LoggerFinder
+                    .getLoggerFinder().getLogger("foo.bar.baz.baz", BootstrapLoggerTest.class);
+        } finally {
+            SimplePolicy.allowAll.set(Boolean.FALSE);
+        }
+        if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) {
+            throw new RuntimeException("LogManager should be initialized now!");
+        }
+        Logger bazbaz2 = System.getLogger("foo.bar.baz.baz");
+        if (bazbaz2.getClass() != bazbaz.getClass()) {
+            throw new RuntimeException("bazbaz2.class != bazbaz.class ["
+                    + bazbaz2.getClass() + " != "
+                    + bazbaz.getClass() + "]");
+        }
+        if (hasJUL != bazbaz2.getClass().getName()
+                .equals("sun.util.logging.internal.LoggingProviderImpl$JULWrapper")) {
+            throw new RuntimeException("Unexpected class for bazbaz: "
+                    + bazbaz.getClass().getName()
+                    + "\n\t expected: "
+                    + "sun.util.logging.internal.LoggingProviderImpl$JULWrapper");
+        }
+
+        // Now we're going to check that the thread of the BootstrapLogger
+        // executor terminates, and that the Executor is GC'ed after that.
+        // This will involve a bit of waiting, hence the timeout=120 in
+        // the @run line.
+        // If this test fails in timeout - we could envisage skipping this part,
+        // or adding some System property to configure the keep alive delay
+        // of the executor.
+        SimplePolicy.allowAll.set(Boolean.TRUE);
+        try {
+            Stream<Thread> stream = Thread.getAllStackTraces().keySet().stream();
+            stream.filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-"))
+                    .forEach(t -> LogStream.err.println(t));
+            stream = null;
+            if (threadRef != null && test == TestCase.SECURE_AND_WAIT) {
+                Thread t = threadRef.get();
+                if (t != null) {
+                    if (!(Boolean)isAlive.invoke(null)) {
+                        throw new RuntimeException("Executor already terminated");
+                    } else {
+                        LogStream.err.println("Executor still alive as expected.");
+                    }
+                    LogStream.err.println("Waiting for " + t.getName() + " to terminate (join)");
+                    t.join(60_000);
+                    t = null;
+                }
+                LogStream.err.println("Calling System.gc()");
+                System.gc();
+                LogStream.err.println("Waiting for BootstrapMessageLoggerTask to be gc'ed");
+                while (queue.remove(1000) == null) {
+                    LogStream.err.println("Calling System.gc()");
+                    System.gc();
+                }
+
+                // Call the reference here to make sure threadRef will not be
+                // eagerly garbage collected before the thread it references.
+                // otherwise, it might not be enqueued, resulting in the
+                // queue.remove() call above to always return null....
+                if (threadRef.get() != null) {
+                    throw new RuntimeException("Reference should have been cleared");
+                }
+
+                LogStream.err.println("BootstrapMessageLoggerTask has been gc'ed");
+                // Wait for the executor to be gc'ed...
+                for (int i=0; i<10; i++) {
+                    LogStream.err.println("Calling System.gc()");
+                    System.gc();
+                    if (!(Boolean)isAlive.invoke(null)) break;
+                    // It would be unexpected that we reach here...
+                    Thread.sleep(1000);
+                }
+
+                if ((Boolean)isAlive.invoke(null)) {
+                    throw new RuntimeException("Executor still alive");
+                } else {
+                    LogStream.err.println("Executor terminated as expected.");
+                }
+            } else {
+                LogStream.err.println("Not checking executor termination for " + test);
+            }
+        } finally {
+            SimplePolicy.allowAll.set(Boolean.FALSE);
+        }
+        LogStream.err.println(test.name() + ": PASSED");
+    }
+
+    final static class SimplePolicy extends Policy {
+        static final ThreadLocal<Boolean> allowAll = new ThreadLocal<Boolean>() {
+            @Override
+            protected Boolean initialValue() {
+                return Boolean.FALSE;
+            }
+        };
+
+        Permissions getPermissions() {
+            Permissions perms = new Permissions();
+            if (allowAll.get()) {
+                perms.add(new AllPermission());
+            }
+            return perms;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions(domain).implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return getPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return getPermissions();
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    Class<?> loggerFinderClass = null;
+//    Class<?> loggerImplClass = null;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (loggerFinderClass != null) return loggerFinderClass;
+
+            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    loggerFinderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    return loggerFinderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+//    private Class<?> defineLoggerImplClass(String name)
+//        throws ClassNotFoundException {
+//        final Object obj = getClassLoadingLock(name);
+//        synchronized(obj) {
+//            if (loggerImplClass != null) return loggerImplClass;
+//
+//            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
+//            File file = new File(url.getPath(), name+".class");
+//            if (file.canRead()) {
+//                try {
+//                    byte[] b = Files.readAllBytes(file.toPath());
+//                    Permissions perms = new Permissions();
+//                    perms.add(new AllPermission());
+//                    loggerImplClass = defineClass(
+//                            name, b, 0, b.length, new ProtectionDomain(
+//                            this.getClass().getProtectionDomain().getCodeSource(),
+//                            perms));
+//                    System.out.println("Loaded " + name);
+//                    return loggerImplClass;
+//                } catch (Throwable ex) {
+//                    ex.printStackTrace();
+//                    throw new ClassNotFoundException(name, ex);
+//                }
+//            } else {
+//                throw new ClassNotFoundException(name,
+//                        new IOException(file.toPath() + ": can't read"));
+//            }
+//        }
+//    }
+//
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (name.endsWith("$LogProducerFinder")) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+//        if (name.endsWith("$LogProducerFinder$LoggerImpl")) {
+//            Class<?> c = defineLoggerImplClass(name);
+//            if (resolve) {
+//                resolveClass(c);
+//            }
+//            return c;
+//        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+//        if (name.endsWith("$LogProducerFinder$LoggerImpl")) {
+//            return defineLoggerImplClass(name);
+//        }
+        if (name.endsWith("$$LogProducerFinder")) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1087 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.stream.Stream;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for JDK internal artifacts.
+ *          Tests all bridge methods with the a custom backend whose
+ *          loggers implement PlatformLogger.Bridge.
+ * @modules java.base/sun.util.logging java.base/jdk.internal.logger
+ * @build CustomSystemClassLoader LoggerBridgeTest
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest NOSECURITY
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest NOPERMISSIONS
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class LoggerBridgeTest {
+
+    public static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent implements Cloneable {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        sun.util.logging.PlatformLogger.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        Supplier<String> supplier;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                loggerName,
+                level,
+                isLoggable,
+                bundle,
+                msg,
+                supplier,
+                thrown,
+                args,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+
+        public LogEvent cloneWith(long sequenceNumber)
+                throws CloneNotSupportedException {
+            LogEvent cloned = (LogEvent)super.clone();
+            cloned.sequenceNumber = sequenceNumber;
+            return cloned;
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    null, null, level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                Supplier<String> supplier, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    null, null, level, bundle, supplier,
+                    thrown, params);
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+        public static LogEvent of(boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name,
+                    className, methodName, level, bundle, key, thrown, params);
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                Supplier<String> supplier, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.supplier = supplier;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+        public static LogEvent of(boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                Supplier<String> supplier, Throwable thrown, Object... params) {
+            return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name,
+                    className, methodName, level, bundle, supplier, thrown, params);
+        }
+
+    }
+    static final Class<?> providerClass;
+    static {
+        try {
+            // Preload classes before the security manager is on.
+            providerClass = ClassLoader.getSystemClassLoader().loadClass("LoggerBridgeTest$LogProducerFinder");
+            ((LoggerFinder)providerClass.newInstance()).getLogger("foo", providerClass);
+        } catch (Exception ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    public static class LogProducerFinder extends LoggerFinder {
+        final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+
+        public class LoggerImpl implements Logger, PlatformLogger.Bridge {
+            private final String name;
+            private sun.util.logging.PlatformLogger.Level level = sun.util.logging.PlatformLogger.Level.INFO;
+            private sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF;
+            private sun.util.logging.PlatformLogger.Level FINE = sun.util.logging.PlatformLogger.Level.FINE;
+            private sun.util.logging.PlatformLogger.Level FINER = sun.util.logging.PlatformLogger.Level.FINER;
+            private sun.util.logging.PlatformLogger.Level FINEST = sun.util.logging.PlatformLogger.Level.FINEST;
+            private sun.util.logging.PlatformLogger.Level CONFIG = sun.util.logging.PlatformLogger.Level.CONFIG;
+            private sun.util.logging.PlatformLogger.Level INFO = sun.util.logging.PlatformLogger.Level.INFO;
+            private sun.util.logging.PlatformLogger.Level WARNING = sun.util.logging.PlatformLogger.Level.WARNING;
+            private sun.util.logging.PlatformLogger.Level SEVERE = sun.util.logging.PlatformLogger.Level.SEVERE;
+
+            public LoggerImpl(String name) {
+                this.name = name;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Level level) {
+                return this.level != OFF && this.level.intValue() <= level.getSeverity();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle,
+                    String format, Object... params) {
+                throw new UnsupportedOperationException();
+            }
+
+            void log(LogEvent event) {
+                eventQueue.add(event);
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier,
+                    Throwable thrown) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msg, null, (Object[])null));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level,
+                    Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msgSupplier, null, (Object[])null));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, String msg,
+                    Object... params) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msg, null, params));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, String msg,
+                    Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown,
+                    Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msgSupplier, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, String msg) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msg, null, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msgSupplier, null, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, String msg, Object... params) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msg, null, params));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, String msg, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, Throwable thrown,
+                    Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msgSupplier, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, ResourceBundle bundle, String msg,
+                    Object... params) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, bundle, msg, null, params));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                    String msg, Object... params) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, bundle, msg, null, params));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, ResourceBundle bundle, String msg,
+                    Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, bundle, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                    String msg, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, bundle, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) {
+                return this.level != OFF && level.intValue()
+                        >= this.level.intValue();
+            }
+
+            @Override
+            public boolean isEnabled() {
+                return this.level != OFF;
+            }
+
+        }
+
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            }
+        }
+    }
+
+    static final sun.util.logging.PlatformLogger.Level[] julLevels = {
+        sun.util.logging.PlatformLogger.Level.ALL,
+        sun.util.logging.PlatformLogger.Level.FINEST,
+        sun.util.logging.PlatformLogger.Level.FINER,
+        sun.util.logging.PlatformLogger.Level.FINE,
+        sun.util.logging.PlatformLogger.Level.CONFIG,
+        sun.util.logging.PlatformLogger.Level.INFO,
+        sun.util.logging.PlatformLogger.Level.WARNING,
+        sun.util.logging.PlatformLogger.Level.SEVERE,
+        sun.util.logging.PlatformLogger.Level.OFF,
+    };
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    PlatformLogger.Level.valueOf(record.getLevel().getName()),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    final static Method lazyGetLogger;
+    static {
+        // jdk.internal.logging.LoggerBridge.getLogger(name, caller)
+        try {
+            Class<?> bridgeClass = Class.forName("jdk.internal.logger.LazyLoggers");
+            lazyGetLogger = bridgeClass.getDeclaredMethod("getLogger",
+                    String.class, Class.class);
+            lazyGetLogger.setAccessible(true);
+        } catch (Throwable ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    static Logger getLogger(LoggerFinder provider, String name, Class<?> caller) {
+        Logger logger;
+        try {
+            logger = Logger.class.cast(lazyGetLogger.invoke(null, name, caller));
+        } catch (Throwable x) {
+            Throwable t = (x instanceof InvocationTargetException) ?
+                    ((InvocationTargetException)x).getTargetException() : x;
+            if (t instanceof RuntimeException) {
+                throw (RuntimeException)t;
+            } else if (t instanceof Exception) {
+                throw new RuntimeException(t);
+            } else {
+                throw (Error)t;
+            }
+        }
+        // The method above does not throw exception...
+        // call the provider here to verify that an exception would have
+        // been thrown by the provider.
+        if (logger != null && caller == Thread.class) {
+            Logger log = provider.getLogger(name, caller);
+        }
+        return logger;
+    }
+
+    static Logger getLogger(LoggerFinder provider, String name, ResourceBundle bundle, Class<?> caller) {
+        if (caller.getClassLoader() != null) {
+            return System.getLogger(name,bundle);
+        } else {
+            return provider.getLocalizedLogger(name, bundle, caller);
+        }
+    }
+
+    static PlatformLogger.Bridge convert(Logger logger) {
+        return PlatformLogger.Bridge.convert(logger);
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                //"NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            LoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = LoggerFinder.getLoggerFinder();
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = LoggerFinder.getLoggerFinder();
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = LoggerFinder.getLoggerFinder();
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = LoggerFinder.getLoggerFinder();
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(LoggerFinder provider, boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Object, String> loggerDescMap = new HashMap<>();
+
+
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "LogProducer.getApplicationLogger(\"foo\")");
+
+        Logger sysLogger1 = null;
+        try {
+            sysLogger1 = getLogger(provider, "foo", Thread.class);
+            loggerDescMap.put(sysLogger1, "LogProducer.getSystemLogger(\"foo\")");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+        }
+
+
+        Logger appLogger2 =
+                System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "LogProducer.getApplicationLogger(\"foo\", loggerBundle)");
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = getLogger(provider, "foo", loggerBundle, Thread.class);
+            loggerDescMap.put(sysLogger2, "provider.getSystemLogger(\"foo\", loggerBundle)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for localized system logger: " + acx);
+        }
+        if (hasRequiredPermissions && appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (appLogger2 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (hasRequiredPermissions && sysLogger2 == sysLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+
+        final LogProducerFinder.LoggerImpl appSink;
+        final LogProducerFinder.LoggerImpl sysSink;
+        boolean old = allowControl.get().get();
+        allowControl.get().set(true);
+        try {
+           appSink = LogProducerFinder.LoggerImpl.class.cast(
+                   provider.getLogger("foo",  LoggerBridgeTest.class));
+           sysSink = LogProducerFinder.LoggerImpl.class.cast(
+                        provider.getLogger("foo", Thread.class));
+        } finally {
+            allowControl.get().set(old);
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, convert(appLogger1), appSink);
+        if (hasRequiredPermissions) {
+            testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1), sysSink);
+        }
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(appLogger2), appSink);
+        if (hasRequiredPermissions) {
+            testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(sysLogger2), sysSink);
+        }
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected) {
+        LogEvent actual =  eventQueue.poll();
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected, boolean expectNotNull) {
+        LogEvent actual =  eventQueue.poll();
+        if (actual == null && !expectNotNull) return;
+        if (actual != null && !expectNotNull) {
+            throw new RuntimeException("Unexpected log event found for " + desc
+                + "\n\tgot: " + actual);
+        }
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void setLevel( LogProducerFinder.LoggerImpl sink,
+            sun.util.logging.PlatformLogger.Level loggerLevel) {
+        sink.level = loggerLevel;
+    }
+
+    // Calls the methods defined on LogProducer and verify the
+    // parameters received by the underlying LogProducerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<Object, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            PlatformLogger.Bridge logger,
+            LogProducerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + "[" + logger + "]");
+        final sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        System.out.println("\tlogger.log(messageLevel, fooMsg)");
+        System.out.println("\tlogger.<level>(fooMsg)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, fooMsg);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        Supplier<String> supplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+        System.out.println("\tlogger.log(messageLevel, supplier)");
+        System.out.println("\tlogger.<level>(supplier)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, null,
+                            supplier, (Throwable)null, (Object[])null);
+                logger.log(messageLevel, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = fooMsg;
+        System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.log(messageLevel, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.log(messageLevel, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.log(messageLevel, thrown, supplier)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, null,
+                            supplier, thrown, (Object[])null);
+                logger.log(messageLevel, thrown, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        String sourceClass = "blah.Blah";
+        String sourceMethod = "blih";
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, loggerBundle,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, null,
+                            supplier, (Throwable)null, (Object[])null);
+                logger.logp(messageLevel, sourceClass, sourceMethod, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, loggerBundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, loggerBundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, null,
+                            supplier, thrown, (Object[])null);
+                logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, bundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.logrb(messageLevel, bundle, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, bundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.logrb(messageLevel, bundle, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, bundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, bundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected);
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+        final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging");
+
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl,
+                ThreadLocal<AtomicBoolean> allowAccess,
+                ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+            allPermissions = new PermissionsBuilder()
+                    .add(new java.security.AllPermission())
+                    .toPermissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS_LOGGER);
+                    builder.add(ACCESS_LOGGING);
+                }
+                if (allowAll.get().get()) {
+                    builder.addAll(allPermissions);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+LoggerBridgeTest$LogProducerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author danielfuchs
+ */
+public final class AccessSystemLogger {
+
+    public AccessSystemLogger() {
+        this(check());
+    }
+
+    private AccessSystemLogger(Void unused) {
+    }
+
+    private static Void check() {
+        if (AccessSystemLogger.class.getClassLoader() != null) {
+            throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader");
+        }
+        return null;
+    }
+
+    public Logger getLogger(String name) {
+        Logger logger = System.getLogger(name);
+        System.out.println("System.getLogger(\"" + name + "\"): " + logger);
+        return logger;
+    }
+
+    public Logger getLogger(String name, ResourceBundle bundle) {
+        Logger logger = System.getLogger(name, bundle);
+        System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger);
+        return logger;
+    }
+
+    static final Class<?>[] toCopy = { AccessSystemLogger.class, CustomSystemClassLoader.class };
+
+    // copy AccessSystemLogger.class to ./boot
+    public static void main(String[] args) throws IOException {
+        Path testDir = Paths.get(System.getProperty("user.dir", "."));
+        Path bootDir = Paths.get(testDir.toString(), "boot");
+        Path classes = Paths.get(System.getProperty("test.classes", "build/classes"));
+        if (Files.notExists(bootDir)) {
+            Files.createDirectory(bootDir);
+        }
+        for (Class<?> c : toCopy) {
+            Path thisClass = Paths.get(classes.toString(),
+                c.getSimpleName()+".class");
+            Path dest = Paths.get(bootDir.toString(),
+                c.getSimpleName()+".class");
+            Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions. The CustomSystemClassLoader class must be
+ * in the BCL, otherwise when system classes - such as
+ * ZoneDateTime try to load their resource bundle a MissingResourceBundle
+ * caused by a SecurityException may be thrown, as the CustomSystemClassLoader
+ * code base will be found in the stack called by doPrivileged.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    final List<String> finderClassNames =
+            Arrays.asList("LoggerFinderLoaderTest$BaseLoggerFinder",
+                    "LoggerFinderLoaderTest$BaseLoggerFinder2");
+    final Map<String, Class<?>> finderClasses = new HashMap<>();
+    Class<?> testLoggerFinderClass;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (finderClasses.get(name) != null) return finderClasses.get(name);
+            if (testLoggerFinderClass == null) {
+                // Hack: we  load testLoggerFinderClass to get its code source.
+                //       we can't use this.getClass() since we are in the boot.
+                testLoggerFinderClass = super.loadClass("LoggerFinderLoaderTest$TestLoggerFinder");
+            }
+            URL url = testLoggerFinderClass.getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    Class<?> finderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    finderClasses.put(name, finderClass);
+                    return finderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (finderClassNames.contains(name)) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (finderClassNames.contains(name)) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,882 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.stream.Stream;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.concurrent.atomic.AtomicReference;
+import jdk.internal.logger.SimpleConsoleLogger;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for LoggerFinderLoader.
+ *          Tests the behavior of LoggerFinderLoader with respect to the
+ *          value of the internal diagnosability switches. Also test the
+ *          DefaultLoggerFinder and SimpleConsoleLogger implementation.
+ * @modules java.base/sun.util.logging
+ *          java.base/jdk.internal.logger
+ * @build AccessSystemLogger LoggerFinderLoaderTest CustomSystemClassLoader
+ * @run  driver AccessSystemLogger
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class LoggerFinderLoaderTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
+    static final Class<?>[] providerClass;
+    static {
+        try {
+            providerClass = new Class<?>[] {
+                ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder"),
+                ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder2")
+            };
+        } catch (ClassNotFoundException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    /**
+     * What our test provider needs to implement.
+     */
+    public static interface TestLoggerFinder {
+        public final static AtomicBoolean fails = new AtomicBoolean();
+        public final static AtomicReference<String> conf = new AtomicReference<>("");
+        public final static AtomicLong sequencer = new AtomicLong();
+        public final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        public final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+
+        public class LoggerImpl implements System.Logger {
+            final String name;
+            final Logger logger;
+
+            public LoggerImpl(String name, Logger logger) {
+                this.name = name;
+                this.logger = logger;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Logger.Level level) {
+                return logger.isLoggable(level);
+            }
+
+            @Override
+            public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) {
+                logger.log(level, bundle, key, thrown);
+            }
+
+            @Override
+            public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) {
+                logger.log(level, bundle, format, params);
+            }
+
+        }
+
+        public Logger getLogger(String name, Class<?> caller);
+        public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class<?> caller);
+    }
+
+    public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder {
+
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                    new RuntimePermission("loggerFinder");
+        public BaseLoggerFinder() {
+            if (fails.get()) {
+                throw new RuntimeException("Simulate exception while loading provider");
+            }
+        }
+
+        System.Logger createSimpleLogger(String name) {
+            PrivilegedAction<System.Logger> pa = () -> SimpleConsoleLogger.makeSimpleLogger(name, false);
+            return AccessController.doPrivileged(pa);
+        }
+
+
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name)));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name)));
+            }
+        }
+    }
+
+    public static class BaseLoggerFinder2 extends LoggerFinder implements TestLoggerFinder {
+
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                    new RuntimePermission("loggerFinder");
+        public BaseLoggerFinder2() {
+            throw new ServiceConfigurationError("Should not come here");
+        }
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            throw new ServiceConfigurationError("Should not come here");
+        }
+    }
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    static LoggerFinder getLoggerFinder(Class<?> expectedClass,
+            String errorPolicy, boolean singleton) {
+        LoggerFinder provider = null;
+        try {
+            TestLoggerFinder.sequencer.incrementAndGet();
+            provider = LoggerFinder.getLoggerFinder();
+            if (TestLoggerFinder.fails.get() || singleton) {
+                if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
+                    throw new RuntimeException("Expected exception not thrown");
+                } else if ("WARNING".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
+                    String warning = ErrorStream.errorStream.peek();
+                    if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) {
+                        throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
+                    }
+                } else if ("DEBUG".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
+                    String warning = ErrorStream.errorStream.peek();
+                    if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) {
+                        throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
+                    }
+                    if (!warning.contains("WARNING: Exception raised trying to instantiate LoggerFinder")) {
+                        throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
+                    }
+                    if (TestLoggerFinder.fails.get()) {
+                        if (!warning.contains("java.util.ServiceConfigurationError: java.lang.System$LoggerFinder: Provider LoggerFinderLoaderTest$BaseLoggerFinder could not be instantiated")) {
+                            throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
+                        }
+                    } else if (singleton) {
+                        if (!warning.contains("java.util.ServiceConfigurationError: More than on LoggerFinder implementation")) {
+                            throw new RuntimeException("Expected message not found. Error stream contained: " + warning);
+                        }
+                    }
+                } else if ("QUIET".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("Unexpected error message found: "
+                                + ErrorStream.errorStream.peek());
+                    }
+                }
+            }
+        } catch(AccessControlException a) {
+            throw a;
+        } catch(Throwable t) {
+            if (TestLoggerFinder.fails.get() || singleton) {
+                // must check System.err
+                if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) {
+                    provider = LoggerFinder.getLoggerFinder();
+                } else {
+                    Throwable orig = t.getCause();
+                    while (orig != null && orig.getCause() != null) orig = orig.getCause();
+                    if (orig != null) orig.printStackTrace(ErrorStream.err);
+                    throw new RuntimeException("Unexpected exception: " + t, t);
+                }
+            } else {
+                throw new RuntimeException("Unexpected exception: " + t, t);
+            }
+        }
+        expectedClass.cast(provider);
+        ErrorStream.errorStream.store();
+        System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName());
+        return provider;
+    }
+
+
+    static class ErrorStream extends PrintStream {
+
+        static AtomicBoolean forward = new AtomicBoolean();
+        ByteArrayOutputStream out;
+        String saved = "";
+        public ErrorStream(ByteArrayOutputStream out) {
+            super(out);
+            this.out = out;
+        }
+
+        @Override
+        public void write(int b) {
+            super.write(b);
+            if (forward.get()) err.write(b);
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            super.write(b);
+            if (forward.get()) err.write(b);
+        }
+
+        @Override
+        public void write(byte[] buf, int off, int len) {
+            super.write(buf, off, len);
+            if (forward.get()) err.write(buf, off, len);
+        }
+
+        public String peek() {
+            flush();
+            return out.toString();
+        }
+
+        public String drain() {
+            flush();
+            String res = out.toString();
+            out.reset();
+            return res;
+        }
+
+        public void store() {
+            flush();
+            saved = out.toString();
+            out.reset();
+        }
+
+        public void restore() {
+            out.reset();
+            try {
+                out.write(saved.getBytes());
+            } catch(IOException io) {
+                throw new UncheckedIOException(io);
+            }
+        }
+
+        static final PrintStream err = System.err;
+        static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream());
+    }
+
+    private static StringBuilder appendProperty(StringBuilder b, String name) {
+        String value = System.getProperty(name);
+        if (value == null) return b;
+        return b.append(name).append("=").append(value).append('\n');
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+        }
+        Locale.setDefault(Locale.ENGLISH);
+        System.setErr(ErrorStream.errorStream);
+        System.setProperty("jdk.logger.packages", TestLoggerFinder.LoggerImpl.class.getName());
+        //System.setProperty("jdk.logger.finder.error", "ERROR");
+        //System.setProperty("jdk.logger.finder.singleton", "true");
+        //System.setProperty("test.fails", "true");
+        TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails"));
+        StringBuilder c = new StringBuilder();
+        appendProperty(c, "jdk.logger.packages");
+        appendProperty(c, "jdk.logger.finder.error");
+        appendProperty(c, "jdk.logger.finder.singleton");
+        appendProperty(c, "test.fails");
+        TestLoggerFinder.conf.set(c.toString());
+        try {
+            test(args);
+        } finally {
+            try {
+                System.setErr(ErrorStream.err);
+            } catch (Error | RuntimeException x) {
+                x.printStackTrace(ErrorStream.err);
+            }
+        }
+    }
+
+
+    public static void test(String[] args) {
+
+        final String errorPolicy =  System.getProperty("jdk.logger.finder.error", "WARNING");
+        final Boolean ensureSingleton = Boolean.getBoolean("jdk.logger.finder.singleton");
+
+        final Class<?> expectedClass =
+                TestLoggerFinder.fails.get() || ensureSingleton
+                ? jdk.internal.logger.DefaultLoggerFinder.class
+                : TestLoggerFinder.class;
+
+        System.out.println("Declared provider class: " + providerClass[0]
+                + "[" + providerClass[0].getClassLoader() + "]");
+
+        if (!TestLoggerFinder.fails.get()) {
+            ServiceLoader<LoggerFinder> serviceLoader =
+                ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader());
+            Iterator<LoggerFinder> iterator = serviceLoader.iterator();
+            Object firstProvider = iterator.next();
+            if (!firstProvider.getClass().getName().equals("LoggerFinderLoaderTest$BaseLoggerFinder")) {
+                throw new RuntimeException("Unexpected provider: " + firstProvider.getClass().getName());
+            }
+            if (!iterator.hasNext()) {
+                throw new RuntimeException("Expected two providers");
+            }
+        }
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            LoggerFinder provider;
+            ErrorStream.errorStream.restore();
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    setSecurityManager();
+                    try {
+                        provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton);
+                        test(provider, true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases.");
+    }
+
+    public static void test(LoggerFinder provider, boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Logger, String> loggerDescMap = new HashMap<>();
+
+        System.Logger sysLogger = accessSystemLogger.getLogger("foo");
+        loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")");
+        System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle);
+        loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)");
+        System.Logger appLogger = System.getLogger("bar");
+        loggerDescMap.put(appLogger,"System.getLogger(\"bar\")");
+        System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle);
+        loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)");
+
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger);
+        testLogger(provider, loggerDescMap, "foo", null, appLogger);
+        testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    // Calls the 8 methods defined on Logger and verify the
+    // parameters received by the underlying TestProvider.LoggerImpl
+    // logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<Logger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            Logger logger) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]");
+        AtomicLong sequencer = TestLoggerFinder.sequencer;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                ErrorStream.errorStream.drain();
+                String desc = "logger.log(messageLevel, foo): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, foo);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + fooMsg)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + fooMsg
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        String msg = "blah";
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg);
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + msgText)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + msgText
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        Supplier<String> fooSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, fooSupplier);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + fooSupplier.get()
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = msg;
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format);
+                    String text = java.text.MessageFormat.format(msgFormat, foo, msg);
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + text
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg);
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + msgText)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + msgText +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, fooSupplier, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + fooSupplier.get() +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, bundle, format, foo, msg);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg);
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + text
+                                + "\n>>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+        for (Level loggerLevel : EnumSet.of(Level.INFO)) {
+            for (Level messageLevel : Level.values()) {
+                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                sequencer.incrementAndGet();
+                logger.log(messageLevel, bundle, msg, thrown);
+                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
+                    if (!ErrorStream.errorStream.peek().isEmpty()) {
+                        throw new RuntimeException("unexpected event in queue for "
+                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
+                    }
+                } else {
+                    String logged = ErrorStream.errorStream.drain();
+                    String textMsg = bundle.getString(msg);
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    thrown.printStackTrace(new PrintStream(baos));
+                    String text = baos.toString();
+                    if (!logged.contains("LoggerFinderLoaderTest testLogger")
+                        || !logged.contains(messageLevel.getName() + ": " + textMsg)
+                        || !logged.contains(text)) {
+                        throw new RuntimeException("mismatch for " + desc
+                                + "\n\texpected:" + "\n<<<<\n"
+                                + "[date] LoggerFinderLoaderTest testLogger\n"
+                                + messageLevel.getName() + " " + textMsg +"\n"
+                                + text
+                                + ">>>>"
+                                + "\n\t  actual:"
+                                + "\n<<<<\n" + logged + ">>>>\n");
+                    } else {
+                        verbose("Got expected results for "
+                                + desc + "\n<<<<\n" + logged + ">>>>\n");
+                    }
+                }
+            }
+        }
+
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+
+        final Permissions permissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl, ThreadLocal<AtomicBoolean> allowAccess) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            permissions = new Permissions();
+            permissions.add(new RuntimePermission("setIO"));
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,3 @@
+LoggerFinderLoaderTest$BaseLoggerFinder
+LoggerFinderLoaderTest$BaseLoggerFinder2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.AllPermission;
+import java.security.Permissions;
+import java.security.ProtectionDomain;
+
+
+/**
+ * A custom ClassLoader to load the concrete LoggerFinder class
+ * with all permissions.
+ *
+ * @author danielfuchs
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+
+
+    Class<?> loggerFinderClass = null;
+
+    public CustomSystemClassLoader() {
+        super();
+    }
+    public CustomSystemClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    private Class<?> defineFinderClass(String name)
+        throws ClassNotFoundException {
+        final Object obj = getClassLoadingLock(name);
+        synchronized(obj) {
+            if (loggerFinderClass != null) return loggerFinderClass;
+
+            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
+            File file = new File(url.getPath(), name+".class");
+            if (file.canRead()) {
+                try {
+                    byte[] b = Files.readAllBytes(file.toPath());
+                    Permissions perms = new Permissions();
+                    perms.add(new AllPermission());
+                    loggerFinderClass = defineClass(
+                            name, b, 0, b.length, new ProtectionDomain(
+                            this.getClass().getProtectionDomain().getCodeSource(),
+                            perms));
+                    System.out.println("Loaded " + name);
+                    return loggerFinderClass;
+                } catch (Throwable ex) {
+                    ex.printStackTrace();
+                    throw new ClassNotFoundException(name, ex);
+                }
+            } else {
+                throw new ClassNotFoundException(name,
+                        new IOException(file.toPath() + ": can't read"));
+            }
+        }
+    }
+
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (name.endsWith("$LogProducerFinder")) {
+            Class<?> c = defineFinderClass(name);
+            if (resolve) {
+                resolveClass(c);
+            }
+            return c;
+        }
+        return super.loadClass(name, resolve);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (name.endsWith("$$LogProducerFinder")) {
+            return defineFinderClass(name);
+        }
+        return super.findClass(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1 @@
+PlatformLoggerBridgeTest$LogProducerFinder
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,876 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.stream.Stream;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for JDK internal artifacts.
+ *          Tests all bridge methods from PlatformLogger with the a custom
+ *          backend whose loggers implement PlatformLogger.Bridge.
+ * @modules java.base/sun.util.logging
+ * @build CustomSystemClassLoader PlatformLoggerBridgeTest
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest NOSECURITY
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest NOPERMISSIONS
+ * @run  main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest WITHPERMISSIONS
+ * @author danielfuchs
+ */
+public class PlatformLoggerBridgeTest {
+
+    static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    static final Class<?> providerClass;
+    static {
+        try {
+            // Preload classes before the security manager is on.
+            providerClass = ClassLoader.getSystemClassLoader().loadClass("PlatformLoggerBridgeTest$LogProducerFinder");
+            ((LoggerFinder)providerClass.newInstance()).getLogger("foo", providerClass);
+        } catch (Exception ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent implements Cloneable {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        sun.util.logging.PlatformLogger.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        Supplier<String> supplier;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                loggerName,
+                level,
+                isLoggable,
+                bundle,
+                msg,
+                supplier,
+                thrown,
+                args,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+
+        public LogEvent cloneWith(long sequenceNumber)
+                throws CloneNotSupportedException {
+            LogEvent cloned = (LogEvent)super.clone();
+            cloned.sequenceNumber = sequenceNumber;
+            return cloned;
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    null, null, level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                Supplier<String> supplier, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    null, null, level, bundle, supplier,
+                    thrown, params);
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+        public static LogEvent of(boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name,
+                    className, methodName, level, bundle, key, thrown, params);
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                Supplier<String> supplier, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.supplier = supplier;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+        public static LogEvent of(boolean isLoggable, String name,
+                String className, String methodName,
+                sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                Supplier<String> supplier, Throwable thrown, Object... params) {
+            return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name,
+                    className, methodName, level, bundle, supplier, thrown, params);
+        }
+
+    }
+
+    public static class LogProducerFinder extends LoggerFinder {
+        static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        final ConcurrentHashMap<String, LoggerImpl> system = new ConcurrentHashMap<>();
+        final ConcurrentHashMap<String, LoggerImpl> user = new ConcurrentHashMap<>();
+
+        public class LoggerImpl implements Logger, PlatformLogger.Bridge {
+            private final String name;
+            private sun.util.logging.PlatformLogger.Level level = sun.util.logging.PlatformLogger.Level.INFO;
+            private sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF;
+            private sun.util.logging.PlatformLogger.Level FINE = sun.util.logging.PlatformLogger.Level.FINE;
+            private sun.util.logging.PlatformLogger.Level FINER = sun.util.logging.PlatformLogger.Level.FINER;
+            private sun.util.logging.PlatformLogger.Level FINEST = sun.util.logging.PlatformLogger.Level.FINEST;
+            private sun.util.logging.PlatformLogger.Level CONFIG = sun.util.logging.PlatformLogger.Level.CONFIG;
+            private sun.util.logging.PlatformLogger.Level INFO = sun.util.logging.PlatformLogger.Level.INFO;
+            private sun.util.logging.PlatformLogger.Level WARNING = sun.util.logging.PlatformLogger.Level.WARNING;
+            private sun.util.logging.PlatformLogger.Level SEVERE = sun.util.logging.PlatformLogger.Level.SEVERE;
+
+            public LoggerImpl(String name) {
+                this.name = name;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public boolean isLoggable(Level level) {
+                return this.level != OFF && this.level.intValue() <= level.getSeverity();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle,
+                    String key, Throwable thrown) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void log(Level level, ResourceBundle bundle,
+                    String format, Object... params) {
+                throw new UnsupportedOperationException();
+            }
+
+            void log(LogEvent event) {
+                eventQueue.add(event);
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void log(Level level, Supplier<String> msgSupplier,
+                            Throwable thrown) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msg, null, (Object[])null));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level,
+                    Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msgSupplier, null, (Object[])null));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, String msg,
+                    Object... params) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msg, null, params));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, String msg,
+                    Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown,
+                    Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, null, msgSupplier, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, String msg) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msg, null, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msgSupplier, null, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, String msg, Object... params) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msg, null, params));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, String msg, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, Throwable thrown,
+                    Supplier<String> msgSupplier) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, null, msgSupplier, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, ResourceBundle bundle, String msg,
+                    Object... params) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, bundle, msg, null, params));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                    String msg, Object... params) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, bundle, msg, null, params));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass,
+                    String sourceMethod, ResourceBundle bundle, String msg,
+                    Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name,
+                        sourceClass, sourceMethod,
+                        level, bundle, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle,
+                    String msg, Throwable thrown) {
+                log(LogEvent.of(isLoggable(level), name, null, null,
+                        level, bundle, msg, thrown, (Object[])null));
+            }
+
+            @Override
+            public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) {
+                return this.level != OFF && level.intValue()
+                        >= this.level.intValue();
+            }
+
+            @Override
+            public boolean isEnabled() {
+                return this.level != OFF;
+            }
+
+
+        }
+
+        @Override
+        public Logger getLogger(String name, Class<?> caller) {
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(LOGGERFINDER_PERMISSION);
+            }
+            PrivilegedAction<ClassLoader> pa = () -> caller.getClassLoader();
+            ClassLoader callerLoader = AccessController.doPrivileged(pa);
+            if (callerLoader == null) {
+                return system.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            } else {
+                return user.computeIfAbsent(name, (n) -> new LoggerImpl(n));
+            }
+        }
+    }
+
+    static final sun.util.logging.PlatformLogger.Level[] julLevels = {
+        sun.util.logging.PlatformLogger.Level.ALL,
+        sun.util.logging.PlatformLogger.Level.FINEST,
+        sun.util.logging.PlatformLogger.Level.FINER,
+        sun.util.logging.PlatformLogger.Level.FINE,
+        sun.util.logging.PlatformLogger.Level.CONFIG,
+        sun.util.logging.PlatformLogger.Level.INFO,
+        sun.util.logging.PlatformLogger.Level.WARNING,
+        sun.util.logging.PlatformLogger.Level.SEVERE,
+        sun.util.logging.PlatformLogger.Level.OFF,
+    };
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap map = new ConcurrentHashMap();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    PlatformLogger.Level.valueOf(record.getLevel().getName()),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                //"NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            LoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    provider = LoggerFinder.getLoggerFinder();
+                    test(provider, true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    try {
+                        provider = LoggerFinder.getLoggerFinder();
+                        throw new RuntimeException("Expected exception not raised");
+                    } catch (AccessControlException x) {
+                        if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                            throw new RuntimeException("Unexpected permission check", x);
+                        }
+                        final boolean control = allowControl.get().get();
+                        try {
+                            allowControl.get().set(true);
+                            provider = LoggerFinder.getLoggerFinder();
+                        } finally {
+                            allowControl.get().set(control);
+                        }
+                    }
+                    test(provider, false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with access permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = LoggerFinder.getLoggerFinder();
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    final boolean access = allowAccess.get().get();
+                    try {
+                        allowAccess.get().set(true);
+                        test(provider, true);
+                    } finally {
+                        allowAccess.get().set(access);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(LoggerFinder provider, boolean hasRequiredPermissions) {
+
+        final Map<PlatformLogger, String> loggerDescMap = new HashMap<>();
+
+        PlatformLogger sysLogger1 = null;
+        try {
+            sysLogger1 = PlatformLogger.getLogger("foo");
+            loggerDescMap.put(sysLogger1, "PlatformLogger.getLogger(\"foo\")");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.ACCESS_LOGGING)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            final boolean old = allowAccess.get().get();
+            allowAccess.get().set(true);
+            try {
+                sysLogger1 = PlatformLogger.getLogger("foo");
+                loggerDescMap.put(sysLogger1, "PlatformLogger.getLogger(\"foo\")");
+            } finally {
+                allowAccess.get().set(old);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+        }
+
+        final LogProducerFinder.LoggerImpl sysSink;
+        boolean old = allowControl.get().get();
+        allowControl.get().set(true);
+        try {
+           sysSink = LogProducerFinder.LoggerImpl.class.cast(
+                        provider.getLogger("foo", Thread.class));
+        } finally {
+            allowControl.get().set(old);
+        }
+
+        testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected) {
+        LogEvent actual =  eventQueue.poll();
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected, boolean expectNotNull) {
+        LogEvent actual =  eventQueue.poll();
+        if (actual == null && !expectNotNull) return;
+        if (actual != null && !expectNotNull) {
+            throw new RuntimeException("Unexpected log event found for " + desc
+                + "\n\tgot: " + actual);
+        }
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void setLevel( LogProducerFinder.LoggerImpl sink,
+            sun.util.logging.PlatformLogger.Level loggerLevel) {
+        sink.level = loggerLevel;
+    }
+
+    // Calls the methods defined on LogProducer and verify the
+    // parameters received by the underlying LogProducerFinder.LoggerImpl
+    // logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<PlatformLogger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            PlatformLogger logger,
+            LogProducerFinder.LoggerImpl sink) {
+
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]");
+        final sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF;
+        final sun.util.logging.PlatformLogger.Level ALL = sun.util.logging.PlatformLogger.Level.OFF;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        System.out.println("\tlogger.log(messageLevel, fooMsg)");
+        System.out.println("\tlogger.<level>(fooMsg)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                if (messageLevel == ALL || messageLevel == OFF) continue;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, (Throwable)null, (Object[])null);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) {
+                    logger.finest(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) {
+                    logger.finer(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) {
+                    logger.fine(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) {
+                    logger.config(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) {
+                    logger.info(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) {
+                    logger.warning(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) {
+                    logger.severe(fooMsg);
+                    checkLogEvent(provider, desc2, expected);
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = fooMsg;
+        System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                if (messageLevel == ALL || messageLevel == OFF) continue;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            format, (Throwable)null, arg1, arg2);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(format, foo, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) {
+                    logger.finest(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) {
+                    logger.finer(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) {
+                    logger.fine(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) {
+                    logger.config(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) {
+                    logger.info(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) {
+                    logger.warning(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) {
+                    logger.severe(format, arg1, arg2);
+                    checkLogEvent(provider, desc2, expected);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)");
+        for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) {
+                if (messageLevel == ALL || messageLevel == OFF) continue;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, thrown, (Object[])null);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) {
+                    logger.finest(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) {
+                    logger.finer(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) {
+                    logger.fine(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) {
+                    logger.config(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) {
+                    logger.info(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) {
+                    logger.warning(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) {
+                    logger.severe(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected);
+                }
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION;
+        final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+        final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging");
+
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl,
+                ThreadLocal<AtomicBoolean> allowAccess,
+                ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+            allPermissions = new PermissionsBuilder()
+                    .add(new java.security.AllPermission())
+                    .toPermissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(CONTROL);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS_LOGGER);
+                    builder.add(ACCESS_LOGGING);
+                }
+                if (allowAll.get().get()) {
+                    builder.addAll(allPermissions);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,497 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug     8140364
+ * @author  danielfuchs
+ * @summary JDK implementation specific unit test for JDK internal artifacts.
+ *          Tests the consistency of the LoggerFinder and JDK extensions.
+ * @modules java.base/sun.util.logging
+ *          java.base/jdk.internal.logger
+ * @run  main LoggerFinderAPITest
+ */
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import sun.util.logging.PlatformLogger;
+
+public class LoggerFinderAPITest {
+
+    static final Class<java.lang.System.Logger> spiLoggerClass
+            = java.lang.System.Logger.class;
+    static final Class<java.lang.System.Logger> jdkLoggerClass
+            = java.lang.System.Logger.class;
+    static final Class<sun.util.logging.PlatformLogger.Bridge> bridgeLoggerClass
+            = sun.util.logging.PlatformLogger.Bridge.class;
+    static final Class<java.util.logging.Logger> julLoggerClass
+            = java.util.logging.Logger.class;
+    static final Class<sun.util.logging.PlatformLogger.Bridge> julLogProducerClass
+            = PlatformLogger.Bridge.class;
+    static final Pattern julLogNames = Pattern.compile(
+            "^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$");
+    static final Collection<Method> julLoggerIgnores;
+    static {
+        List<Method> ignores = new ArrayList<>();
+        try {
+            ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class));
+        } catch (NoSuchMethodException | SecurityException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+        julLoggerIgnores = Collections.unmodifiableList(ignores);
+    }
+
+
+
+    // Don't require LoggerBridge to have a body for those methods
+    interface LoggerBridgeMethodsWithNoBody extends
+        PlatformLogger.Bridge, java.lang.System.Logger {
+
+        @Override
+        public default String getName() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public default boolean isLoggable(PlatformLogger.Level level) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public default void log(sun.util.logging.PlatformLogger.Level level,
+                         String msg, Throwable thrown) {
+        }
+        @Override
+        public default void log(sun.util.logging.PlatformLogger.Level level,
+                         Throwable thrown, Supplier<String> msgSupplier) {
+        }
+        @Override
+        public default void log(sun.util.logging.PlatformLogger.Level level,
+                         Supplier<String> msgSupplier) {
+        }
+        @Override
+        public default void log(sun.util.logging.PlatformLogger.Level level, String msg) {
+        }
+        @Override
+        public default void log(sun.util.logging.PlatformLogger.Level level,
+                         String format, Object... params) {
+        }
+        @Override
+        public default void logrb(sun.util.logging.PlatformLogger.Level level,
+                         ResourceBundle bundle, String key, Throwable thrown) {
+        }
+        @Override
+        public default void logrb(sun.util.logging.PlatformLogger.Level level,
+                         ResourceBundle bundle, String format, Object... params) {
+        }
+
+        @Override
+        public default void logrb(PlatformLogger.Level level,
+                         String sourceClass, String sourceMethod,
+                         ResourceBundle bundle, String msg, Throwable thrown) {
+        }
+
+        @Override
+        public default void logrb(PlatformLogger.Level level, String sourceClass,
+                         String sourceMethod, ResourceBundle bundle, String msg,
+                         Object... params) {
+        }
+
+        @Override
+        public default void logp(PlatformLogger.Level level, String sourceClass,
+                         String sourceMethod, Supplier<String> msgSupplier) {
+        }
+
+        @Override
+        public default void logp(PlatformLogger.Level level, String sourceClass,
+                         String sourceMethod, String msg, Object... params) {
+        }
+
+        @Override
+        public default void logp(PlatformLogger.Level level, String sourceClass,
+                         String sourceMethod, String msg, Throwable thrown) {
+        }
+
+        @Override
+        public default void logp(PlatformLogger.Level level, String sourceClass,
+                         String sourceMethod, String msg) {
+        }
+
+        @Override
+        public default void logp(PlatformLogger.Level level, String sourceClass,
+                         String sourceMethod, Throwable thrown,
+                         Supplier<String> msgSupplier) {
+        }
+
+        static boolean requiresDefaultBodyFor(Method m) {
+            try {
+                Method m2 = LoggerBridgeMethodsWithNoBody.class
+                        .getDeclaredMethod(m.getName(),
+                        m.getParameterTypes());
+                return !m2.isDefault();
+            } catch (NoSuchMethodException x) {
+                return true;
+            }
+        }
+    }
+
+    final boolean warnDuplicateMappings;
+    public LoggerFinderAPITest(boolean verbose) {
+        this.warnDuplicateMappings = verbose;
+        for (Handler h : Logger.getLogger("").getHandlers()) {
+            if (h instanceof ConsoleHandler) {
+                Logger.getLogger("").removeHandler(h);
+            }
+        }
+        Logger.getLogger("").addHandler( new Handler() {
+            @Override
+            public void publish(LogRecord record) {
+                StringBuilder builder = new StringBuilder();
+                builder.append("GOT LogRecord: ")
+                        .append(record.getLevel().getLocalizedName())
+                        .append(": [").append(record.getLoggerName())
+                        .append("] ").append(record.getSourceClassName())
+                        .append('.')
+                        .append(record.getSourceMethodName()).append(" -> ")
+                        .append(record.getMessage())
+                        .append(' ')
+                        .append(record.getParameters() == null ? ""
+                                : Arrays.toString(record.getParameters()))
+                        ;
+                System.out.println(builder);
+                if (record.getThrown() != null) {
+                    record.getThrown().printStackTrace(System.out);
+                }
+            }
+            @Override public void flush() {}
+            @Override public void close() {}
+        });
+    }
+
+    public Stream<Method> getJulLogMethodStream(Class<?> loggerClass) {
+
+        return Stream.of(loggerClass.getMethods()).filter((x) -> {
+            final Matcher m = julLogNames.matcher(x.getName());
+            return m.matches() ? x.getAnnotation(Deprecated.class) == null : false;
+        });
+    }
+
+    /**
+     * Tells whether a method invocation of 'origin' can be transformed in a
+     * method invocation of 'target'.
+     * This method only look at the parameter signatures, it doesn't look at
+     * the name, nor does it look at the return types.
+     * <p>
+     * Example:
+     * <ul>
+     *     <li>java.util.logging.Logger.log(Level, String, Object) can be invoked as<br>
+         java.util.logging.spi.Logger.log(Level, String, Object...) because the
+         last parameter in 'target' is a varargs.</li>
+     *     <li>java.util.logging.Logger.log(Level, String) can also be invoked as<br>
+         java.util.logging.spi.Logger.log(Level, String, Object...) for the
+         same reason.</li>
+     * </ul>
+     * <p>
+     * The algorithm is tailored for our needs: when the last parameter in the
+     * target is a vararg, and when origin & target have the same number of
+     * parameters, then we consider that the types of the last parameter *must*
+     * match.
+     * <p>
+     * Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y)
+     * although strictly speaking, it should...
+     *
+     * @param origin The method in the original class
+     * @param target The correspondent candidate in the target class
+     * @return true if a method invocation of 'origin' can be transformed in a
+     * method invocation of 'target'.
+     */
+    public boolean canBeInvokedAs(Method origin, Method target,
+                                  Map<Class<?>,Class<?>> substitutes) {
+        final Class<?>[] xParams = target.getParameterTypes();
+        final Class<?>[] mParams = Stream.of(origin.getParameterTypes())
+                .map((x) -> substitutes.getOrDefault(x, x))
+                .collect(Collectors.toList()).toArray(new Class<?>[0]);
+        if (Arrays.deepEquals(xParams, mParams)) return true;
+        if (target.isVarArgs()) {
+            if (xParams.length == mParams.length) {
+                if (xParams[xParams.length-1].isArray()) {
+                    return mParams[mParams.length -1].equals(
+                            xParams[xParams.length -1].getComponentType());
+                }
+            } else if (xParams.length == mParams.length + 1) {
+                return Arrays.deepEquals(
+                        Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Look whether {@code otherClass} has a public method similar to m
+     * @param m
+     * @param otherClass
+     * @return
+     */
+    public Stream<Method> findInvokable(Method m, Class<?> otherClass) {
+        final Map<Class<?>,Class<?>> substitues =
+                Collections.singletonMap(java.util.logging.Level.class,
+                        sun.util.logging.PlatformLogger.Level.class);
+        return Stream.of(otherClass.getMethods())
+                .filter((x) -> m.getName().equals(x.getName()))
+                .filter((x) -> canBeInvokedAs(m, x, substitues));
+    }
+
+    /**
+     * Test that the concrete Logger implementation passed as parameter
+     * overrides all the methods defined by its interface.
+     * @param julLogger A concrete implementation of System.Logger
+     *    whose backend is a JUL Logger.
+     */
+    StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) {
+        final StringBuilder errors = new StringBuilder();
+        if (!bridgeLoggerClass.isInstance(julLogger)) {
+            final String errorMsg =
+                    "Logger returned by LoggerFactory.getLogger(\"foo\") is not a "
+                    + bridgeLoggerClass + "\n\t" + julLogger;
+            System.err.println(errorMsg);
+            errors.append(errorMsg).append('\n');
+        }
+        final Class<? extends java.lang.System.Logger> xClass = julLogger.getClass();
+        List<Method> notOverridden =
+                Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> {
+            try {
+                Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes());
+                return x == null;
+            } catch (NoSuchMethodException ex) {
+                return !Modifier.isStatic(m.getModifiers());
+            }
+        }).collect(Collectors.toList());
+        notOverridden.stream().filter((x) -> {
+            boolean shouldOverride = true;
+            try {
+                final Method m = xClass.getMethod(x.getName(), x.getParameterTypes());
+                Method m2 = null;
+                try {
+                    m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes());
+                } catch (Exception e) {
+
+                }
+                shouldOverride = m.isDefault() || m2 == null;
+            } catch (Exception e) {
+                // should override.
+            }
+            return shouldOverride;
+        }).forEach(x -> {
+            final String errorMsg = xClass.getName() + " should override\n\t" + x.toString();
+            System.err.println(errorMsg);
+            errors.append(errorMsg).append('\n');
+        });
+        if (notOverridden.isEmpty()) {
+            System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass);
+        }
+        return errors;
+    }
+
+    public static class ResourceBundeParam extends ResourceBundle {
+        Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());
+        @Override
+        protected Object handleGetObject(String key) {
+            map.putIfAbsent(key, "${"+key+"}");
+            return map.get(key);
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(new LinkedHashSet<>(map.keySet()));
+        }
+
+    }
+
+    final ResourceBundle bundleParam =
+            ResourceBundle.getBundle(ResourceBundeParam.class.getName());
+
+    public static class ResourceBundeLocalized extends ResourceBundle {
+        Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());
+        @Override
+        protected Object handleGetObject(String key) {
+            map.putIfAbsent(key, "Localized:${"+key+"}");
+            return map.get(key);
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(new LinkedHashSet<>(map.keySet()));
+        }
+
+    }
+
+    final static ResourceBundle bundleLocalized =
+            ResourceBundle.getBundle(ResourceBundeLocalized.class.getName());
+
+    final Map<Class<?>, Object> params = new HashMap<>();
+    {
+        params.put(String.class, "TestString");
+        params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING);
+        params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING);
+        params.put(ResourceBundle.class, bundleParam);
+        params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)"));
+        params.put(Object[].class, new Object[] {"One", "Two"});
+        params.put(Object.class, new Object() {
+            @Override public String toString() { return "I am an object!"; }
+        });
+    }
+
+    public Object[] getParamsFor(Method m) {
+        final Object[] res = new Object[m.getParameterCount()];
+        final Class<?>[] sig = m.getParameterTypes();
+        if (res.length == 0) {
+            return res;
+        }
+        for (int i=0; i<res.length; i++) {
+            Object p = params.get(sig[i]);
+            if (p == null && sig[i].equals(Supplier.class)) {
+                final String msg = "SuppliedMsg["+i+"]";
+                p = (Supplier<String>) () -> msg;
+            }
+            if (p instanceof String) {
+                res[i] = String.valueOf(p)+"["+i+"]";
+            } else {
+                res[i] = p;
+            }
+        }
+        return res;
+    }
+
+    public void invokeOn(java.lang.System.Logger logger, Method m) {
+        Object[] p = getParamsFor(m);
+        try {
+            m.invoke(logger, p);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to invoke "+m.toString(), e);
+        }
+    }
+
+    public void testAllJdkExtensionMethods(java.lang.System.Logger logger) {
+        Stream.of(jdkLoggerClass.getDeclaredMethods())
+                .filter(m -> !Modifier.isStatic(m.getModifiers()))
+                .forEach((m) -> invokeOn(logger, m));
+    }
+
+    public void testAllAPIMethods(java.lang.System.Logger logger) {
+        Stream.of(spiLoggerClass.getDeclaredMethods())
+                .filter(m -> !Modifier.isStatic(m.getModifiers()))
+                .forEach((m) -> invokeOn(logger, m));
+    }
+
+    public void testAllBridgeMethods(java.lang.System.Logger logger) {
+        Stream.of(bridgeLoggerClass.getDeclaredMethods())
+                .filter(m -> !Modifier.isStatic(m.getModifiers()))
+                .forEach((m) -> invokeOn(logger, m));
+    }
+
+    public void testAllLogProducerMethods(java.lang.System.Logger logger) {
+        Stream.of(julLogProducerClass.getDeclaredMethods())
+                .filter(m -> !Modifier.isStatic(m.getModifiers()))
+                .forEach((m) -> invokeOn(logger, m));
+    }
+
+    public StringBuilder testGetLoggerOverriddenOnSpi() {
+        final StringBuilder errors = new StringBuilder();
+        Stream.of(jdkLoggerClass.getDeclaredMethods())
+                .filter(m -> Modifier.isStatic(m.getModifiers()))
+                .filter(m -> Modifier.isPublic(m.getModifiers()))
+                .filter(m -> !m.getName().equals("getLoggerFinder"))
+                .filter(m -> {
+                    try {
+                        final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes());
+                        return x == null;
+                    } catch (NoSuchMethodException ex) {
+                        return true;
+                    }
+                }).forEach(m -> {
+                    final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString();
+                    System.err.println(errorMsg);
+                    errors.append(errorMsg).append('\n');
+                });
+        if (errors.length() == 0) {
+            System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass);
+        } else {
+            if (errors.length() > 0) throw new RuntimeException(errors.toString());
+        }
+        return errors;
+    }
+
+    public static void main(String argv[]) throws Exception {
+        final LoggerFinderAPITest test = new LoggerFinderAPITest(false);
+        final StringBuilder errors = new StringBuilder();
+        errors.append(test.testGetLoggerOverriddenOnSpi());
+        java.lang.System.Logger julLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLogger("foo", LoggerFinderAPITest.class);
+        errors.append(test.testDefaultJULLogger(julLogger));
+        if (errors.length() > 0) throw new RuntimeException(errors.toString());
+        java.lang.System.Logger julSystemLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLogger("bar", Thread.class);
+        errors.append(test.testDefaultJULLogger(julSystemLogger));
+        if (errors.length() > 0) throw new RuntimeException(errors.toString());
+        java.lang.System.Logger julLocalizedLogger =
+                (java.lang.System.Logger)
+                System.getLogger("baz", bundleLocalized);
+        java.lang.System.Logger julLocalizedSystemLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLocalizedLogger("oof", bundleLocalized, Thread.class);
+        final String error = errors.toString();
+        if (!error.isEmpty()) throw new RuntimeException(error);
+        for (java.lang.System.Logger logger : new java.lang.System.Logger[] {
+            julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger
+        }) {
+            test.testAllJdkExtensionMethods(logger);
+            test.testAllAPIMethods(logger);
+            test.testAllBridgeMethods(logger);
+            test.testAllLogProducerMethods(logger);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,2316 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug     8140364
+ * @author  danielfuchs
+ * @summary  JDK implementation specific unit test for JDK internal artifacts.
+ *           This test tests all the public API methods defined in the {@link
+ *           java.lang.System.Logger} interface, as well as all the JDK
+ *           internal methods defined in the
+ *           {@link sun.util.logging.PlatformLogger.Bridge}
+ *           interface, with loggers returned by  {@link
+ *           java.lang.System.LoggerFinder#getLogger(java.lang.String, java.lang.Class)}
+ *           and {@link java.lang.System.LoggerFinder#getLocalizedLogger(java.lang.String,
+ *           java.util.ResourceBundle, java.lang.Class)}
+ *           (using both a null resource bundle and a non null resource bundle).
+ *           It calls both the {@link java.lang.System} factory methods and
+ *           {@link jdk.internal.logger.LazyLoggers} to obtains those loggers,
+ *           and configure them with all possible known levels.
+ * @modules java.base/sun.util.logging
+ *          java.base/jdk.internal.logger
+ *          java.logging/sun.util.logging.internal
+ * @build LoggerFinderBackendTest SystemClassLoader
+ * @run  main/othervm -Djava.system.class.loader=SystemClassLoader -Dtest.logger.hidesProvider=true LoggerFinderBackendTest
+ * @run  main/othervm -Djava.system.class.loader=SystemClassLoader -Dtest.logger.hidesProvider=false LoggerFinderBackendTest
+ */
+
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.lang.System.LoggerFinder;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import sun.util.logging.PlatformLogger.Level;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import sun.util.logging.internal.LoggingProviderImpl;
+
+/**
+ * @author danielfuchs
+ */
+public class LoggerFinderBackendTest {
+
+    // whether the implementation of Logger try to do a best
+    // effort for logp... If the provider is not hidden, then
+    // the logp() implementation comes from LoggerWrapper - which does a
+    // best effort. Otherwise, it comes from the default provider
+    // which does support logp.
+    static final boolean BEST_EFFORT_FOR_LOGP =
+            !Boolean.getBoolean("test.logger.hidesProvider");
+    static final boolean VERBOSE = false;
+
+    static final Class<java.lang.System.Logger> spiLoggerClass =
+            java.lang.System.Logger.class;
+    static final Class<java.lang.System.Logger> jdkLoggerClass =
+            java.lang.System.Logger.class;
+    static final Class<sun.util.logging.PlatformLogger.Bridge> bridgeLoggerClass =
+            sun.util.logging.PlatformLogger.Bridge.class;
+
+    /** Use to retrieve the log records that were produced by the JUL backend */
+    static class LoggerTesterHandler extends Handler {
+        public final List<LogRecord> records =
+                Collections.synchronizedList(new ArrayList<>());
+
+        @Override
+        public void publish(LogRecord record) {
+            record.getSourceClassName(); record.getSourceMethodName();
+            records.add(record);
+        }
+
+        @Override
+        public void flush() {
+        }
+
+        @Override
+        public void close() throws SecurityException {
+            records.clear();
+        }
+
+        public void reset() {
+            records.clear();
+        }
+    }
+
+    /** The {@link LoggerTesterHandler} handler is added to the root logger. */
+    static final LoggerTesterHandler handler = new LoggerTesterHandler();
+    static {
+        for (Handler h : Logger.getLogger("").getHandlers()) {
+            if (h instanceof ConsoleHandler) {
+                Logger.getLogger("").removeHandler(h);
+            }
+        }
+        Logger.getLogger("").addHandler(handler);
+    }
+
+    /**
+     * A resource handler parameter that will be used when calling out the
+     * logrb-like methods - as well as when calling the level-specific
+     * methods that take a ResourceBundle parameter.
+     */
+    public static class ResourceBundeParam extends ResourceBundle {
+        Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());
+        @Override
+        protected Object handleGetObject(String key) {
+            map.putIfAbsent(key, "${"+key+"}");
+            return map.get(key);
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(new LinkedHashSet<>(map.keySet()));
+        }
+
+    }
+
+    final static ResourceBundle bundleParam =
+            ResourceBundle.getBundle(ResourceBundeParam.class.getName());
+
+    /**
+     * A resource handler parameter that will be used when creating localized
+     * loggers by calling {@link
+     * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)}.
+     */
+    public static class ResourceBundeLocalized extends ResourceBundle {
+        Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());
+        @Override
+        protected Object handleGetObject(String key) {
+            map.putIfAbsent(key, "Localized:${"+key+"}");
+            return map.get(key);
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(new LinkedHashSet<>(map.keySet()));
+        }
+
+    }
+
+    /**
+     * The Levels enum is used to call all the level-specific methods on
+     * a logger instance. To minimize the amount of code it uses reflection
+     * to do so.
+     */
+    static Lookup lookup = MethodHandles.lookup();
+    public enum Levels {
+        /** Used to call all forms of Logger.log?(SEVERE, ...) */
+        SEVERE("severe", bridgeLoggerClass, Level.SEVERE, null, "error", false),
+        /** Used to call all forms of Logger.log?(WARNING,...) */
+        WARNING("warning", bridgeLoggerClass, Level.WARNING, "warning", "warning", false),
+        /** Used to call all forms of Logger.log?(INFO,...) */
+        INFO("info", bridgeLoggerClass, Level.INFO, "info", "info", false),
+        /** Used to call all forms of Logger.log?(CONFIG,...) */
+        CONFIG("config", bridgeLoggerClass, Level.CONFIG, null, "debug", false),
+        /** Used to call all forms of Logger.log?(FINE,...) */
+        FINE("fine", bridgeLoggerClass, Level.FINE, null, "debug", false),
+        /** Used to call all forms of Logger.log?(FINER,...) */
+        FINER("finer", bridgeLoggerClass, Level.FINER, null, "trace", false),
+        /** Used to call all forms of Logger.log?(FINEST,...) */
+        FINEST("finest", bridgeLoggerClass, Level.FINEST, null, "trace", false),
+        ;
+        public final String method;  // The name of the level-specific method to call
+        public final Class<?> definingClass; // which interface j.u.logger.Logger or j.u.logging.spi.Logger defines it
+        public final Level platformLevel; // The platform Level it will be mapped to in Jul when Jul is the backend
+        public final String jdkExtensionToJUL; // The name of the method called on the JUL logger when JUL is the backend
+        public final String julToJdkExtension; // The name of the method called in the jdk extension by the default impl in jdk.internal.logging.Logger
+        public final String enableMethod; // The name of the isXxxxEnabled method
+        public final boolean hasSpecificIsEnabled;
+        Levels(String method, Class<?> definingClass, Level defaultMapping,
+                String jdkExtensionToJUL, String julToJdkExtension,
+                boolean hasSpecificIsEnabled) {
+            this.method = method;
+            this.definingClass = definingClass;
+            this.platformLevel = defaultMapping;
+            this.jdkExtensionToJUL = jdkExtensionToJUL;
+            this.julToJdkExtension = julToJdkExtension;
+            this.hasSpecificIsEnabled = hasSpecificIsEnabled;
+            if (hasSpecificIsEnabled) {
+                this.enableMethod = "is" + method.substring(0,1).toUpperCase()
+                    + method.substring(1) + "Enabled";
+            } else {
+                this.enableMethod = "isLoggable";
+            }
+        }
+
+        /*
+         * calls this level specific method - e.g. if this==INFO: logger.info(msg);
+         */
+        public void level(Object logger, String msg) {
+            MethodType mt = MethodType.methodType(void.class, Level.class, String.class);
+            invoke("log", logger, mt, platformLevel, msg);
+        }
+
+        /*
+         * calls this level specific method - e.g. if this==INFO: logger.info(msgSupplier);
+         */
+        public void level(Object logger, Supplier<String> msgSupplier) {
+            MethodType mt = MethodType.methodType(void.class,  Level.class, Supplier.class);
+            invoke("log", logger, mt, platformLevel, msgSupplier);
+        }
+
+        /*
+         * calls this level specific method - e.g. if this==INFO: logger.info(msg, params);
+         */
+        public void level(Object logger, String msg, Object... params) {
+            MethodType mt = MethodType.methodType(void.class,  Level.class, String.class,
+                    Object[].class);
+            invoke("log", logger, mt, platformLevel, msg, params);
+        }
+
+        /*
+         * calls this level specific method - e.g. if this==INFO: logger.info(msg, thrown);
+         */
+        public void level(Object logger, String msg, Throwable thrown) {
+            MethodType mt = MethodType.methodType(void.class,  Level.class, String.class,
+                    Throwable.class);
+            invoke("log", logger, mt, platformLevel, msg, thrown);
+        }
+
+        /*
+         * calls this level specific method - e.g. if this==INFO: logger.info(msgSupplier, thrown);
+         */
+        public void level(Object logger, Supplier<String> msgSupplier, Throwable thrown) {
+            MethodType mt = MethodType.methodType(void.class,  Level.class,
+                     Throwable.class, Supplier.class);
+            invoke("log", logger, mt, platformLevel, thrown, msgSupplier);
+        }
+
+        /*
+         * calls this level specific method - e.g. if this==INFO: logger.info(bundle, msg);
+         */
+        public void level(Object logger, String msg, ResourceBundle bundle) {
+            MethodType mt = MethodType.methodType(void.class, Level.class,
+                    ResourceBundle.class, String.class, Object[].class);
+            invoke("logrb", logger, mt, platformLevel, bundle, msg, null);
+        }
+
+        public void level(Object logger, String msg, ResourceBundle bundle,
+                Object... params) {
+            MethodType mt = MethodType.methodType(void.class, Level.class,
+                    ResourceBundle.class, String.class, Object[].class);
+            invoke("logrb", logger, mt, platformLevel, bundle, msg, params);
+        }
+
+        public void level(Object logger, String msg, ResourceBundle bundle,
+                Throwable thrown) {
+            MethodType mt = MethodType.methodType(void.class, Level.class,
+                    ResourceBundle.class, String.class, Throwable.class);
+            invoke("logrb", logger, mt, platformLevel, bundle, msg, thrown);
+        }
+
+        public boolean isEnabled(Object logger) {
+            try {
+                if (hasSpecificIsEnabled) {
+                    MethodType mt = MethodType.methodType(boolean.class);
+                    final MethodHandle handle = lookup.findVirtual(definingClass,
+                        enableMethod, mt).bindTo(logger);
+                    return Boolean.class.cast(handle.invoke());
+                } else {
+                    MethodType mt = MethodType.methodType(boolean.class,
+                        Level.class);
+                    final MethodHandle handle = lookup.findVirtual(definingClass,
+                        enableMethod, mt).bindTo(logger);
+                    return Boolean.class.cast(handle.invoke(platformLevel));
+                }
+            } catch (Throwable ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        private void invoke(String method, Object logger, MethodType mt, Object... args) {
+            try {
+                final int last = mt.parameterCount()-1;
+                boolean isVarargs = mt.parameterType(last).isArray();
+                final MethodHandle handle = lookup.findVirtual(definingClass,
+                        method, mt).bindTo(logger);
+
+                final StringBuilder builder = new StringBuilder();
+                builder.append(logger.getClass().getSimpleName()).append('.')
+                        .append(method).append('(');
+                String sep = "";
+                int offset = 0;
+                Object[] params = args;
+                for (int i=0; (i-offset) < params.length; i++) {
+                    if (isVarargs && i == last) {
+                        offset = last;
+                        params = (Object[])args[i];
+                        if (params == null) break;
+                    }
+                    Object p = params[i - offset];
+                    String quote = (p instanceof String) ? "\"" : "";
+                    builder.append(sep).append(quote).append(p).append(quote);
+                    sep = ", ";
+                }
+                builder.append(')');
+                if (verbose) {
+                    System.out.println(builder);
+                }
+                handle.invokeWithArguments(args);
+            } catch (Throwable ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+    };
+
+    static interface Checker<LogResult, L> extends BiFunction<LogResult, L, Void> {}
+    static interface JdkLogTester
+            extends BiFunction<sun.util.logging.PlatformLogger.Bridge, Level, Void> {}
+    static interface SpiLogTester
+            extends BiFunction<java.lang.System.Logger, java.lang.System.Logger.Level, Void> {}
+
+    static interface MethodInvoker<LOGGER, LEVEL> {
+        public void logX(LOGGER logger, LEVEL level, Object... args);
+    }
+
+    public enum JdkLogMethodInvoker
+           implements MethodInvoker<sun.util.logging.PlatformLogger.Bridge, Level> {
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, String, Object...)};
+         **/
+        LOG_STRING_PARAMS("log", MethodType.methodType(void.class,
+                Level.class, String.class, Object[].class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, String, Throwable)};
+         **/
+        LOG_STRING_THROWN("log", MethodType.methodType(void.class,
+                Level.class, String.class, Throwable.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, Supplier<String>)};
+         **/
+        LOG_SUPPLIER("log", MethodType.methodType(void.class,
+                Level.class, Supplier.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, Throwable, Supplier<String>)};
+         **/
+        LOG_SUPPLIER_THROWN("log", MethodType.methodType(void.class,
+                Level.class, Throwable.class, Supplier.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logp(Level, String, String, String)};
+         **/
+        LOGP_STRING("logp", MethodType.methodType(void.class,
+                Level.class, String.class, String.class, String.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logp(Level, String, String, String, Object...)};
+         **/
+        LOGP_STRING_PARAMS("logp", MethodType.methodType(void.class,
+                Level.class, String.class, String.class, String.class, Object[].class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logp(Level, String, String, String, Throwable)};
+         **/
+        LOGP_STRING_THROWN("logp", MethodType.methodType(void.class,
+                Level.class, String.class, String.class, String.class, Throwable.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logp(Level, String, String, Supplier<String>)};
+         **/
+        LOGP_SUPPLIER("logp", MethodType.methodType(void.class,
+                Level.class, String.class, String.class, Supplier.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logp(Level, String, String, Throwable, Supplier<String>)};
+         **/
+        LOGP_SUPPLIER_THROWN("logp", MethodType.methodType(void.class,
+                Level.class, String.class, String.class,
+                Throwable.class, Supplier.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Object...)};
+         **/
+        LOGRB_STRING_PARAMS("logrb", MethodType.methodType(void.class,
+                Level.class, ResourceBundle.class, String.class, Object[].class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Throwable)};
+         **/
+        LOGRB_STRING_THROWN("logrb", MethodType.methodType(void.class,
+                Level.class, ResourceBundle.class, String.class, Throwable.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logrb(Level, String, String, ResourceBundle, String, Object...)};
+         **/
+        LOGRBP_STRING_PARAMS("logrb", MethodType.methodType(void.class,
+                Level.class, String.class, String.class, ResourceBundle.class,
+                String.class, Object[].class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logrb(Level, String, String, ResourceBundle, String, Throwable)};
+         **/
+        LOGRBP_STRING_THROWN("logrb", MethodType.methodType(void.class,
+                Level.class, String.class, String.class, ResourceBundle.class,
+                String.class, Throwable.class)),
+        ;
+        final MethodType mt;
+        final String method;
+        JdkLogMethodInvoker(String method, MethodType mt) {
+            this.mt = mt;
+            this.method = method;
+        }
+        Object[] makeArgs(Level level, Object... rest) {
+            List<Object> list = new ArrayList<>(rest == null ? 1 : rest.length + 1);
+            list.add(level);
+            if (rest != null) {
+                list.addAll(Arrays.asList(rest));
+            }
+            return list.toArray(new Object[list.size()]);
+        }
+
+        @Override
+        public void logX(sun.util.logging.PlatformLogger.Bridge logger, Level level, Object... args) {
+            try {
+                MethodHandle handle = lookup.findVirtual(bridgeLoggerClass,
+                        method, mt).bindTo(logger);
+                final int last = mt.parameterCount()-1;
+                boolean isVarargs = mt.parameterType(last).isArray();
+
+                args = makeArgs(level, args);
+
+                final StringBuilder builder = new StringBuilder();
+                builder.append(logger.getClass().getSimpleName()).append('.')
+                        .append(this.method).append('(');
+                String sep = "";
+                int offset = 0;
+                Object[] params = args;
+                for (int i=0; (i-offset) < params.length; i++) {
+                    if (isVarargs && i == last) {
+                        offset = last;
+                        params = (Object[])args[i];
+                        if (params == null) break;
+                    }
+                    Object p = params[i - offset];
+                    String quote = (p instanceof String) ? "\"" : "";
+                    p = p instanceof Level ? "Level."+p : p;
+                    builder.append(sep).append(quote).append(p).append(quote);
+                    sep = ", ";
+                }
+                builder.append(')');
+                if (verbose) System.out.println(builder);
+                handle.invokeWithArguments(args);
+            } catch (Throwable ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+
+    public enum SpiLogMethodInvoker implements MethodInvoker<java.lang.System.Logger,
+            java.lang.System.Logger.Level> {
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, String, Object...)};
+         **/
+        LOG_STRING_PARAMS("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, String.class, Object[].class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, String, Throwable)};
+         **/
+        LOG_STRING_THROWN("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, String.class, Throwable.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, Supplier<String>)};
+         **/
+        LOG_SUPPLIER("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, Supplier.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, Throwable, Supplier<String>)};
+         **/
+        LOG_SUPPLIER_THROWN("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, Supplier.class, Throwable.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#log(Level, Supplier<String>)};
+         **/
+        LOG_OBJECT("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, Object.class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Object...)};
+         **/
+        LOGRB_STRING_PARAMS("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, ResourceBundle.class,
+                String.class, Object[].class)),
+        /**
+         * Tests {@link
+         * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Throwable)};
+         **/
+        LOGRB_STRING_THROWN("log", MethodType.methodType(void.class,
+                java.lang.System.Logger.Level.class, ResourceBundle.class,
+                String.class, Throwable.class)),
+        ;
+        final MethodType mt;
+        final String method;
+        SpiLogMethodInvoker(String method, MethodType mt) {
+            this.mt = mt;
+            this.method = method;
+        }
+        Object[] makeArgs(java.lang.System.Logger.Level level, Object... rest) {
+            List<Object> list = new ArrayList<>(rest == null ? 1 : rest.length + 1);
+            list.add(level);
+            if (rest != null) {
+                list.addAll(Arrays.asList(rest));
+            }
+            return list.toArray(new Object[list.size()]);
+        }
+
+        @Override
+        public void logX(java.lang.System.Logger logger,
+                java.lang.System.Logger.Level level, Object... args) {
+            try {
+                MethodHandle handle = lookup.findVirtual(spiLoggerClass,
+                        method, mt).bindTo(logger);
+                final int last = mt.parameterCount()-1;
+                boolean isVarargs = mt.parameterType(last).isArray();
+
+                args = makeArgs(level, args);
+
+                final StringBuilder builder = new StringBuilder();
+                builder.append(logger.getClass().getSimpleName()).append('.')
+                        .append(this.method).append('(');
+                String sep = "";
+                int offset = 0;
+                Object[] params = args;
+                for (int i=0; (i-offset) < params.length; i++) {
+                    if (isVarargs && i == last) {
+                        offset = last;
+                        params = (Object[])args[i];
+                        if (params == null) break;
+                    }
+                    Object p = params[i - offset];
+                    String quote = (p instanceof String) ? "\"" : "";
+                    p = p instanceof Level ? "Level."+p : p;
+                    builder.append(sep).append(quote).append(p).append(quote);
+                    sep = ", ";
+                }
+                builder.append(')');
+                if (verbose) System.out.println(builder);
+                handle.invokeWithArguments(args);
+            } catch (Throwable ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+
+    public abstract static class BackendTester<BackendRecord> {
+        static final Level[] levelMap = {Level.ALL, Level.FINER, Level.FINE,
+            Level.INFO, Level.WARNING, Level.SEVERE, Level.OFF};
+
+        abstract class BackendAdaptor {
+            public abstract String getLoggerName(BackendRecord res);
+            public abstract Object getLevel(BackendRecord res);
+            public abstract String getMessage(BackendRecord res);
+            public abstract String getSourceClassName(BackendRecord res);
+            public abstract String getSourceMethodName(BackendRecord res);
+            public abstract Throwable getThrown(BackendRecord res);
+            public abstract ResourceBundle getResourceBundle(BackendRecord res);
+            public abstract void setLevel(java.lang.System.Logger logger,
+                    Level level);
+            public abstract void setLevel(java.lang.System.Logger logger,
+                    java.lang.System.Logger.Level level);
+            public abstract List<BackendRecord> getBackendRecords();
+            public abstract void resetBackendRecords();
+            public boolean shouldBeLoggable(Levels level, Level loggerLevel) {
+                final Level logLevel = level.platformLevel;
+                return shouldBeLoggable(logLevel, loggerLevel);
+            }
+            public boolean shouldBeLoggable(Level logLevel, Level loggerLevel) {
+                return loggerLevel.intValue() != Level.OFF.intValue()
+                        && logLevel.intValue() >= loggerLevel.intValue();
+            }
+            public boolean shouldBeLoggable(java.lang.System.Logger.Level logLevel,
+                    java.lang.System.Logger.Level loggerLevel) {
+                return loggerLevel != java.lang.System.Logger.Level.OFF
+                        && logLevel.ordinal() >= loggerLevel.ordinal();
+            }
+            public boolean isLoggable(java.lang.System.Logger logger, Level l) {
+                return bridgeLoggerClass.cast(logger).isLoggable(l);
+            }
+            public String getCallerClassName(Levels level, String clazz) {
+                return clazz != null ? clazz : Levels.class.getName();
+            }
+            public String getCallerClassName(MethodInvoker<?,?> logMethod,
+                   String clazz) {
+                return clazz != null ? clazz : logMethod.getClass().getName();
+            }
+            public String getCallerMethodName(Levels level, String method) {
+                return method != null ? method : "invoke";
+            }
+            public String getCallerMethodName(MethodInvoker<?,?> logMethod,
+                    String method) {
+                return method != null ? method : "logX";
+            }
+            public Object getMappedLevel(Object level) {
+                return level;
+            }
+
+            public Level toJUL(java.lang.System.Logger.Level level) {
+                return levelMap[level.ordinal()];
+            }
+        }
+
+        public final boolean isSystem;
+        public final Class<? extends java.lang.System.Logger> restrictedTo;
+        public final ResourceBundle localized;
+        public BackendTester(boolean isSystem) {
+            this(isSystem,null,null);
+        }
+        public BackendTester(boolean isSystem, ResourceBundle localized) {
+            this(isSystem,null,localized);
+        }
+        public BackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo) {
+            this(isSystem, restrictedTo, null);
+        }
+        public BackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo,
+                ResourceBundle localized) {
+            this.isSystem = isSystem;
+            this.restrictedTo = restrictedTo;
+            this.localized = localized;
+        }
+
+        public java.lang.System.Logger convert(java.lang.System.Logger logger) {
+            return logger;
+        }
+
+        public static Level[] LEVELS = {
+            Level.OFF,
+            Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG,
+            Level.FINE, Level.FINER, Level.FINEST,
+            Level.ALL
+        };
+
+        abstract BackendAdaptor adaptor();
+
+        protected void checkRecord(Levels test, BackendRecord res, String loggerName,
+                Level level, String msg, String className, String methodName,
+                Throwable thrown, ResourceBundle bundle, Object... params) {
+            checkRecord(test, res, loggerName, level, ()->msg, className,
+                    methodName, thrown, bundle, params);
+
+        }
+        protected void checkRecord(Levels test, BackendRecord res, String loggerName,
+                Level level, Supplier<String> msg, String className, String methodName,
+                Throwable thrown, ResourceBundle bundle, Object... params) {
+            checkRecord(test.method, res, loggerName, level, msg,
+                    className, methodName, thrown, bundle, params);
+        }
+        protected <L> void checkRecord(String logMethod, BackendRecord res, String loggerName,
+                L level, Supplier<String> msg, String className, String methodName,
+                Throwable thrown, ResourceBundle bundle, Object... params) {
+            final BackendAdaptor analyzer = adaptor();
+            if (! Objects.equals(analyzer.getLoggerName(res), loggerName)) {
+                throw new RuntimeException(logMethod+": expected logger name "
+                        + loggerName + " got " + analyzer.getLoggerName(res));
+            }
+            if (!Objects.equals(analyzer.getLevel(res), analyzer.getMappedLevel(level))) {
+                throw new RuntimeException(logMethod+": expected level "
+                        + analyzer.getMappedLevel(level) + " got " + analyzer.getLevel(res));
+            }
+            if (!Objects.equals(analyzer.getMessage(res), msg.get())) {
+                throw new RuntimeException(logMethod+": expected message \""
+                        + msg.get() + "\" got \"" + analyzer.getMessage(res) +"\"");
+            }
+            if (!Objects.equals(analyzer.getSourceClassName(res), className)) {
+                throw new RuntimeException(logMethod
+                        + ": expected class name \"" + className
+                        + "\" got \"" + analyzer.getSourceClassName(res) +"\"");
+            }
+            if (!Objects.equals(analyzer.getSourceMethodName(res), methodName)) {
+                throw new RuntimeException(logMethod
+                        + ": expected method name \"" + methodName
+                        + "\" got \"" + analyzer.getSourceMethodName(res) +"\"");
+            }
+            final Throwable thrownRes = analyzer.getThrown(res);
+            if (!Objects.equals(thrownRes, thrown)) {
+                throw new RuntimeException(logMethod
+                        + ": expected throwable \"" + thrown
+                        + "\" got \"" + thrownRes + "\"");
+            }
+            if (!Objects.equals(analyzer.getResourceBundle(res), bundle)) {
+                throw new RuntimeException(logMethod
+                        + ": expected bundle \"" + bundle
+                        + "\" got \"" + analyzer.getResourceBundle(res) +"\"");
+            }
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                String msg) {
+            Runnable test = () -> level.level(logger, msg);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            null, localized);
+                return null;
+            };
+            test("msg", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                String msg, Object... params) {
+            Runnable test = () -> level.level(logger, msg, (Object[])params);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            null, localized, (Object[])params);
+                return null;
+            };
+            test("msg, params", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                String msg, Throwable thrown) {
+            Runnable test = () -> level.level(logger, msg, thrown);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            thrown, localized);
+                return null;
+            };
+            test("msg, thrown", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                Supplier<String> msg) {
+            Runnable test = () -> level.level(logger, msg);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            null, null);
+                return null;
+            };
+            test("msgSupplier", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                Supplier<String> msg, Throwable thrown) {
+            Runnable test = () -> level.level(logger, msg, thrown);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            thrown, null);
+                return null;
+            };
+            test("throw, msgSupplier", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                String msg, ResourceBundle bundle) {
+            Runnable test = () -> level.level(logger, msg, bundle);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            null, bundle);
+                return null;
+            };
+            test("bundle, msg", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                String msg, ResourceBundle bundle, Object... params) {
+            Runnable test = () -> level.level(logger, msg, bundle, (Object[])params);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            null, bundle, (Object[])params);
+                return null;
+            };
+            test("bundle, msg, params", level, logger, test, check);
+        }
+
+        public void testLevel(Levels level, java.lang.System.Logger logger,
+                String msg, ResourceBundle bundle, Throwable thrown) {
+            Runnable test = () -> level.level(logger, msg, bundle, thrown);
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord(level, res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(level, Levels.class.getName()),
+                            adaptor().getCallerMethodName(level, "invoke"),
+                            thrown, bundle);
+                return null;
+            };
+            test("bundle, msg, throwable", level, logger, test, check);
+        }
+
+        // System.Logger
+        public void testSpiLog(java.lang.System.Logger logger, String msg) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOG_STRING_PARAMS,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOG_STRING_PARAMS,
+                                    "logX"), null, localized);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, (Object[])null);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\")";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger,
+                ResourceBundle bundle, String msg) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    "logX"), null, bundle);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, (Object[])null);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l
+                    + ", bundle, \"" + msg + "\")";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger, String msg, Object... params) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOG_STRING_PARAMS,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOG_STRING_PARAMS,
+                                    "logX"), null, localized, params);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, params);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\", params...)";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger,
+                ResourceBundle bundle, String msg, Object... params) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    "logX"), null, bundle, params);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, params);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l
+                    + ", bundle, \"" + msg + "\", params...)";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger, String msg, Throwable thrown) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                           adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOG_STRING_THROWN,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOG_STRING_THROWN,
+                                    "logX"), thrown, localized);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOG_STRING_THROWN.logX(x, level, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", \"" + msg + "\", thrown)";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger,
+                ResourceBundle bundle, String msg, Throwable thrown) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOGRB_STRING_THROWN,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOGRB_STRING_THROWN,
+                                    "logX"), thrown, bundle);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOGRB_STRING_THROWN.logX(x, level, bundle, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", bundle, \"" + msg + "\", thrown)";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger, Supplier<String> msg) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOG_SUPPLIER,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOG_SUPPLIER,
+                                    "logX"), null, null);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOG_SUPPLIER.logX(x, level, msg);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", () -> \"" + msg.get() + "\")";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger, Object obj) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> obj.toString(),
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOG_OBJECT,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOG_OBJECT,
+                                    "logX"), null, null);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOG_OBJECT.logX(x, level, obj);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", new "+obj.getClass().getSimpleName()+"(\""
+                            + obj.toString() + "\"))";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+        public void testSpiLog(java.lang.System.Logger logger, Throwable thrown, Supplier<String> msg) {
+            Checker<BackendRecord, java.lang.System.Logger.Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(
+                                    SpiLogMethodInvoker.LOG_SUPPLIER_THROWN,
+                                    SpiLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    SpiLogMethodInvoker.LOG_SUPPLIER_THROWN,
+                                    "logX"), thrown, null);
+                return null;
+            };
+            SpiLogTester tester = (x, level) -> {
+                SpiLogMethodInvoker.LOG_SUPPLIER_THROWN.logX(x, level, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", () -> \"" + msg.get() + "\", thrown)";
+            testSpiLog(logger, tester, check, nameProducer);
+        }
+
+
+        // JDK
+
+        public void testLog(java.lang.System.Logger logger, String msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOG_STRING_PARAMS,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOG_STRING_PARAMS,
+                                    "logX"), null, localized);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, (Object[])null);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\")";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogrb(java.lang.System.Logger logger,
+                ResourceBundle bundle, String msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    "logX"), null, bundle);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, (Object[])null);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "logrb(Level." + l
+                    + ", bundle, \"" + msg + "\")";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLog(java.lang.System.Logger logger, String msg, Object... params) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOG_STRING_PARAMS,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOG_STRING_PARAMS,
+                                    "logX"), null, localized, params);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, params);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\", params...)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogrb(java.lang.System.Logger logger,
+                ResourceBundle bundle, String msg, Object... params) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOGRB_STRING_PARAMS,
+                                    "logX"), null, bundle, params);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, params);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) -> "log(Level." + l
+                    + ", bundle, \"" + msg + "\", params...)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLog(java.lang.System.Logger logger, String msg, Throwable thrown) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                           adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOG_STRING_THROWN,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOG_STRING_THROWN,
+                                    "logX"), thrown, localized);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOG_STRING_THROWN.logX(x, level, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", \"" + msg + "\", thrown)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogrb(java.lang.System.Logger logger,
+                ResourceBundle bundle, String msg, Throwable thrown) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRB_STRING_THROWN,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOGRB_STRING_THROWN,
+                                    "logX"), thrown, bundle);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGRB_STRING_THROWN.logX(x, level, bundle, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", bundle, \"" + msg + "\", thrown)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLog(java.lang.System.Logger logger, Supplier<String> msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOG_SUPPLIER,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOG_SUPPLIER,
+                                    "logX"), null, null);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOG_SUPPLIER.logX(x, level, msg);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", () -> \"" + msg.get() + "\")";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLog(java.lang.System.Logger logger, Throwable thrown, Supplier<String> msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOG_SUPPLIER_THROWN,
+                                    JdkLogMethodInvoker.class.getName()),
+                            adaptor().getCallerMethodName(
+                                    JdkLogMethodInvoker.LOG_SUPPLIER_THROWN,
+                                    "logX"), thrown, null);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOG_SUPPLIER_THROWN.logX(x, level, thrown, msg);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", () -> \"" + msg.get() + "\", thrown)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        static Supplier<String> logpMessage(ResourceBundle bundle,
+                String className, String methodName, Supplier<String> msg) {
+            if (BEST_EFFORT_FOR_LOGP && bundle == null
+                    && (className != null || methodName != null)) {
+                final String cName = className == null ? "" :  className;
+                final String mName = methodName == null ? "" : methodName;
+                return () -> {
+                    String m = msg.get();
+                    return String.format("[%s %s] %s", cName, mName, m == null ? "" : m);
+                };
+            } else {
+                return msg;
+            }
+        }
+
+        public void testLogp(java.lang.System.Logger logger, String className,
+                String methodName, String msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("logp", res, logger.getName(), l,
+                            logpMessage(localized, className, methodName, () -> msg),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_STRING, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_STRING, methodName),
+                            null, localized);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGP_STRING.logX(x, level,
+                        className, methodName, msg);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "logp(Level." + l + ", class, method, \"" + msg + "\")";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogrb(java.lang.System.Logger logger, String className,
+                String methodName, ResourceBundle bundle, String msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("logp", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, methodName),
+                            null, bundle);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGRBP_STRING_PARAMS.logX(x, level,
+                        className, methodName, bundle, msg, (Object[])null);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "logp(Level." + l + ", class, method, bundle, \"" + msg + "\")";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogp(java.lang.System.Logger logger, String className,
+                String methodName, String msg, Object... params) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("logp", res, logger.getName(), l,
+                            logpMessage(localized, className, methodName, () -> msg),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_STRING_PARAMS, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_STRING_PARAMS, methodName),
+                            null, localized, params);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGP_STRING_PARAMS.logX(x, level,
+                        className, methodName, msg, params);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", class, method, \"" + msg + "\", params...)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogrb(java.lang.System.Logger logger, String className,
+                String methodName, ResourceBundle bundle, String msg,
+                Object... params) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("logp", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, methodName),
+                            null, bundle, params);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGRBP_STRING_PARAMS.logX(x, level,
+                        className, methodName, bundle, msg, params);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", class, method, bundle, \""
+                            + msg + "\", params...)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogp(java.lang.System.Logger logger, String className,
+                String methodName, String msg, Throwable thrown) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l,
+                            logpMessage(localized, className, methodName, () -> msg),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_STRING_THROWN, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_STRING_THROWN, methodName),
+                            thrown, localized);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGP_STRING_THROWN.logX(x, level,
+                        className, methodName, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", class, method, \"" + msg + "\", thrown)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogrb(java.lang.System.Logger logger, String className,
+                String methodName, ResourceBundle bundle,
+                String msg, Throwable thrown) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l, () -> msg,
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRBP_STRING_THROWN, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGRBP_STRING_THROWN, methodName),
+                            thrown, bundle);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGRBP_STRING_THROWN.logX(x, level,
+                        className, methodName, bundle, msg, thrown);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", class, method, bundle, \"" + msg + "\", thrown)";
+            testJdkLog(logger, tester, check, nameProducer);
+
+        }
+
+        public void testLogp(java.lang.System.Logger logger, String className,
+                String methodName, Supplier<String> msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l,
+                            logpMessage(null, className, methodName, msg),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_SUPPLIER, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_SUPPLIER, methodName),
+                            null, null);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGP_SUPPLIER.logX(x, level,
+                        className, methodName, msg);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", class, method, () -> \"" + msg.get() + "\")";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        public void testLogp(java.lang.System.Logger logger, String className,
+                String methodName, Throwable thrown, Supplier<String> msg) {
+            Checker<BackendRecord, Level> check = (res, l) -> {
+                checkRecord("log", res, logger.getName(), l,
+                            logpMessage(null, className, methodName, msg),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN, className),
+                            adaptor().getCallerClassName(
+                                    JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN, methodName),
+                            thrown, null);
+                return null;
+            };
+            JdkLogTester tester = (x, level) -> {
+                JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN.logX(x, level,
+                        className, methodName, thrown, msg);
+                return null;
+            };
+            Function<String, String> nameProducer = (l) ->
+                    "log(Level." + l + ", class, method, () -> \"" + msg.get() + "\", thrown)";
+            testJdkLog(logger, tester, check, nameProducer);
+        }
+
+        private void testJdkLog(java.lang.System.Logger logger,
+                JdkLogTester log, Checker<BackendRecord,Level> check,
+                Function<String, String> nameProducer) {
+            if (restrictedTo != null) {
+                if (!bridgeLoggerClass.isAssignableFrom(restrictedTo)) {
+                    if (VERBOSE) {
+                        System.out.println("Skipping method from "
+                            + bridgeLoggerClass);
+                    }
+                    return;
+                }
+            }
+            System.out.println("Testing Logger." + nameProducer.apply("*")
+                     + " on " + logger);
+            final BackendAdaptor adaptor = adaptor();
+            for (Level loggerLevel : LEVELS) {
+                adaptor.setLevel(logger, loggerLevel);
+                for (Level l : LEVELS) {
+                    check(logger, () -> log.apply(bridgeLoggerClass.cast(logger), l),
+                          check, () -> adaptor.isLoggable(logger, l),
+                          () -> adaptor.shouldBeLoggable(l, loggerLevel),
+                          l, loggerLevel, nameProducer.apply(l.toString()));
+                }
+            }
+        }
+
+        private void testSpiLog(java.lang.System.Logger logger,
+                SpiLogTester log, Checker<BackendRecord, java.lang.System.Logger.Level> check,
+                Function<String, String> nameProducer) {
+            System.out.println("Testing System.Logger." + nameProducer.apply("*")
+                     + " on " + logger);
+            final BackendAdaptor adaptor = adaptor();
+            for (java.lang.System.Logger.Level loggerLevel : java.lang.System.Logger.Level.values()) {
+
+                adaptor.setLevel(logger, loggerLevel);
+                for (java.lang.System.Logger.Level l : java.lang.System.Logger.Level.values()) {
+                    check(logger, () -> log.apply(logger, l),
+                          check, () -> logger.isLoggable(l),
+                          () -> adaptor.shouldBeLoggable(l, loggerLevel),
+                          l, loggerLevel, nameProducer.apply(l.toString()));
+                }
+            }
+        }
+
+        private void test(String args, Levels level, java.lang.System.Logger logger,
+                Runnable test, Checker<BackendRecord, Level> check) {
+            if (restrictedTo != null) {
+                if (!level.definingClass.isAssignableFrom(restrictedTo)) {
+                    if (VERBOSE) {
+                        System.out.println("Skipping method from "
+                            + level.definingClass);
+                    }
+                    return;
+                }
+            }
+            String method = args.contains("bundle") ? "logrb" : "log";
+            System.out.println("Testing Logger."
+                    + method + "(Level." + level.platformLevel
+                    + ", "+ args + ")" + " on " + logger);
+            final BackendAdaptor adaptor = adaptor();
+            for (Level loggerLevel : LEVELS) {
+                adaptor.setLevel(logger, loggerLevel);
+                check(logger, test, check,
+                        () -> level.isEnabled(logger),
+                        () -> adaptor.shouldBeLoggable(level, loggerLevel),
+                        level.platformLevel, loggerLevel, level.method);
+            }
+        }
+
+        private <L> void check(java.lang.System.Logger logger,
+                Runnable test, Checker<BackendRecord,L> check,
+                BooleanSupplier checkLevelEnabled,
+                BooleanSupplier shouldBeLoggable,
+                L logLevel, L loggerLevel, String logMethod) {
+            final BackendAdaptor adaptor = adaptor();
+            adaptor.resetBackendRecords();
+            test.run();
+            final List<BackendRecord> records = adaptor.getBackendRecords();
+            if (shouldBeLoggable.getAsBoolean()) {
+                if (!checkLevelEnabled.getAsBoolean()) {
+                    throw new RuntimeException("Logger is not enabled for "
+                            + logMethod
+                            + " although logger level is " + loggerLevel);
+                }
+                if (records.size() != 1) {
+                    throw new RuntimeException(loggerLevel + " [" +
+                            logLevel + "] : Unexpected record sizes: "
+                        + records.toString());
+                }
+                BackendRecord res = records.get(0);
+                check.apply(res, logLevel);
+            } else {
+                if (checkLevelEnabled.getAsBoolean()) {
+                    throw new RuntimeException("Logger is enabled for "
+                            + logMethod
+                            + " although logger level is " + loggerLevel);
+                }
+                if (!records.isEmpty()) {
+                    throw new RuntimeException(loggerLevel + " [" +
+                            logLevel + "] : Unexpected record sizes: "
+                        + records.toString());
+                }
+            }
+        }
+    }
+
+    public static class JULBackendTester extends BackendTester<LogRecord>{
+
+        public JULBackendTester(boolean isSystem) {
+            this(isSystem,null,null);
+        }
+        public JULBackendTester(boolean isSystem, ResourceBundle localized) {
+            this(isSystem,null,localized);
+        }
+        public JULBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo) {
+            this(isSystem, restrictedTo, null);
+        }
+        public JULBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo,
+                ResourceBundle localized) {
+            super(isSystem, restrictedTo, localized);
+        }
+
+        Logger getBackendLogger(String name) {
+            if (isSystem) {
+                return LoggingProviderImpl.getLogManagerAccess().demandLoggerFor(
+                        LogManager.getLogManager(), name, Thread.class);
+            } else {
+                return Logger.getLogger(name);
+            }
+        }
+
+        class JULBackendAdaptor extends BackendAdaptor {
+            @Override
+            public String getLoggerName(LogRecord res) {
+                return res.getLoggerName();
+            }
+            @Override
+            public Level getLevel(LogRecord res) {
+                return Level.valueOf(res.getLevel().getName());
+            }
+            @Override
+            public String getMessage(LogRecord res) {
+                return res.getMessage();
+            }
+            @Override
+            public String getSourceClassName(LogRecord res) {
+                return res.getSourceClassName();
+            }
+            @Override
+            public String getSourceMethodName(LogRecord res) {
+                return res.getSourceMethodName();
+            }
+            @Override
+            public Throwable getThrown(LogRecord res) {
+                return res.getThrown();
+            }
+            @Override
+            public ResourceBundle getResourceBundle(LogRecord res) {
+                return res.getResourceBundle();
+            }
+            @Override
+            public void setLevel(java.lang.System.Logger logger, Level level) {
+                Logger backend = getBackendLogger(logger.getName());
+                backend.setLevel(java.util.logging.Level.parse(level.name()));
+            }
+            @Override
+            public void setLevel(java.lang.System.Logger logger, java.lang.System.Logger.Level level) {
+                setLevel(logger, toJUL(level));
+            }
+            @Override
+            public List<LogRecord> getBackendRecords() {
+                return handler.records;
+            }
+            @Override
+            public void resetBackendRecords() {
+                handler.reset();
+            }
+            @Override
+            public Level getMappedLevel(Object level) {
+                if (level instanceof java.lang.System.Logger.Level) {
+                    return toJUL((java.lang.System.Logger.Level)level);
+                }
+                return (Level)level;
+            }
+        }
+
+        final JULBackendAdaptor julAdaptor = new JULBackendAdaptor();
+
+        @Override
+        BackendAdaptor adaptor() {
+            return julAdaptor;
+        }
+
+    }
+
+    public abstract static class BackendTesterFactory {
+        public abstract BackendTester createBackendTester(boolean isSystem);
+        public abstract  BackendTester createBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo);
+        public abstract  BackendTester createBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo,
+                ResourceBundle bundle);
+        public abstract  BackendTester createBackendTester(boolean isSystem,
+                ResourceBundle bundle);
+    }
+
+    public static class JULBackendTesterFactory extends BackendTesterFactory {
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem) {
+            return new JULBackendTester(isSystem);
+        }
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo) {
+            return new JULBackendTester(isSystem, restrictedTo);
+        }
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo,
+                ResourceBundle bundle) {
+            return new JULBackendTester(isSystem, restrictedTo, bundle);
+        }
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem,
+                ResourceBundle bundle) {
+            return new JULBackendTester(isSystem, bundle);
+        }
+    }
+
+    public static class CustomLoggerFinder extends LoggerFinder {
+
+        static enum CustomLevel { OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL };
+        static CustomLevel[] customLevelMap = { CustomLevel.ALL,
+            CustomLevel.TRACE, CustomLevel.DEBUG, CustomLevel.INFO,
+            CustomLevel.WARN, CustomLevel.ERROR, CustomLevel.OFF
+        };
+        static class CustomLogRecord {
+            public final CustomLevel logLevel;
+            public final java.lang.System.Logger logger;
+            public final String msg;
+            public final Object[] params;
+            public final Throwable thrown;
+            public final ResourceBundle bundle;
+
+            CustomLogRecord(java.lang.System.Logger producer,
+                    CustomLevel level, String msg) {
+                this(producer, level, msg, (ResourceBundle)null, (Throwable)null, (Object[])null);
+            }
+
+            CustomLogRecord(java.lang.System.Logger producer,
+                    CustomLevel level, String msg, ResourceBundle bundle,
+                    Throwable thrown, Object... params) {
+                this.logger   = producer;
+                this.logLevel = level;
+                this.msg = msg;
+                this.params = params;
+                this.thrown = thrown;
+                this.bundle = bundle;
+            }
+        }
+
+        static final List<CustomLogRecord>  records =
+                Collections.synchronizedList(new ArrayList<>());
+
+        static class CustomLogger implements java.lang.System.Logger {
+
+            final String name;
+            volatile CustomLevel level;
+            CustomLogger(String name) {
+                this.name = name;
+                this.level = CustomLevel.INFO;
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            public void setLevel(CustomLevel level) {
+                this.level = level;
+            }
+
+
+            @Override
+            public boolean isLoggable(java.lang.System.Logger.Level level) {
+
+                return this.level != CustomLevel.OFF && this.level.ordinal()
+                        >= customLevelMap[level.ordinal()].ordinal();
+            }
+
+            @Override
+            public void log(java.lang.System.Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) {
+                if (isLoggable(level)) {
+                    records.add(new CustomLogRecord(this, customLevelMap[level.ordinal()],
+                              key, bundle, thrown));
+                }
+            }
+
+            @Override
+            public void log(java.lang.System.Logger.Level level, ResourceBundle bundle, String format, Object... params) {
+                if (isLoggable(level)) {
+                    records.add(new CustomLogRecord(this, customLevelMap[level.ordinal()],
+                              format, bundle, null, params));
+                }
+            }
+
+        }
+
+        final Map<String, java.lang.System.Logger> applicationLoggers =
+                Collections.synchronizedMap(new HashMap<>());
+        final Map<String, java.lang.System.Logger> systemLoggers =
+                Collections.synchronizedMap(new HashMap<>());
+
+        @Override
+        public java.lang.System.Logger getLogger(String name, Class<?> caller) {
+            ClassLoader callerLoader = caller.getClassLoader();
+            if (callerLoader == null) {
+                systemLoggers.putIfAbsent(name, new CustomLogger(name));
+                return systemLoggers.get(name);
+            } else {
+                applicationLoggers.putIfAbsent(name, new CustomLogger(name));
+                return applicationLoggers.get(name);
+            }
+        }
+
+        CustomLevel fromJul(Level level) {
+            if (level.intValue() == Level.OFF.intValue()) {
+                return CustomLevel.OFF;
+            } else if (level.intValue() > Level.SEVERE.intValue()) {
+                return CustomLevel.ERROR;
+            } else if (level.intValue() > Level.WARNING.intValue()) {
+                return CustomLevel.ERROR;
+            } else if (level.intValue() > Level.INFO.intValue()) {
+                return CustomLevel.WARN;
+            } else if (level.intValue() > Level.CONFIG.intValue()) {
+                return CustomLevel.INFO;
+            } else if (level.intValue() > Level.FINER.intValue()) {
+                return CustomLevel.DEBUG;
+            } else if (level.intValue() > Level.FINEST.intValue()) {
+                return CustomLevel.TRACE;
+            } else if (level.intValue() == Level.ALL.intValue()) {
+                return CustomLevel.ALL;
+            } else {
+                return CustomLevel.TRACE;
+            }
+        }
+
+        Level toJul(CustomLevel level) {
+            switch(level) {
+                case OFF: return Level.OFF;
+                case FATAL: return Level.SEVERE;
+                case ERROR: return Level.SEVERE;
+                case WARN: return Level.WARNING;
+                case INFO: return Level.INFO;
+                case DEBUG: return Level.FINE;
+                case TRACE: return Level.FINER;
+                case ALL: return Level.ALL;
+                default: throw new InternalError("No such level: "+level);
+            }
+        }
+
+    }
+
+    public static class CustomBackendTester extends
+            BackendTester<CustomLoggerFinder.CustomLogRecord> {
+
+        public final CustomLoggerFinder provider;
+
+        public CustomBackendTester(boolean isSystem) {
+            this(isSystem, null, null);
+        }
+
+        public CustomBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo) {
+            this(isSystem, restrictedTo, null);
+        }
+
+        public CustomBackendTester(boolean isSystem,
+                ResourceBundle localized) {
+            this(isSystem, null, localized);
+        }
+
+        public CustomBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo,
+                ResourceBundle localized) {
+            super(isSystem, restrictedTo, localized);
+            provider = (CustomLoggerFinder)java.lang.System.LoggerFinder.getLoggerFinder();
+        }
+
+        @Override
+        public java.lang.System.Logger convert(java.lang.System.Logger logger) {
+            if (restrictedTo != null && restrictedTo.isInstance(logger)) {
+                return logger;
+            } else if (restrictedTo == jdkLoggerClass) {
+                return logger;
+            } else {
+                return java.lang.System.Logger.class.cast(
+                        sun.util.logging.PlatformLogger.Bridge.convert(logger));
+            }
+        }
+
+        class CustomBackendAdaptor extends BackendAdaptor {
+
+            @Override
+            public String getLoggerName(CustomLoggerFinder.CustomLogRecord res) {
+                return res.logger.getName();
+            }
+
+            @Override
+            public CustomLoggerFinder.CustomLevel getLevel(CustomLoggerFinder.CustomLogRecord res) {
+                return res.logLevel;
+            }
+
+            @Override
+            public String getMessage(CustomLoggerFinder.CustomLogRecord res) {
+                return res.msg;
+            }
+
+            @Override // we don't support source class name in our custom provider implementation
+            public String getSourceClassName(CustomLoggerFinder.CustomLogRecord res) {
+                return null;
+            }
+
+            @Override // we don't support source method name in our custom provider implementation
+            public String getSourceMethodName(CustomLoggerFinder.CustomLogRecord res) {
+                return null;
+            }
+
+            @Override
+            public Throwable getThrown(CustomLoggerFinder.CustomLogRecord res) {
+                return res.thrown;
+            }
+
+            @Override
+            public ResourceBundle getResourceBundle(CustomLoggerFinder.CustomLogRecord res) {
+                return res.bundle;
+            }
+
+            @Override
+            public void setLevel(java.lang.System.Logger logger, Level level) {
+                final CustomLoggerFinder.CustomLogger l =
+                        (CustomLoggerFinder.CustomLogger)
+                        (isSystem ? provider.getLogger(logger.getName(), Thread.class) :
+                        provider.getLogger(logger.getName(), LoggerFinderBackendTest.class));
+                l.setLevel(provider.fromJul(level));
+            }
+            @Override
+            public void setLevel(java.lang.System.Logger logger,
+                    java.lang.System.Logger.Level level) {
+                setLevel(logger, toJUL(level));
+            }
+
+            CustomLoggerFinder.CustomLevel getLevel(java.lang.System.Logger logger) {
+                final CustomLoggerFinder.CustomLogger l =
+                        (CustomLoggerFinder.CustomLogger)
+                        (isSystem ? provider.getLogger(logger.getName(), Thread.class) :
+                        provider.getLogger(logger.getName(), LoggerFinderBackendTest.class));
+                return l.level;
+            }
+
+            @Override
+            public List<CustomLoggerFinder.CustomLogRecord> getBackendRecords() {
+                return CustomLoggerFinder.records;
+            }
+
+            @Override
+            public void resetBackendRecords() {
+                CustomLoggerFinder.records.clear();
+            }
+
+            @Override
+            public boolean shouldBeLoggable(Levels level, Level loggerLevel) {
+                return loggerLevel != Level.OFF &&
+                       fromLevels(level).ordinal() <= provider.fromJul(loggerLevel).ordinal();
+            }
+
+            @Override
+            public boolean isLoggable(java.lang.System.Logger logger, Level l) {
+                return super.isLoggable(logger, l);
+            }
+
+            @Override
+            public boolean shouldBeLoggable(Level logLevel, Level loggerLevel) {
+                return loggerLevel != Level.OFF &&
+                        provider.fromJul(logLevel).ordinal() <= provider.fromJul(loggerLevel).ordinal();
+            }
+
+            @Override // we don't support source class name in our custom provider implementation
+            public String getCallerClassName(Levels level, String clazz) {
+                return null;
+            }
+
+            @Override // we don't support source method name in our custom provider implementation
+            public String getCallerMethodName(Levels level, String method) {
+                return null;
+            }
+
+            @Override // we don't support source class name in our custom provider implementation
+            public String getCallerClassName(MethodInvoker<?,?> logMethod, String clazz) {
+                return null;
+            }
+
+            @Override // we don't support source method name in our custom provider implementation
+            public String getCallerMethodName(MethodInvoker<?,?> logMethod, String method) {
+                return null;
+            }
+
+            @Override
+            public CustomLoggerFinder.CustomLevel getMappedLevel(Object level) {
+                if (level instanceof java.lang.System.Logger.Level) {
+                    final int index = ((java.lang.System.Logger.Level)level).ordinal();
+                    return CustomLoggerFinder.customLevelMap[index];
+                } else if (level instanceof Level) {
+                    return provider.fromJul((Level)level);
+                }
+                return (CustomLoggerFinder.CustomLevel) level;
+            }
+
+            CustomLoggerFinder.CustomLevel fromLevels(Levels level) {
+                switch(level) {
+                    case SEVERE:
+                        return CustomLoggerFinder.CustomLevel.ERROR;
+                    case WARNING:
+                        return CustomLoggerFinder.CustomLevel.WARN;
+                    case INFO:
+                        return CustomLoggerFinder.CustomLevel.INFO;
+                    case CONFIG: case FINE:
+                        return CustomLoggerFinder.CustomLevel.DEBUG;
+                    case FINER:  case FINEST:
+                        return CustomLoggerFinder.CustomLevel.TRACE;
+                }
+                throw new InternalError("No such level "+level);
+            }
+
+        }
+
+        @Override
+        BackendAdaptor adaptor() {
+            return new CustomBackendAdaptor();
+        }
+
+    }
+
+    public static class CustomBackendTesterFactory extends BackendTesterFactory {
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem) {
+            return new CustomBackendTester(isSystem);
+        }
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo) {
+            return new CustomBackendTester(isSystem, restrictedTo);
+        }
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem,
+                Class<? extends java.lang.System.Logger> restrictedTo,
+                ResourceBundle bundle) {
+            return new CustomBackendTester(isSystem, restrictedTo, bundle);
+        }
+
+        @Override
+        public BackendTester createBackendTester(boolean isSystem,
+                ResourceBundle bundle) {
+            return new CustomBackendTester(isSystem, bundle);
+        }
+    }
+
+    static final Method getLazyLogger;
+    static final Method accessLoggerFinder;
+    static {
+        // jdk.internal.logger.LazyLoggers.getLazyLogger(name, caller);
+        try {
+            Class<?> lazyLoggers = jdk.internal.logger.LazyLoggers.class;
+            getLazyLogger = lazyLoggers.getMethod("getLazyLogger",
+                    String.class, Class.class);
+            getLazyLogger.setAccessible(true);
+            Class<?> loggerFinderLoader =
+                    Class.forName("java.lang.System$LoggerFinder");
+            accessLoggerFinder = loggerFinderLoader.getDeclaredMethod("accessProvider");
+            accessLoggerFinder.setAccessible(true);
+        } catch (Throwable ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    static java.lang.System.Logger getSystemLogger(String name, Class<?> caller) throws Exception {
+        try {
+            return java.lang.System.Logger.class.cast(getLazyLogger.invoke(null, name, caller));
+        } catch (InvocationTargetException x) {
+            Throwable t = x.getTargetException();
+            if (t instanceof Exception) {
+                throw (Exception)t;
+            } else {
+                throw (Error)t;
+            }
+        }
+    }
+    static java.lang.System.Logger getSystemLogger(String name,
+            ResourceBundle bundle, Class<?> caller) throws Exception {
+        try {
+            LoggerFinder provider = LoggerFinder.class.cast(accessLoggerFinder.invoke(null));
+            return provider.getLocalizedLogger(name, bundle, caller);
+        } catch (InvocationTargetException x) {
+            Throwable t = x.getTargetException();
+            if (t instanceof Exception) {
+                throw (Exception)t;
+            } else {
+                throw (Error)t;
+            }
+        }
+    }
+
+    // Change this to 'true' to get more traces...
+    public static boolean verbose = false;
+
+    public static void main(String[] argv) throws Exception {
+
+        final AtomicInteger nb = new AtomicInteger(0);
+        final boolean hidesProvider = Boolean.getBoolean("test.logger.hidesProvider");
+        System.out.println(ClassLoader.getSystemClassLoader());
+        final BackendTesterFactory factory;
+        if (java.lang.System.LoggerFinder.getLoggerFinder() instanceof CustomLoggerFinder) {
+            if (hidesProvider) {
+                System.err.println("Custom backend "
+                        + java.lang.System.LoggerFinder.getLoggerFinder()
+                        + " should have been hidden!");
+                throw new RuntimeException(
+                        "Custom backend should have been hidden: "
+                        + "check value of java.system.class.loader property");
+            }
+            System.out.println("Using custom backend");
+            factory = new CustomBackendTesterFactory();
+        } else {
+            if (!hidesProvider) {
+                System.err.println("Default JUL backend "
+                        + java.lang.System.LoggerFinder.getLoggerFinder()
+                        + " should have been hidden!");
+                throw new RuntimeException(
+                        "Default JUL backend should have been hidden: "
+                        + "check value of java.system.class.loader property");
+            }
+            System.out.println("Using JUL backend");
+            factory = new JULBackendTesterFactory();
+        }
+
+        testBackend(nb, factory);
+    }
+
+    public static void testBackend(AtomicInteger nb, BackendTesterFactory factory) throws Exception {
+
+        // Tests all level specifics methods with loggers configured with
+        // all possible levels and loggers obtained with all possible
+        // entry points from LoggerFactory and JdkLoggerFactory, with
+        // JUL as backend.
+
+        // Test a simple application logger with JUL backend
+        final BackendTester tester = factory.createBackendTester(false);
+        final java.lang.System.Logger logger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLogger("foo", LoggerFinderBackendTest.class);
+
+        testLogger(tester, logger, nb);
+
+        // Test a simple system logger with JUL backend
+        final java.lang.System.Logger system =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLogger("bar", Thread.class);
+        final BackendTester systemTester = factory.createBackendTester(true);
+        testLogger(systemTester, system, nb);
+
+        // Test a localized application logger with null resource bundle and
+        // JUL backend
+        final java.lang.System.Logger noBundleLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLocalizedLogger("baz", null, LoggerFinderBackendTest.class);
+        final BackendTester noBundleTester =
+                factory.createBackendTester(false, spiLoggerClass);
+        testLogger(noBundleTester, noBundleLogger, nb);
+
+        // Test a localized system logger with null resource bundle and JUL
+        // backend
+        final java.lang.System.Logger noBundleSysLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLocalizedLogger("oof", null, Thread.class);
+        final BackendTester noBundleSysTester =
+                factory.createBackendTester(true, spiLoggerClass);
+        testLogger(noBundleSysTester, noBundleSysLogger, nb);
+
+        // Test a localized application logger with null resource bundle and
+        // JUL backend
+        try {
+            System.getLogger("baz", null);
+            throw new RuntimeException("Expected NullPointerException not thrown");
+        } catch (NullPointerException x) {
+            System.out.println("System.Loggers.getLogger(\"baz\", null): got expected " + x);
+        }
+        final java.lang.System.Logger noBundleExtensionLogger =
+                getSystemLogger("baz", null, LoggerFinderBackendTest.class);
+        final BackendTester noBundleExtensionTester =
+                factory.createBackendTester(false, jdkLoggerClass);
+        testLogger(noBundleExtensionTester, noBundleExtensionLogger, nb);
+
+        // Test a simple system logger with JUL backend
+        final java.lang.System.Logger sysExtensionLogger =
+                getSystemLogger("oof", Thread.class);
+        final BackendTester sysExtensionTester =
+                factory.createBackendTester(true, jdkLoggerClass);
+        testLogger(sysExtensionTester, sysExtensionLogger, nb);
+
+        // Test a localized system logger with null resource bundle and JUL
+        // backend
+        final java.lang.System.Logger noBundleSysExtensionLogger =
+                getSystemLogger("oof", null, Thread.class);
+        final BackendTester noBundleSysExtensionTester =
+                factory.createBackendTester(true, jdkLoggerClass);
+        testLogger(noBundleSysExtensionTester, noBundleSysExtensionLogger, nb);
+
+        // Test a localized application logger converted to JDK with null
+        // resource bundle and JUL backend
+        final java.lang.System.Logger noBundleConvertedLogger =
+                (java.lang.System.Logger)
+                sun.util.logging.PlatformLogger.Bridge.convert(noBundleLogger);
+        final BackendTester noBundleJdkTester = factory.createBackendTester(false);
+        testLogger(noBundleJdkTester, noBundleConvertedLogger, nb);
+
+        // Test a localized system logger converted to JDK with null resource
+        // bundle and JUL backend
+        final java.lang.System.Logger noBundleConvertedSysLogger =
+                (java.lang.System.Logger)
+                sun.util.logging.PlatformLogger.Bridge.convert(noBundleSysLogger);
+        final BackendTester noBundleJdkSysTester = factory.createBackendTester(true);
+        testLogger(noBundleJdkSysTester, noBundleConvertedSysLogger, nb);
+
+        // Test a localized application logger with resource bundle and JUL
+        // backend
+        final ResourceBundle bundle =
+                ResourceBundle.getBundle(ResourceBundeLocalized.class.getName());
+        final java.lang.System.Logger bundleLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLocalizedLogger("toto", bundle, LoggerFinderBackendTest.class);
+        final BackendTester bundleTester =
+                factory.createBackendTester(false, spiLoggerClass, bundle);
+        testLogger(bundleTester, bundleLogger, nb);
+
+        // Test a localized system logger with resource bundle and JUL backend
+        final java.lang.System.Logger bundleSysLogger =
+                java.lang.System.LoggerFinder.getLoggerFinder()
+                        .getLocalizedLogger("titi", bundle, Thread.class);
+        final BackendTester bundleSysTester =
+                factory.createBackendTester(true, spiLoggerClass, bundle);
+        testLogger(bundleSysTester, bundleSysLogger, nb);
+
+        // Test a localized Jdk application logger with resource bundle and JUL
+        // backend
+        final java.lang.System.Logger bundleExtensionLogger =
+                System.getLogger("tita", bundle);
+        final BackendTester bundleExtensionTester =
+                factory.createBackendTester(false, jdkLoggerClass, bundle);
+        testLogger(bundleExtensionTester, bundleExtensionLogger, nb);
+
+        // Test a localized Jdk system logger with resource bundle and JUL
+        // backend
+        final java.lang.System.Logger bundleExtensionSysLogger =
+                getSystemLogger("titu", bundle, Thread.class);
+        final BackendTester bundleExtensionSysTester =
+                factory.createBackendTester(true, jdkLoggerClass, bundle);
+        testLogger(bundleExtensionSysTester, bundleExtensionSysLogger, nb);
+
+        // Test a localized application logger converted to JDK with resource
+        // bundle and JUL backend
+        final BackendTester bundleJdkTester =
+                factory.createBackendTester(false, bundle);
+        final java.lang.System.Logger bundleConvertedLogger =
+                (java.lang.System.Logger)
+                sun.util.logging.PlatformLogger.Bridge.convert(bundleLogger);
+        testLogger(bundleJdkTester, bundleConvertedLogger, nb);
+
+        // Test a localized Jdk system logger converted to JDK with resource
+        // bundle and JUL backend
+        final BackendTester bundleJdkSysTester =
+                factory.createBackendTester(true, bundle);
+        final java.lang.System.Logger bundleConvertedSysLogger =
+                (java.lang.System.Logger)
+                sun.util.logging.PlatformLogger.Bridge.convert(bundleSysLogger);
+        testLogger(bundleJdkSysTester, bundleConvertedSysLogger, nb);
+
+        // Now need to add tests for all the log/logp/logrb methods...
+
+    }
+
+    private static class FooObj {
+        final String s;
+        FooObj(String s) {
+            this.s = s;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() +": "+s;
+        }
+
+    }
+
+    public static void testLogger(BackendTester tester,
+            java.lang.System.Logger spiLogger, AtomicInteger nb) {
+
+        // Test all level-specific method forms:
+        // fatal(...) error(...) severe(...) etc...
+        java.lang.System.Logger jdkLogger = tester.convert(spiLogger);
+        for (Levels l : Levels.values()) {
+            java.lang.System.Logger logger =
+                    l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger;
+            tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-"
+                    + nb.incrementAndGet());
+            tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-"
+                    + nb.incrementAndGet(),
+                    bundleParam);
+            final int nbb = nb.incrementAndGet();
+            tester.testLevel(l, logger, () -> l.method + "[" + logger.getName()
+                    + "]-" + nbb);
+        }
+        for (Levels l : Levels.values()) {
+            java.lang.System.Logger logger =
+                    l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger;
+            tester.testLevel(l, logger,
+                    l.method + "[" + logger.getName()+ "]({0},{1})-"
+                    + nb.incrementAndGet(),
+                    "One", "Two");
+            tester.testLevel(l, logger,
+                    l.method + "[" + logger.getName()+ "]({0},{1})-"
+                    + nb.incrementAndGet(),
+                    bundleParam, "One", "Two");
+        }
+        final Throwable thrown = new RuntimeException("Test");
+        for (Levels l : Levels.values()) {
+            java.lang.System.Logger logger =
+                    l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger;
+            tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-"
+                    + nb.incrementAndGet(),
+                    thrown);
+            tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-"
+                    + nb.incrementAndGet(),
+                    bundleParam, thrown);
+            final int nbb = nb.incrementAndGet();
+            tester.testLevel(l, logger, ()->l.method + "[" + logger.getName()+ "]-"
+                    + nbb, thrown);
+        }
+
+        java.lang.System.Logger logger = jdkLogger;
+
+         // test System.Logger methods
+       tester.testSpiLog(logger, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet());
+        tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet());
+        tester.testSpiLog(logger, "[" + logger.getName()+ "]-({0},{1})"
+                + nb.incrementAndGet(), "One", "Two");
+        tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-({0},{1})"
+                + nb.incrementAndGet(), "One", "Two");
+        tester.testSpiLog(logger, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet(), thrown);
+        tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet(), thrown);
+        final int nbb01 = nb.incrementAndGet();
+        tester.testSpiLog(logger, () -> "[" + logger.getName()+ "]-" + nbb01);
+        final int nbb02 = nb.incrementAndGet();
+        tester.testSpiLog(logger, thrown, () -> "[" + logger.getName()+ "]-" + nbb02);
+        final int nbb03 = nb.incrementAndGet();
+        tester.testSpiLog(logger, new FooObj("[" + logger.getName()+ "]-" + nbb03));
+
+        // Test all log method forms:
+        // jdk.internal.logging.Logger.log(...)
+        tester.testLog(logger, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet());
+        tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet());
+        tester.testLog(logger, "[" + logger.getName()+ "]-({0},{1})"
+                + nb.incrementAndGet(), "One", "Two");
+        tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-({0},{1})"
+                + nb.incrementAndGet(), "One", "Two");
+        tester.testLog(logger, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet(), thrown);
+        tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet(), thrown);
+        final int nbb1 = nb.incrementAndGet();
+        tester.testLog(logger, () -> "[" + logger.getName()+ "]-" + nbb1);
+        final int nbb2 = nb.incrementAndGet();
+        tester.testLog(logger, thrown, () -> "[" + logger.getName()+ "]-" + nbb2);
+
+        // Test all logp method forms
+        // jdk.internal.logging.Logger.logp(...)
+        tester.testLogp(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(),
+                "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet());
+        tester.testLogrb(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(), bundleParam,
+                "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet());
+        tester.testLogp(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(),
+                "[" + logger.getName()+ "]-({0},{1})"
+                + nb.incrementAndGet(), "One", "Two");
+        tester.testLogrb(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(), bundleParam,
+                "[" + logger.getName()+ "]-({0},{1})"
+                + nb.incrementAndGet(), "One", "Two");
+        tester.testLogp(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(),
+                "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet(), thrown);
+        tester.testLogrb(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(), bundleParam,
+                "[" + logger.getName()+ "]-"
+                + nb.incrementAndGet(), thrown);
+        final int nbb3 = nb.incrementAndGet();
+        tester.testLogp(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(),
+                () -> "[" + logger.getName()+ "]-" + nbb3);
+        final int nbb4 = nb.incrementAndGet();
+        tester.testLogp(logger, "clazz" + nb.incrementAndGet(),
+                "method" + nb.incrementAndGet(),
+                thrown, () -> "[" + logger.getName()+ "]-" + nbb4);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,2 @@
+LoggerFinderBackendTest$CustomLoggerFinder
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.lang.System.LoggerFinder;
+
+/**
+ * A custom class loader which can hide the registered LoggerProvider
+ * depending on the value of a test.logger.hidesProvider system property.
+ * @author danielfuchs
+ */
+public class SystemClassLoader extends ClassLoader {
+
+    final public boolean hidesProvider;
+
+    public SystemClassLoader() {
+        hidesProvider = Boolean.getBoolean("test.logger.hidesProvider");
+    }
+    public SystemClassLoader(ClassLoader parent) {
+        super(parent);
+        hidesProvider = Boolean.getBoolean("test.logger.hidesProvider");
+    }
+
+    boolean accept(String name) {
+        final boolean res = !name.endsWith(LoggerFinder.class.getName());
+        if (res == false) {
+            System.out.println("Hiding " + name);
+        }
+        return res;
+    }
+
+    @Override
+    public URL getResource(String name) {
+        if (hidesProvider && !accept(name)) {
+            return null;
+        } else {
+            return super.getResource(name);
+        }
+    }
+
+    class Enumerator implements Enumeration<URL> {
+        final Enumeration<URL> enumeration;
+        volatile URL next;
+        Enumerator(Enumeration<URL> enumeration) {
+            this.enumeration = enumeration;
+        }
+
+        @Override
+        public boolean hasMoreElements() {
+            if (next != null) return true;
+            if (!enumeration.hasMoreElements()) return false;
+            if (hidesProvider == false) return true;
+            next = enumeration.nextElement();
+            if (accept(next.getPath())) return true;
+            next = null;
+            return hasMoreElements();
+        }
+
+        @Override
+        public URL nextElement() {
+            final URL res = next == null ? enumeration.nextElement() : next;
+            next = null;
+            if (hidesProvider == false || accept(res.getPath())) return res;
+            return nextElement();
+        }
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        final Enumeration<URL> enumeration = super.getResources(name);
+        return hidesProvider ? new Enumerator(enumeration) : enumeration;
+    }
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,850 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.AccessControlException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import sun.util.logging.PlatformLogger;
+import java.util.logging.LogRecord;
+import java.lang.System.LoggerFinder;
+import java.lang.System.Logger;
+import java.util.stream.Stream;
+import sun.util.logging.internal.LoggingProviderImpl;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary JDK implementation specific unit test for JDK internal artifacts.
+ *          Tests all internal bridge methods with the default LoggerFinder
+ *          JUL backend.
+ * @modules java.base/sun.util.logging
+ *          java.base/jdk.internal.logger
+ *          java.logging/sun.util.logging.internal
+ * @run  main/othervm DefaultLoggerBridgeTest
+ * @author danielfuchs
+ */
+public class DefaultLoggerBridgeTest {
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAccess = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent implements Cloneable {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        java.util.logging.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                isLoggable,
+                loggerName,
+                level,
+                bundle,
+                thrown,
+                args,
+                msg,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+
+        public LogEvent cloneWith(long sequenceNumber)
+                throws CloneNotSupportedException {
+            LogEvent cloned = (LogEvent)super.clone();
+            cloned.sequenceNumber = sequenceNumber;
+            return cloned;
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    DefaultLoggerBridgeTest.class.getName(),
+                    "testLogger", level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+    }
+
+    static final java.util.logging.Level[] julLevels = {
+        java.util.logging.Level.ALL,
+        java.util.logging.Level.FINEST,
+        java.util.logging.Level.FINER,
+        java.util.logging.Level.FINE,
+        java.util.logging.Level.CONFIG,
+        java.util.logging.Level.INFO,
+        java.util.logging.Level.WARNING,
+        java.util.logging.Level.SEVERE,
+        java.util.logging.Level.OFF,
+    };
+
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    record.getLevel(),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    static PlatformLogger.Bridge convert(Logger logger) {
+        boolean old = allowAccess.get().get();
+        allowAccess.get().set(true);
+        try {
+            return PlatformLogger.Bridge.convert(logger);
+        } finally {
+            allowAccess.get().set(old);
+        }
+    }
+
+    static Logger getLogger(String name, Class<?> caller) {
+        boolean old = allowAccess.get().get();
+        allowAccess.get().set(true);
+        try {
+            return jdk.internal.logger.LazyLoggers.getLogger(name, caller);
+        } finally {
+            allowAccess.get().set(old);
+        }
+    }
+
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+
+    static void setSecurityManager() {
+        if (System.getSecurityManager() == null) {
+            Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll));
+            System.setSecurityManager(new SecurityManager());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length == 0)
+            args = new String[] {
+                "NOSECURITY",
+                "NOPERMISSIONS",
+                "WITHPERMISSIONS"
+            };
+
+        Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
+            LoggerFinder provider;
+            switch (testCase) {
+                case NOSECURITY:
+                    System.out.println("\n*** Without Security Manager\n");
+                    test(true);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case NOPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, without permissions\n");
+                    setSecurityManager();
+                    test(false);
+                    System.out.println("Tetscase count: " + sequencer.get());
+                    break;
+                case WITHPERMISSIONS:
+                    System.out.println("\n*** With Security Manager, with control permission\n");
+                    setSecurityManager();
+                    final boolean control = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(true);
+                    } finally {
+                        allowControl.get().set(control);
+                    }
+                    break;
+                default:
+                    throw new RuntimeException("Unknown test case: " + testCase);
+            }
+        });
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(boolean hasRequiredPermissions) {
+
+        ResourceBundle loggerBundle =
+                ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<Object, String> loggerDescMap = new HashMap<>();
+
+        Logger sysLogger1a = getLogger("foo", Thread.class);
+        loggerDescMap.put(sysLogger1a, "jdk.internal.logger.LazyLoggers.getLogger(\"foo\", Thread.class)");
+
+        Logger appLogger1 = System.getLogger("foo");
+        loggerDescMap.put(appLogger1, "System.getLogger(\"foo\")");
+
+        LoggerFinder provider;
+        try {
+            provider = LoggerFinder.getLoggerFinder();
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Expected exception not raised");
+            }
+        } catch (AccessControlException x) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected permission check", x);
+            }
+            if (!SimplePolicy.LOGGERFINDER_PERMISSION.equals(x.getPermission())) {
+                throw new RuntimeException("Unexpected permission in exception: " + x, x);
+            }
+            final boolean control = allowControl.get().get();
+            try {
+                allowControl.get().set(true);
+                provider = LoggerFinder.getLoggerFinder();
+            } finally {
+                allowControl.get().set(control);
+            }
+        }
+
+        Logger sysLogger1b = null;
+        try {
+            sysLogger1b = provider.getLogger("foo", Thread.class);
+            if (sysLogger1b != sysLogger1a) {
+                loggerDescMap.put(sysLogger1b, "provider.getLogger(\"foo\", Thread.class)");
+            }
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for system logger: " + acx);
+        }
+
+        Logger appLogger2 = System.getLogger("foo", loggerBundle);
+        loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
+
+        if (appLogger2 == appLogger1) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        Logger sysLogger2 = null;
+        try {
+            sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class);
+            loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)");
+            if (!hasRequiredPermissions) {
+                throw new RuntimeException("Managed to obtain a system logger without permission");
+            }
+        } catch (AccessControlException acx) {
+            if (hasRequiredPermissions) {
+                throw new RuntimeException("Unexpected security exception: ", acx);
+            }
+            if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) {
+                throw new RuntimeException("Unexpected permission in exception: " + acx, acx);
+            }
+            System.out.println("Got expected exception for localized system logger: " + acx);
+        }
+        if (hasRequiredPermissions && appLogger2 == sysLogger2) {
+            throw new RuntimeException("identical loggers");
+        }
+        if (hasRequiredPermissions && sysLogger2 == sysLogger1a) {
+            throw new RuntimeException("identical loggers");
+        }
+
+        final java.util.logging.Logger appSink;
+        final java.util.logging.Logger sysSink;
+        final MyHandler appHandler;
+        final MyHandler sysHandler;
+        final boolean old = allowAll.get().get();
+        allowAll.get().set(true);
+        try {
+            sysSink = LoggingProviderImpl.getLogManagerAccess().demandLoggerFor(
+                    LogManager.getLogManager(), "foo", Thread.class);
+            appSink = LoggingProviderImpl.getLogManagerAccess().demandLoggerFor(
+                    LogManager.getLogManager(), "foo", DefaultLoggerBridgeTest.class);
+            if (appSink == sysSink) {
+                throw new RuntimeException("identical backend loggers");
+            }
+            appSink.addHandler(appHandler = new MyHandler());
+            sysSink.addHandler(sysHandler = new MyHandler());
+            appSink.setUseParentHandlers(VERBOSE);
+            sysSink.setUseParentHandlers(VERBOSE);
+        } finally {
+            allowAll.get().set(old);
+        }
+
+        try {
+            testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1a), sysSink);
+            testLogger(provider, loggerDescMap, "foo", null, convert(appLogger1), appSink);
+            testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(appLogger2), appSink);
+            if (sysLogger1b != null && sysLogger1b != sysLogger1a) {
+                testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1b), sysSink);
+            }
+            if (sysLogger2 != null) {
+                testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(sysLogger2), sysSink);
+            }
+        } finally {
+            allowAll.get().set(true);
+            try {
+                appSink.removeHandler(appHandler);
+                sysSink.removeHandler(sysHandler);
+            } finally {
+                allowAll.get().set(old);
+            }
+        }
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected) {
+        LogEvent actual =  eventQueue.poll();
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected, boolean expectNotNull) {
+        LogEvent actual =  eventQueue.poll();
+        if (actual == null && !expectNotNull) return;
+        if (actual != null && !expectNotNull) {
+            throw new RuntimeException("Unexpected log event found for " + desc
+                + "\n\tgot: " + actual);
+        }
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) {
+        boolean before = allowAll.get().get();
+        try {
+            allowAll.get().set(true);
+            sink.setLevel(loggerLevel);
+        } finally {
+            allowAll.get().set(before);
+        }
+    }
+
+    static sun.util.logging.PlatformLogger.Level toPlatformLevel(java.util.logging.Level level) {
+        boolean old = allowAccess.get().get();
+        allowAccess.get().set(true);
+        try {
+            return sun.util.logging.PlatformLogger.Level.valueOf(level.getName());
+        } finally {
+            allowAccess.get().set(old);
+        }
+    }
+
+    // Calls the methods defined on LogProducer and verify the
+    // parameters received by the underlying logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<Object, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            PlatformLogger.Bridge logger,
+            java.util.logging.Logger sink) {
+
+        if (loggerDescMap.get(logger) == null) {
+            throw new RuntimeException("Missing description for " + logger);
+        }
+        System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]");
+        final java.util.logging.Level OFF = java.util.logging.Level.OFF;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        System.out.println("\tlogger.log(messageLevel, fooMsg)");
+        System.out.println("\tlogger.<level>(fooMsg)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.log(toPlatformLevel(messageLevel), fooMsg);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        Supplier<String> supplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return this.toString();
+            }
+        };
+        System.out.println("\tlogger.log(messageLevel, supplier)");
+        System.out.println("\tlogger.<level>(supplier)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, null,
+                            supplier.get(), (Throwable)null, (Object[])null);
+                logger.log(toPlatformLevel(messageLevel), supplier);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = fooMsg;
+        System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.log(toPlatformLevel(messageLevel), format, arg1, arg2);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.log(toPlatformLevel(messageLevel), fooMsg, thrown);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.log(messageLevel, thrown, supplier)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, null,
+                            supplier.get(), thrown, (Object[])null);
+                logger.log(toPlatformLevel(messageLevel), thrown, supplier);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        String sourceClass = "blah.Blah";
+        String sourceMethod = "blih";
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, loggerBundle,
+                            fooMsg, (Throwable)null, (Object[])null);
+                logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, fooMsg);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, null,
+                            supplier.get(), (Throwable)null, (Object[])null);
+                logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, supplier);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, loggerBundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, loggerBundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, null,
+                            supplier.get(), thrown, (Object[])null);
+                logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, thrown, supplier);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
+        System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, bundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.logrb(toPlatformLevel(messageLevel), bundle, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, bundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.logrb(toPlatformLevel(messageLevel), bundle, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, bundle,
+                            format, (Throwable)null, arg1, arg2);
+                logger.logrb(toPlatformLevel(messageLevel), sourceClass, sourceMethod, bundle, format, arg1, arg2);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+
+        System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julLevels) {
+                String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, sourceClass, sourceMethod, messageLevel, bundle,
+                            fooMsg, thrown, (Object[])null);
+                logger.logrb(toPlatformLevel(messageLevel), sourceClass, sourceMethod, bundle, fooMsg, thrown);
+                checkLogEvent(provider, desc, expected, expected.isLoggable);
+            }
+        }
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        public static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+        final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger");
+        final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging");
+
+        final Permissions permissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        final ThreadLocal<AtomicBoolean> allowAccess;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowControl,
+                ThreadLocal<AtomicBoolean> allowAccess,
+                ThreadLocal<AtomicBoolean> allowAll) {
+            this.allowControl = allowControl;
+            this.allowAccess = allowAccess;
+            this.allowAll = allowAll;
+            permissions = new Permissions();
+            allPermissions = new PermissionsBuilder()
+                    .add(new java.security.AllPermission())
+                    .toPermissions();
+        }
+
+        Permissions getPermissions() {
+            if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) {
+                PermissionsBuilder builder =  new PermissionsBuilder()
+                        .addAll(permissions);
+                if (allowControl.get().get()) {
+                    builder.add(LOGGERFINDER_PERMISSION);
+                }
+                if (allowAccess.get().get()) {
+                    builder.add(ACCESS_LOGGER);
+                    builder.add(ACCESS_LOGGING);
+                }
+                if (allowAll.get().get()) {
+                    builder.addAll(allPermissions);
+                }
+                return builder.toPermissions();
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            return getPermissions().implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(getPermissions()).toPermissions();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,544 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.ResourceBundle;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.lang.System.LoggerFinder;
+import sun.util.logging.PlatformLogger;
+import sun.util.logging.internal.LoggingProviderImpl;
+
+/**
+ * @test
+ * @bug     8140364
+ * @summary Tests all PlatformLogger methods with the default LoggerFinder JUL backend.
+ * @modules java.base/sun.util.logging java.logging/sun.util.logging.internal
+ * @run  main/othervm DefaultPlatformLoggerTest
+ * @author danielfuchs
+ */
+public class DefaultPlatformLoggerTest {
+
+    final static AtomicLong sequencer = new AtomicLong();
+    final static boolean VERBOSE = false;
+    static final ThreadLocal<AtomicBoolean> allowControl = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+    static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
+        @Override
+        protected AtomicBoolean initialValue() {
+            return  new AtomicBoolean(false);
+        }
+    };
+
+    public static final Queue<LogEvent> eventQueue = new ArrayBlockingQueue<>(128);
+
+    public static final class LogEvent implements Cloneable {
+
+        public LogEvent() {
+            this(sequencer.getAndIncrement());
+        }
+
+        LogEvent(long sequenceNumber) {
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        long sequenceNumber;
+        boolean isLoggable;
+        String loggerName;
+        java.util.logging.Level level;
+        ResourceBundle bundle;
+        Throwable thrown;
+        Object[] args;
+        String msg;
+        String className;
+        String methodName;
+
+        Object[] toArray() {
+            return new Object[] {
+                sequenceNumber,
+                isLoggable,
+                loggerName,
+                level,
+                bundle,
+                thrown,
+                args,
+                msg,
+                className,
+                methodName,
+            };
+        }
+
+        @Override
+        public String toString() {
+            return Arrays.deepToString(toArray());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj instanceof LogEvent
+                    && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(toArray());
+        }
+
+        public LogEvent cloneWith(long sequenceNumber)
+                throws CloneNotSupportedException {
+            LogEvent cloned = (LogEvent)super.clone();
+            cloned.sequenceNumber = sequenceNumber;
+            return cloned;
+        }
+
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            return LogEvent.of(sequenceNumber, isLoggable, name,
+                    DefaultPlatformLoggerTest.class.getName(),
+                    "testLogger", level, bundle, key,
+                    thrown, params);
+        }
+        public static LogEvent of(long sequenceNumber,
+                boolean isLoggable, String name,
+                String className, String methodName,
+                java.util.logging.Level level, ResourceBundle bundle,
+                String key, Throwable thrown, Object... params) {
+            LogEvent evt = new LogEvent(sequenceNumber);
+            evt.loggerName = name;
+            evt.level = level;
+            evt.args = params;
+            evt.bundle = bundle;
+            evt.thrown = thrown;
+            evt.msg = key;
+            evt.isLoggable = isLoggable;
+            evt.className = className;
+            evt.methodName = methodName;
+            return evt;
+        }
+
+    }
+
+    static final java.util.logging.Level[] julLevels = {
+        java.util.logging.Level.ALL,
+        new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {},
+        java.util.logging.Level.FINEST,
+        new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {},
+        java.util.logging.Level.FINER,
+        new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {},
+        java.util.logging.Level.FINE,
+        new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {},
+        java.util.logging.Level.CONFIG,
+        new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {},
+        java.util.logging.Level.INFO,
+        new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {},
+        java.util.logging.Level.WARNING,
+        new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {},
+        java.util.logging.Level.SEVERE,
+        new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {},
+        java.util.logging.Level.OFF,
+    };
+
+    static final java.util.logging.Level[] julPlatformLevels = {
+        java.util.logging.Level.FINEST,
+        java.util.logging.Level.FINER,
+        java.util.logging.Level.FINE,
+        java.util.logging.Level.CONFIG,
+        java.util.logging.Level.INFO,
+        java.util.logging.Level.WARNING,
+        java.util.logging.Level.SEVERE,
+    };
+
+
+    public static class MyBundle extends ResourceBundle {
+
+        final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+
+        @Override
+        protected Object handleGetObject(String key) {
+            if (key.contains(" (translated)")) {
+                throw new RuntimeException("Unexpected key: " + key);
+            }
+            return map.computeIfAbsent(key, k -> k + " (translated)");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            return Collections.enumeration(map.keySet());
+        }
+
+    }
+
+    public static class MyHandler extends Handler {
+
+        @Override
+        public java.util.logging.Level getLevel() {
+            return java.util.logging.Level.ALL;
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            eventQueue.add(LogEvent.of(sequencer.getAndIncrement(),
+                    true, record.getLoggerName(),
+                    record.getSourceClassName(),
+                    record.getSourceMethodName(),
+                    record.getLevel(),
+                    record.getResourceBundle(), record.getMessage(),
+                    record.getThrown(), record.getParameters()));
+        }
+        @Override
+        public void flush() {
+        }
+        @Override
+        public void close() throws SecurityException {
+        }
+
+    }
+
+    public static class MyLoggerBundle extends MyBundle {
+
+    }
+
+    public static void main(String[] args) throws Exception {
+        LoggerFinder provider = LoggerFinder.getLoggerFinder();
+        java.util.logging.Logger appSink = LoggingProviderImpl.getLogManagerAccess()
+                .demandLoggerFor(LogManager.getLogManager(), "foo",
+                        DefaultPlatformLoggerTest.class);
+        java.util.logging.Logger sysSink = LoggingProviderImpl.getLogManagerAccess()
+                .demandLoggerFor(LogManager.getLogManager(),"foo", Thread.class);
+        appSink.addHandler(new MyHandler());
+        sysSink.addHandler(new MyHandler());
+        appSink.setUseParentHandlers(VERBOSE);
+        sysSink.setUseParentHandlers(VERBOSE);
+
+        System.out.println("\n*** Without Security Manager\n");
+        test(provider, true, appSink, sysSink);
+        System.out.println("Tetscase count: " + sequencer.get());
+
+        Policy.setPolicy(new SimplePolicy(allowAll, allowControl));
+        System.setSecurityManager(new SecurityManager());
+
+        System.out.println("\n*** With Security Manager, without permissions\n");
+        test(provider, false, appSink, sysSink);
+        System.out.println("Tetscase count: " + sequencer.get());
+
+        System.out.println("\n*** With Security Manager, with control permission\n");
+        allowControl.get().set(true);
+        test(provider, true, appSink, sysSink);
+
+        System.out.println("\nPASSED: Tested " + sequencer.get() + " cases.");
+    }
+
+    public static void test(LoggerFinder provider, boolean hasRequiredPermissions,
+            java.util.logging.Logger appSink, java.util.logging.Logger sysSink) throws Exception {
+
+        // No way to giva a resource bundle to a platform logger.
+        // ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
+        final Map<PlatformLogger, String> loggerDescMap = new HashMap<>();
+
+        PlatformLogger platform = PlatformLogger.getLogger("foo");
+        loggerDescMap.put(platform, "PlatformLogger.getLogger(\"foo\")");
+
+        testLogger(provider, loggerDescMap, "foo", null, platform, sysSink);
+    }
+
+    public static class Foo {
+
+    }
+
+    static void verbose(String msg) {
+       if (VERBOSE) {
+           System.out.println(msg);
+       }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected) {
+        LogEvent actual =  eventQueue.poll();
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void checkLogEvent(LoggerFinder provider, String desc,
+            LogEvent expected, boolean expectNotNull) {
+        LogEvent actual =  eventQueue.poll();
+        if (actual == null && !expectNotNull) return;
+        if (actual != null && !expectNotNull) {
+            throw new RuntimeException("Unexpected log event found for " + desc
+                + "\n\tgot: " + actual);
+        }
+        if (!expected.equals(actual)) {
+            throw new RuntimeException("mismatch for " + desc
+                    + "\n\texpected=" + expected
+                    + "\n\t  actual=" + actual);
+        } else {
+            verbose("Got expected results for "
+                    + desc + "\n\t" + expected);
+        }
+    }
+
+    static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) {
+        boolean before = allowAll.get().get();
+        try {
+            allowAll.get().set(true);
+            sink.setLevel(loggerLevel);
+        } finally {
+            allowAll.get().set(before);
+        }
+    }
+
+    // Calls the methods defined on LogProducer and verify the
+    // parameters received by the underlying logger.
+    private static void testLogger(LoggerFinder provider,
+            Map<PlatformLogger, String> loggerDescMap,
+            String name,
+            ResourceBundle loggerBundle,
+            PlatformLogger logger,
+            java.util.logging.Logger sink) throws Exception {
+
+        System.out.println("Testing " + loggerDescMap.get(logger));
+        final java.util.logging.Level OFF = java.util.logging.Level.OFF;
+
+        Foo foo = new Foo();
+        String fooMsg = foo.toString();
+        System.out.println("\tlogger.<level>(fooMsg)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel : julPlatformLevels) {
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, (Throwable)null, (Object[])null);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == java.util.logging.Level.FINEST) {
+                    logger.finest(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.FINER) {
+                    logger.finer(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.FINE) {
+                    logger.fine(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.CONFIG) {
+                    logger.config(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.INFO) {
+                    logger.info(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.WARNING) {
+                    logger.warning(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.SEVERE) {
+                    logger.severe(fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                }
+            }
+        }
+
+        Throwable thrown = new Exception("OK: log me!");
+        System.out.println("\tlogger.<level>(msg, thrown)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel :julPlatformLevels) {
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            fooMsg, thrown, (Object[])null);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(msg, thrown): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == java.util.logging.Level.FINEST) {
+                    logger.finest(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.FINER) {
+                    logger.finer(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.FINE) {
+                    logger.fine(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.CONFIG) {
+                    logger.config(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.INFO) {
+                    logger.info(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.WARNING) {
+                    logger.warning(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.SEVERE) {
+                    logger.severe(fooMsg, thrown);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                }
+            }
+        }
+
+        String format = "two params [{1} {2}]";
+        Object arg1 = foo;
+        Object arg2 = fooMsg;
+        System.out.println("\tlogger.<level>(format, arg1, arg2)");
+        for (java.util.logging.Level loggerLevel : julLevels) {
+            setLevel(sink, loggerLevel);
+            for (java.util.logging.Level messageLevel : julPlatformLevels) {
+                LogEvent expected =
+                        LogEvent.of(
+                            sequencer.get(),
+                            loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(),
+                            name, messageLevel, loggerBundle,
+                            format, (Throwable)null, foo, fooMsg);
+                String desc2 = "logger." + messageLevel.toString().toLowerCase()
+                        + "(format, foo, fooMsg): loggerLevel="
+                        + loggerLevel+", messageLevel="+messageLevel;
+                if (messageLevel == java.util.logging.Level.FINEST) {
+                    logger.finest(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.FINER) {
+                    logger.finer(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.FINE) {
+                    logger.fine(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.CONFIG) {
+                    logger.config(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.INFO) {
+                    logger.info(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.WARNING) {
+                    logger.warning(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                } else if (messageLevel == java.util.logging.Level.SEVERE) {
+                    logger.severe(format, foo, fooMsg);
+                    checkLogEvent(provider, desc2, expected, expected.isLoggable);
+                }
+            }
+        }
+
+    }
+
+    final static class PermissionsBuilder {
+        final Permissions perms;
+        public PermissionsBuilder() {
+            this(new Permissions());
+        }
+        public PermissionsBuilder(Permissions perms) {
+            this.perms = perms;
+        }
+        public PermissionsBuilder add(Permission p) {
+            perms.add(p);
+            return this;
+        }
+        public PermissionsBuilder addAll(PermissionCollection col) {
+            if (col != null) {
+                for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
+                    perms.add(e.nextElement());
+                }
+            }
+            return this;
+        }
+        public Permissions toPermissions() {
+            final PermissionsBuilder builder = new PermissionsBuilder();
+            builder.addAll(perms);
+            return builder.perms;
+        }
+    }
+
+    public static class SimplePolicy extends Policy {
+        public static final RuntimePermission LOGGERFINDER_PERMISSION =
+                new RuntimePermission("loggerFinder");
+
+        final Permissions permissions;
+        final Permissions withControlPermissions;
+        final Permissions allPermissions;
+        final ThreadLocal<AtomicBoolean> allowAll;
+        final ThreadLocal<AtomicBoolean> allowControl;
+        public SimplePolicy(ThreadLocal<AtomicBoolean> allowAll,
+                ThreadLocal<AtomicBoolean> allowControl) {
+            this.allowAll = allowAll;
+            this.allowControl = allowControl;
+            permissions = new Permissions();
+
+            withControlPermissions = new Permissions();
+            withControlPermissions.add(LOGGERFINDER_PERMISSION);
+
+            // these are used for configuring the test itself...
+            allPermissions = new Permissions();
+            allPermissions.add(new java.security.AllPermission());
+        }
+
+        @Override
+        public boolean implies(ProtectionDomain domain, Permission permission) {
+            if (allowAll.get().get()) return allPermissions.implies(permission);
+            if (allowControl.get().get()) return withControlPermissions.implies(permission);
+            return permissions.implies(permission);
+        }
+
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource) {
+            return new PermissionsBuilder().addAll(
+                    allowAll.get().get() ? allPermissions :
+                    allowControl.get().get()
+                    ? withControlPermissions : permissions).toPermissions();
+        }
+
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain domain) {
+            return new PermissionsBuilder().addAll(
+                    allowAll.get().get() ? allPermissions :
+                    allowControl.get().get()
+                    ? withControlPermissions : permissions).toPermissions();
+        }
+    }
+}
--- a/jdk/test/java/lang/invoke/AccessControlTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/java/lang/invoke/AccessControlTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 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
@@ -225,6 +225,31 @@
                 System.out.println(this+" willAccess "+lc+" m1="+m1+" m2="+m2+" => "+((m2 & m1) != 0));
             return (m2 & m1) != 0;
         }
+
+        /** Predict the success or failure of accessing this class. */
+        public boolean willAccessClass(Class<?> c2, boolean load) {
+            Class<?> c1 = lookupClass();
+            if (load && c1.getClassLoader() == null) {
+                return false;
+            }
+            LookupCase lc = this.in(c2);
+            int m1 = lc.lookupModes();
+            boolean r = false;
+            if (m1 == 0) {
+                r = false;
+            } else {
+                int m2 = fixMods(c2.getModifiers());
+                if ((m2 & PUBLIC) != 0) {
+                    r = true;
+                } else if ((m1 & PACKAGE) != 0 && c1.getPackage() == c2.getPackage()) {
+                    r = true;
+                }
+            }
+            if (verbosity >= 2) {
+                System.out.println(this+" willAccessClass "+lc+" c1="+c1+" c2="+c2+" => "+r);
+            }
+            return r;
+        }
     }
 
     private static Class<?> topLevelClass(Class<?> cls) {
@@ -342,6 +367,8 @@
                 Method method = targetMethod(targetClass, targetAccess, methodType);
                 // Try to access target method from various contexts.
                 for (LookupCase sourceCase : CASES) {
+                    testOneAccess(sourceCase, method, "findClass");
+                    testOneAccess(sourceCase, method, "accessClass");
                     testOneAccess(sourceCase, method, "find");
                     testOneAccess(sourceCase, method, "unreflect");
                 }
@@ -356,11 +383,19 @@
         Class<?> targetClass = method.getDeclaringClass();
         String methodName = method.getName();
         MethodType methodType = methodType(method.getReturnType(), method.getParameterTypes());
-        boolean willAccess = sourceCase.willAccess(method);
+        boolean isFindOrAccessClass = "findClass".equals(kind) || "accessClass".equals(kind);
+        boolean willAccess = isFindOrAccessClass ?
+                sourceCase.willAccessClass(targetClass, "findClass".equals(kind)) : sourceCase.willAccess(method);
         boolean didAccess = false;
         ReflectiveOperationException accessError = null;
         try {
             switch (kind) {
+            case "accessClass":
+                sourceCase.lookup().accessClass(targetClass);
+                break;
+            case "findClass":
+                sourceCase.lookup().findClass(targetClass.getName());
+                break;
             case "find":
                 if ((method.getModifiers() & Modifier.STATIC) != 0)
                     sourceCase.lookup().findStatic(targetClass, methodName, methodType);
@@ -378,8 +413,8 @@
             accessError = ex;
         }
         if (willAccess != didAccess) {
-            System.out.println(sourceCase+" => "+targetClass.getSimpleName()+"."+methodName+methodType);
-            System.out.println("fail on "+method+" ex="+accessError);
+            System.out.println(sourceCase+" => "+targetClass.getSimpleName()+(isFindOrAccessClass?"":"."+methodName+methodType));
+            System.out.println("fail "+(isFindOrAccessClass?kind:"on "+method)+" ex="+accessError);
             assertEquals(willAccess, didAccess);
         }
         testCount++;
--- a/jdk/test/java/lang/invoke/BigArityTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/java/lang/invoke/BigArityTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -58,6 +58,8 @@
         return x == null ? dflt : x;
     }
 
+    static final MethodType MT_A = MethodType.methodType(Object.class, Object.class, Object[].class, Object.class);
+
     static Object hashArguments(Object... args) {
         return Objects.hash(args);
     }
@@ -108,9 +110,36 @@
             }
         }
         // Sizes not in the above array are good:
-        target.asCollector(Object[].class, minbig-1);
+        target.asCollector(Object[].class, minbig - 1);
         for (int i = 2; i <= 10; i++)
-            target.asCollector(Object[].class, minbig-i);
+            target.asCollector(Object[].class, minbig - i);
+    }
+
+    static void asciae02target(Object[] a, Object b) {
+        // naught
+    }
+
+    @Test
+    public void asCollectorIAE02() throws ReflectiveOperationException {
+        final int[] INVALID_ARRAY_LENGTHS = {
+            Integer.MIN_VALUE, Integer.MIN_VALUE + 1, -2, -1, 254, 255, Integer.MAX_VALUE - 1, Integer.MAX_VALUE
+        };
+        MethodHandle target = MethodHandles.lookup().findStatic(BigArityTest.class, "asciae02target",
+                MethodType.methodType(void.class, Object[].class, Object.class));
+        int minbig = Integer.MAX_VALUE;
+        for (int invalidLength : INVALID_ARRAY_LENGTHS) {
+            if (minbig > invalidLength && invalidLength > 100) minbig = invalidLength;
+            try {
+                target.asCollector(0, Object[].class, invalidLength);
+                assert(false) : invalidLength;
+            } catch (IllegalArgumentException ex) {
+                System.out.println("OK: "+ex);
+            }
+        }
+        // Sizes not in the above array are good:
+        for (int i = 1; i <= 10; ++i) {
+            target.asCollector(0, Object[].class, minbig - i);
+        }
     }
 
     @Test
@@ -216,51 +245,86 @@
             Class<? extends Object[]> cls = (Class<? extends Object[]>) cls0;
             //Class<? extends Object[]> cls = Object[].class.asSubclass(cls0);
             int nargs = args.length, skip;
+            Object hr;
             MethodHandle smh = mh.asSpreader(cls, nargs - (skip = 0));
+            MethodHandle hsmh = mh.asSpreader(0, cls, nargs - skip);
             Object[] tail = Arrays.copyOfRange(args, skip, nargs, cls);
-            if (cls == Object[].class)
+            Object[] head = Arrays.copyOfRange(args, 0, nargs - skip, cls);
+            if (cls == Object[].class) {
                 r = smh.invokeExact(tail);
-            else if (cls == Integer[].class)
+                hr = hsmh.invokeExact(head);
+            } else if (cls == Integer[].class) {
                 r = smh.invokeExact((Integer[]) tail); //warning OK, see 8019340
-            else
+                hr = hsmh.invokeExact((Integer[]) head);
+            } else {
                 r = smh.invoke(tail);
+                hr = hsmh.invoke(head);
+            }
             assertEquals(r0, r);
+            assertEquals(r0, hr);
             smh = mh.asSpreader(cls, nargs - (skip = 1));
+            hsmh = mh.asSpreader(0, cls, nargs - skip);
             tail = Arrays.copyOfRange(args, skip, nargs, cls);
-            if (cls == Object[].class)
+            head = Arrays.copyOfRange(args, 0, nargs - skip, cls);
+            if (cls == Object[].class) {
                 r = smh.invokeExact(args[0], tail);
-            else if (cls == Integer[].class)
+                hr = hsmh.invokeExact(head, args[2]);
+            } else if (cls == Integer[].class) {
                 r = smh.invokeExact(args[0], (Integer[]) tail);
-            else
+                hr = hsmh.invokeExact((Integer[]) head, args[2]);
+            } else {
                 r = smh.invoke(args[0], tail);
+                hr = hsmh.invoke(head, args[2]);
+            }
             assertEquals(r0, r);
+            assertEquals(r0, hr);
             smh = mh.asSpreader(cls, nargs - (skip = 2));
+            hsmh = mh.asSpreader(0, cls, nargs - skip);
             tail = Arrays.copyOfRange(args, skip, nargs, cls);
-            if (cls == Object[].class)
+            head = Arrays.copyOfRange(args, 0, nargs - skip, cls);
+            if (cls == Object[].class) {
                 r = smh.invokeExact(args[0], args[1], tail);
-            else if (cls == Integer[].class)
+                hr = hsmh.invokeExact(head, args[1], args[2]);
+            } else if (cls == Integer[].class) {
                 r = smh.invokeExact(args[0], args[1], (Integer[]) tail);
-            else
+                hr = hsmh.invokeExact((Integer[]) head, args[1], args[2]);
+            } else {
                 r = smh.invoke(args[0], args[1], tail);
+                hr = hsmh.invoke(head, args[1], args[2]);
+            }
             assertEquals(r0, r);
+            assertEquals(r0, hr);
             smh = mh.asSpreader(cls, nargs - (skip = 3));
+            hsmh = mh.asSpreader(0, cls, nargs - skip);
             tail = Arrays.copyOfRange(args, skip, nargs, cls);
-            if (cls == Object[].class)
+            head = Arrays.copyOfRange(args, 0, nargs - skip, cls);
+            if (cls == Object[].class) {
                 r = smh.invokeExact(args[0], args[1], args[2], tail);
-            else if (cls == Integer[].class)
+                hr = hsmh.invokeExact(head, args[0], args[1], args[2]);
+            } else if (cls == Integer[].class) {
                 r = smh.invokeExact(args[0], args[1], args[2], (Integer[]) tail);
-            else
+                hr = hsmh.invokeExact((Integer[]) head, args[0], args[1], args[2]);
+            } else {
                 r = smh.invoke(args[0], args[1], args[2], tail);
+                hr = hsmh.invoke(head, args[0], args[1], args[2]);
+            }
             assertEquals(r0, r);
+            assertEquals(r0, hr);
             // Try null array in addition to zero-length array:
             tail = null;
-            if (cls == Object[].class)
+            head = null;
+            if (cls == Object[].class) {
                 r = smh.invokeExact(args[0], args[1], args[2], tail);
-            else if (cls == Integer[].class)
+                hr = hsmh.invokeExact(head, args[0], args[1], args[2]);
+            } else if (cls == Integer[].class) {
                 r = smh.invokeExact(args[0], args[1], args[2], (Integer[]) tail);
-            else
+                hr = hsmh.invokeExact((Integer[]) head, args[0], args[1], args[2]);
+            } else {
                 r = smh.invoke(args[0], args[1], args[2], tail);
+                hr = hsmh.invoke(head, args[0], args[1], args[2]);
+            }
             assertEquals(r0, r);
+            assertEquals(r0, hr);
         }
     }
 
@@ -292,7 +356,7 @@
     @Test
     public void testArities() throws Throwable {
         System.out.println("testing spreaders and collectors on high arities...");
-            int iterations = ITERATION_COUNT;
+        int iterations = ITERATION_COUNT;
         testArities(Object[].class, MIN_ARITY-10, MIN_ARITY-1, iterations / 1000);
         testArities(Object[].class, MIN_ARITY, SLOW_ARITY-1, iterations);
         testArities(Object[].class, SLOW_ARITY, MAX_ARITY, iterations / 1000);
@@ -307,8 +371,13 @@
             Class<? extends Object[]> cls = (Class<? extends Object[]>) cls0;
             System.out.println("array class: "+cls.getSimpleName());
             int iterations = ITERATION_COUNT / 1000;
-            testArities(cls, MIN_ARITY, SLOW_ARITY-1, iterations);
-            testArities(cls, SLOW_ARITY, MAX_ARITY, iterations / 100);
+            try {
+                testArities(cls, MIN_ARITY, SLOW_ARITY - 1, iterations);
+                testArities(cls, SLOW_ARITY, MAX_ARITY, iterations / 100);
+            } catch (Throwable t) {
+                t.printStackTrace();
+                throw t;
+            }
         }
     }
 
@@ -321,11 +390,14 @@
             if (verbose)  System.out.println("arity="+arity);
             MethodHandle mh = MH_hashArguments(cls, arity);
             MethodHandle mh_VA = mh.asSpreader(cls, arity);
+            MethodHandle mh_VA_h = mh.asSpreader(0, cls, arity-1);
             assert(mh_VA.type().parameterType(0) == cls);
-            testArities(cls, arity, iterations, verbose, mh, mh_VA);
+            assert(mh_VA_h.type().parameterType(0) == cls);
+            testArities(cls, arity, iterations, verbose, mh, mh_VA, mh_VA_h);
             // mh_CA will collect arguments of a particular type and pass them to mh_VA
             MethodHandle mh_CA = mh_VA.asCollector(cls, arity);
             MethodHandle mh_VA2 = mh_CA.asSpreader(cls, arity);
+            MethodHandle mh_VA2_h = mh_CA.asSpreader(0, cls, arity-1);
             assert(mh_CA.type().equals(mh.type()));
             assert(mh_VA2.type().equals(mh_VA.type()));
             if (cls != Object[].class) {
@@ -336,7 +408,7 @@
                 }
             }
             int iterations_VA = iterations / 100;
-            testArities(cls, arity, iterations_VA, false, mh_CA, mh_VA2);
+            testArities(cls, arity, iterations_VA, false, mh_CA, mh_VA2, mh_VA2_h);
         }
     }
 
@@ -357,13 +429,16 @@
      * @param verbose are we printing extra output?
      * @param mh      a fixed-arity version of {@code hashArguments}
      * @param mh_VA   a variable-arity version of {@code hashArguments}, accepting the given array type {@code cls}
+     * @param mh_VA_h a version of {@code hashArguments} that has a leading {@code cls} array and one final {@code cls}
+    *                 argument
      */
     private void testArities(Class<? extends Object[]> cls,
                              int arity,
                              int iterations,
                              boolean verbose,
                              MethodHandle mh,
-                             MethodHandle mh_VA
+                             MethodHandle mh_VA,
+                             MethodHandle mh_VA_h
                              ) throws Throwable {
         if (iterations < 4)  iterations = 4;
         final int MAX_MH_ARITY      = MAX_JVM_ARITY - 1;  // mh.invoke(arg*[N])
@@ -373,6 +448,7 @@
             args = Arrays.copyOf(args, arity, cls);
         Object r0 = Objects.hash(args);
         Object r;
+        Object hr;
         MethodHandle ximh = null;
         MethodHandle gimh = null;
         if (arity <= MAX_INVOKER_ARITY) {
@@ -397,13 +473,18 @@
         Object[] mh_args = cat(mh, args);
         assert(arity <= MAX_MH_ARITY);
         for (int i = 0; i < iterations; ++i) {
-            if (cls == Object[].class)
+            if (cls == Object[].class) {
                 r = mh_VA.invokeExact(args);
-            else if (cls == Integer[].class)
-                r = mh_VA.invokeExact((Integer[])args); //warning OK, see 8019340
-            else
+                hr = mh_VA_h.invokeExact(Arrays.copyOfRange(args, 0, arity - 1), args[arity - 1]);
+            } else if (cls == Integer[].class) {
+                r = mh_VA.invokeExact((Integer[]) args); //warning OK, see 8019340
+                hr = mh_VA_h.invokeExact((Integer[]) Arrays.copyOfRange(args, 0, arity - 1), (Integer) args[arity - 1]);
+            } else {
                 r = mh_VA.invoke(args);
+                hr = mh_VA_h.invoke(Arrays.copyOfRange(args, 0, arity - 1), args[arity - 1]);
+            }
             assertEquals(r0, r);
+            assertEquals(r0, hr);
             r = mh.invokeWithArguments(args);
             assertEquals(r0, r);
             if (ximh != null) {
@@ -473,6 +554,43 @@
     // </editor-fold>
     xF8, xF9, xFA, xFB);
     }
+    static Object hashArguments_252_a(Object x00, Object[] x01_FA, Object xFB) {
+        return Objects.hash(
+                // <editor-fold defaultstate="collapsed" desc="x00, x01_FA[0], x01_FA[1], x01_FA[2], ...">
+                x00, x01_FA[0], x01_FA[1], x01_FA[2], x01_FA[3], x01_FA[4], x01_FA[5], x01_FA[6], x01_FA[7], x01_FA[8],
+                x01_FA[9], x01_FA[10], x01_FA[11], x01_FA[12], x01_FA[13], x01_FA[14], x01_FA[15], x01_FA[16],
+                x01_FA[17], x01_FA[18], x01_FA[19], x01_FA[20], x01_FA[21], x01_FA[22], x01_FA[23], x01_FA[24],
+                x01_FA[25], x01_FA[26], x01_FA[27], x01_FA[28], x01_FA[29], x01_FA[30], x01_FA[31], x01_FA[32],
+                x01_FA[33], x01_FA[34], x01_FA[35], x01_FA[36], x01_FA[37], x01_FA[38], x01_FA[39], x01_FA[40],
+                x01_FA[41], x01_FA[42], x01_FA[43], x01_FA[44], x01_FA[45], x01_FA[46], x01_FA[47], x01_FA[48],
+                x01_FA[49], x01_FA[50], x01_FA[51], x01_FA[52], x01_FA[53], x01_FA[54], x01_FA[55], x01_FA[56],
+                x01_FA[57], x01_FA[58], x01_FA[59], x01_FA[60], x01_FA[61], x01_FA[62], x01_FA[63], x01_FA[64],
+                x01_FA[65], x01_FA[66], x01_FA[67], x01_FA[68], x01_FA[69], x01_FA[70], x01_FA[71], x01_FA[72],
+                x01_FA[73], x01_FA[74], x01_FA[75], x01_FA[76], x01_FA[77], x01_FA[78], x01_FA[79], x01_FA[80],
+                x01_FA[81], x01_FA[82], x01_FA[83], x01_FA[84], x01_FA[85], x01_FA[86], x01_FA[87], x01_FA[88],
+                x01_FA[89], x01_FA[90], x01_FA[91], x01_FA[92], x01_FA[93], x01_FA[94], x01_FA[95], x01_FA[96],
+                x01_FA[97], x01_FA[98], x01_FA[99], x01_FA[100], x01_FA[101], x01_FA[102], x01_FA[103], x01_FA[104],
+                x01_FA[105], x01_FA[106], x01_FA[107], x01_FA[108], x01_FA[109], x01_FA[110], x01_FA[111], x01_FA[112],
+                x01_FA[113], x01_FA[114], x01_FA[115], x01_FA[116], x01_FA[117], x01_FA[118], x01_FA[119], x01_FA[120],
+                x01_FA[121], x01_FA[122], x01_FA[123], x01_FA[124], x01_FA[125], x01_FA[126], x01_FA[127], x01_FA[128],
+                x01_FA[129], x01_FA[130], x01_FA[131], x01_FA[132], x01_FA[133], x01_FA[134], x01_FA[135], x01_FA[136],
+                x01_FA[137], x01_FA[138], x01_FA[139], x01_FA[140], x01_FA[141], x01_FA[142], x01_FA[143], x01_FA[144],
+                x01_FA[145], x01_FA[146], x01_FA[147], x01_FA[148], x01_FA[149], x01_FA[150], x01_FA[151], x01_FA[152],
+                x01_FA[153], x01_FA[154], x01_FA[155], x01_FA[156], x01_FA[157], x01_FA[158], x01_FA[159], x01_FA[160],
+                x01_FA[161], x01_FA[162], x01_FA[163], x01_FA[164], x01_FA[165], x01_FA[166], x01_FA[167], x01_FA[168],
+                x01_FA[169], x01_FA[170], x01_FA[171], x01_FA[172], x01_FA[173], x01_FA[174], x01_FA[175], x01_FA[176],
+                x01_FA[177], x01_FA[178], x01_FA[179], x01_FA[180], x01_FA[181], x01_FA[182], x01_FA[183], x01_FA[184],
+                x01_FA[185], x01_FA[186], x01_FA[187], x01_FA[188], x01_FA[189], x01_FA[190], x01_FA[191], x01_FA[192],
+                x01_FA[193], x01_FA[194], x01_FA[195], x01_FA[196], x01_FA[197], x01_FA[198], x01_FA[199], x01_FA[200],
+                x01_FA[201], x01_FA[202], x01_FA[203], x01_FA[204], x01_FA[205], x01_FA[206], x01_FA[207], x01_FA[208],
+                x01_FA[209], x01_FA[210], x01_FA[211], x01_FA[212], x01_FA[213], x01_FA[214], x01_FA[215], x01_FA[216],
+                x01_FA[217], x01_FA[218], x01_FA[219], x01_FA[220], x01_FA[221], x01_FA[222], x01_FA[223], x01_FA[224],
+                x01_FA[225], x01_FA[226], x01_FA[227], x01_FA[228], x01_FA[229], x01_FA[230], x01_FA[231], x01_FA[232],
+                x01_FA[233], x01_FA[234], x01_FA[235], x01_FA[236], x01_FA[237], x01_FA[238], x01_FA[239], x01_FA[240],
+                x01_FA[241], x01_FA[242], x01_FA[243], x01_FA[244], x01_FA[245], x01_FA[246], x01_FA[247], x01_FA[248],
+                // </editor-fold>
+                x01_FA[249], xFB);
+    }
 
     @Test
     public void test252() throws Throwable {
@@ -507,6 +625,8 @@
         test252(mh, a, r0);
         MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY);
         test252(mh_CA, a, r0);
+        MethodHandle mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2);
+        test252(mh_a, a, r0);
     }
     public void test252(MethodHandle mh, Object[] a, Object r0) throws Throwable {
         Object r;
@@ -686,6 +806,43 @@
     // </editor-fold>
     xF8, xF9, xFA, xFB, xFC);
     }
+    static Object hashArguments_253_a(Object x00, Object[] x01_FB, Object xFC) {
+        return Objects.hash(
+                // <editor-fold defaultstate="collapsed" desc="x00, x01_FB[0], x01_FB[1], x01_FB[2], ...">
+                x00, x01_FB[0], x01_FB[1], x01_FB[2], x01_FB[3], x01_FB[4], x01_FB[5], x01_FB[6], x01_FB[7], x01_FB[8],
+                x01_FB[9], x01_FB[10], x01_FB[11], x01_FB[12], x01_FB[13], x01_FB[14], x01_FB[15], x01_FB[16],
+                x01_FB[17], x01_FB[18], x01_FB[19], x01_FB[20], x01_FB[21], x01_FB[22], x01_FB[23], x01_FB[24],
+                x01_FB[25], x01_FB[26], x01_FB[27], x01_FB[28], x01_FB[29], x01_FB[30], x01_FB[31], x01_FB[32],
+                x01_FB[33], x01_FB[34], x01_FB[35], x01_FB[36], x01_FB[37], x01_FB[38], x01_FB[39], x01_FB[40],
+                x01_FB[41], x01_FB[42], x01_FB[43], x01_FB[44], x01_FB[45], x01_FB[46], x01_FB[47], x01_FB[48],
+                x01_FB[49], x01_FB[50], x01_FB[51], x01_FB[52], x01_FB[53], x01_FB[54], x01_FB[55], x01_FB[56],
+                x01_FB[57], x01_FB[58], x01_FB[59], x01_FB[60], x01_FB[61], x01_FB[62], x01_FB[63], x01_FB[64],
+                x01_FB[65], x01_FB[66], x01_FB[67], x01_FB[68], x01_FB[69], x01_FB[70], x01_FB[71], x01_FB[72],
+                x01_FB[73], x01_FB[74], x01_FB[75], x01_FB[76], x01_FB[77], x01_FB[78], x01_FB[79], x01_FB[80],
+                x01_FB[81], x01_FB[82], x01_FB[83], x01_FB[84], x01_FB[85], x01_FB[86], x01_FB[87], x01_FB[88],
+                x01_FB[89], x01_FB[90], x01_FB[91], x01_FB[92], x01_FB[93], x01_FB[94], x01_FB[95], x01_FB[96],
+                x01_FB[97], x01_FB[98], x01_FB[99], x01_FB[100], x01_FB[101], x01_FB[102], x01_FB[103], x01_FB[104],
+                x01_FB[105], x01_FB[106], x01_FB[107], x01_FB[108], x01_FB[109], x01_FB[110], x01_FB[111], x01_FB[112],
+                x01_FB[113], x01_FB[114], x01_FB[115], x01_FB[116], x01_FB[117], x01_FB[118], x01_FB[119], x01_FB[120],
+                x01_FB[121], x01_FB[122], x01_FB[123], x01_FB[124], x01_FB[125], x01_FB[126], x01_FB[127], x01_FB[128],
+                x01_FB[129], x01_FB[130], x01_FB[131], x01_FB[132], x01_FB[133], x01_FB[134], x01_FB[135], x01_FB[136],
+                x01_FB[137], x01_FB[138], x01_FB[139], x01_FB[140], x01_FB[141], x01_FB[142], x01_FB[143], x01_FB[144],
+                x01_FB[145], x01_FB[146], x01_FB[147], x01_FB[148], x01_FB[149], x01_FB[150], x01_FB[151], x01_FB[152],
+                x01_FB[153], x01_FB[154], x01_FB[155], x01_FB[156], x01_FB[157], x01_FB[158], x01_FB[159], x01_FB[160],
+                x01_FB[161], x01_FB[162], x01_FB[163], x01_FB[164], x01_FB[165], x01_FB[166], x01_FB[167], x01_FB[168],
+                x01_FB[169], x01_FB[170], x01_FB[171], x01_FB[172], x01_FB[173], x01_FB[174], x01_FB[175], x01_FB[176],
+                x01_FB[177], x01_FB[178], x01_FB[179], x01_FB[180], x01_FB[181], x01_FB[182], x01_FB[183], x01_FB[184],
+                x01_FB[185], x01_FB[186], x01_FB[187], x01_FB[188], x01_FB[189], x01_FB[190], x01_FB[191], x01_FB[192],
+                x01_FB[193], x01_FB[194], x01_FB[195], x01_FB[196], x01_FB[197], x01_FB[198], x01_FB[199], x01_FB[200],
+                x01_FB[201], x01_FB[202], x01_FB[203], x01_FB[204], x01_FB[205], x01_FB[206], x01_FB[207], x01_FB[208],
+                x01_FB[209], x01_FB[210], x01_FB[211], x01_FB[212], x01_FB[213], x01_FB[214], x01_FB[215], x01_FB[216],
+                x01_FB[217], x01_FB[218], x01_FB[219], x01_FB[220], x01_FB[221], x01_FB[222], x01_FB[223], x01_FB[224],
+                x01_FB[225], x01_FB[226], x01_FB[227], x01_FB[228], x01_FB[229], x01_FB[230], x01_FB[231], x01_FB[232],
+                x01_FB[233], x01_FB[234], x01_FB[235], x01_FB[236], x01_FB[237], x01_FB[238], x01_FB[239], x01_FB[240],
+                x01_FB[241], x01_FB[242], x01_FB[243], x01_FB[244], x01_FB[245], x01_FB[246], x01_FB[247], x01_FB[248],
+                // </editor-fold>
+                x01_FB[249], x01_FB[250], xFC);
+    }
 
     @Test
     public void test253() throws Throwable {
@@ -720,6 +877,8 @@
         test253(mh, a, r0);
         MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY);
         test253(mh_CA, a, r0);
+        MethodHandle mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2);
+        test253(mh_a, a, r0);
     }
     public void test253(MethodHandle mh, Object[] a, Object r0) throws Throwable {
         Object r;
@@ -899,6 +1058,43 @@
     // </editor-fold>
     xF8, xF9, xFA, xFB, xFC, xFD);
     }
+    static Object hashArguments_254_a(Object x00, Object[] x01_FC, Object xFD) {
+        return Objects.hash(
+                // <editor-fold defaultstate="collapsed" desc="x00, x01_FC[0], x01_FC[1], x01_FC[2], ...">
+                x00, x01_FC[0], x01_FC[1], x01_FC[2], x01_FC[3], x01_FC[4], x01_FC[5], x01_FC[6], x01_FC[7], x01_FC[8],
+                x01_FC[9], x01_FC[10], x01_FC[11], x01_FC[12], x01_FC[13], x01_FC[14], x01_FC[15], x01_FC[16],
+                x01_FC[17], x01_FC[18], x01_FC[19], x01_FC[20], x01_FC[21], x01_FC[22], x01_FC[23], x01_FC[24],
+                x01_FC[25], x01_FC[26], x01_FC[27], x01_FC[28], x01_FC[29], x01_FC[30], x01_FC[31], x01_FC[32],
+                x01_FC[33], x01_FC[34], x01_FC[35], x01_FC[36], x01_FC[37], x01_FC[38], x01_FC[39], x01_FC[40],
+                x01_FC[41], x01_FC[42], x01_FC[43], x01_FC[44], x01_FC[45], x01_FC[46], x01_FC[47], x01_FC[48],
+                x01_FC[49], x01_FC[50], x01_FC[51], x01_FC[52], x01_FC[53], x01_FC[54], x01_FC[55], x01_FC[56],
+                x01_FC[57], x01_FC[58], x01_FC[59], x01_FC[60], x01_FC[61], x01_FC[62], x01_FC[63], x01_FC[64],
+                x01_FC[65], x01_FC[66], x01_FC[67], x01_FC[68], x01_FC[69], x01_FC[70], x01_FC[71], x01_FC[72],
+                x01_FC[73], x01_FC[74], x01_FC[75], x01_FC[76], x01_FC[77], x01_FC[78], x01_FC[79], x01_FC[80],
+                x01_FC[81], x01_FC[82], x01_FC[83], x01_FC[84], x01_FC[85], x01_FC[86], x01_FC[87], x01_FC[88],
+                x01_FC[89], x01_FC[90], x01_FC[91], x01_FC[92], x01_FC[93], x01_FC[94], x01_FC[95], x01_FC[96],
+                x01_FC[97], x01_FC[98], x01_FC[99], x01_FC[100], x01_FC[101], x01_FC[102], x01_FC[103], x01_FC[104],
+                x01_FC[105], x01_FC[106], x01_FC[107], x01_FC[108], x01_FC[109], x01_FC[110], x01_FC[111], x01_FC[112],
+                x01_FC[113], x01_FC[114], x01_FC[115], x01_FC[116], x01_FC[117], x01_FC[118], x01_FC[119], x01_FC[120],
+                x01_FC[121], x01_FC[122], x01_FC[123], x01_FC[124], x01_FC[125], x01_FC[126], x01_FC[127], x01_FC[128],
+                x01_FC[129], x01_FC[130], x01_FC[131], x01_FC[132], x01_FC[133], x01_FC[134], x01_FC[135], x01_FC[136],
+                x01_FC[137], x01_FC[138], x01_FC[139], x01_FC[140], x01_FC[141], x01_FC[142], x01_FC[143], x01_FC[144],
+                x01_FC[145], x01_FC[146], x01_FC[147], x01_FC[148], x01_FC[149], x01_FC[150], x01_FC[151], x01_FC[152],
+                x01_FC[153], x01_FC[154], x01_FC[155], x01_FC[156], x01_FC[157], x01_FC[158], x01_FC[159], x01_FC[160],
+                x01_FC[161], x01_FC[162], x01_FC[163], x01_FC[164], x01_FC[165], x01_FC[166], x01_FC[167], x01_FC[168],
+                x01_FC[169], x01_FC[170], x01_FC[171], x01_FC[172], x01_FC[173], x01_FC[174], x01_FC[175], x01_FC[176],
+                x01_FC[177], x01_FC[178], x01_FC[179], x01_FC[180], x01_FC[181], x01_FC[182], x01_FC[183], x01_FC[184],
+                x01_FC[185], x01_FC[186], x01_FC[187], x01_FC[188], x01_FC[189], x01_FC[190], x01_FC[191], x01_FC[192],
+                x01_FC[193], x01_FC[194], x01_FC[195], x01_FC[196], x01_FC[197], x01_FC[198], x01_FC[199], x01_FC[200],
+                x01_FC[201], x01_FC[202], x01_FC[203], x01_FC[204], x01_FC[205], x01_FC[206], x01_FC[207], x01_FC[208],
+                x01_FC[209], x01_FC[210], x01_FC[211], x01_FC[212], x01_FC[213], x01_FC[214], x01_FC[215], x01_FC[216],
+                x01_FC[217], x01_FC[218], x01_FC[219], x01_FC[220], x01_FC[221], x01_FC[222], x01_FC[223], x01_FC[224],
+                x01_FC[225], x01_FC[226], x01_FC[227], x01_FC[228], x01_FC[229], x01_FC[230], x01_FC[231], x01_FC[232],
+                x01_FC[233], x01_FC[234], x01_FC[235], x01_FC[236], x01_FC[237], x01_FC[238], x01_FC[239], x01_FC[240],
+                x01_FC[241], x01_FC[242], x01_FC[243], x01_FC[244], x01_FC[245], x01_FC[246], x01_FC[247], x01_FC[248],
+                // </editor-fold>
+                x01_FC[249], x01_FC[250], x01_FC[251], xFD);
+    }
 
     @Test
     public void test254() throws Throwable {
@@ -933,6 +1129,8 @@
         test254(mh, a, r0);
         MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY);
         test254(mh_CA, a, r0);
+        MethodHandle mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2);
+        test254(mh_a, a, r0);
     }
     public void test254(MethodHandle mh, Object[] a, Object r0) throws Throwable {
         Object r;
@@ -1094,6 +1292,43 @@
     // </editor-fold>
     xF8, xF9, xFA, xFB, xFC, xFD, xFE);
     }
+    static Object hashArguments_255_a(Object x00, Object[] x01_FD, Object xFE) {
+        return Objects.hash(
+                // <editor-fold defaultstate="collapsed" desc="x00, x01_FD[0], x01_FD[1], x01_FD[2], ...">
+                x00, x01_FD[0], x01_FD[1], x01_FD[2], x01_FD[3], x01_FD[4], x01_FD[5], x01_FD[6], x01_FD[7], x01_FD[8],
+                x01_FD[9], x01_FD[10], x01_FD[11], x01_FD[12], x01_FD[13], x01_FD[14], x01_FD[15], x01_FD[16],
+                x01_FD[17], x01_FD[18], x01_FD[19], x01_FD[20], x01_FD[21], x01_FD[22], x01_FD[23], x01_FD[24],
+                x01_FD[25], x01_FD[26], x01_FD[27], x01_FD[28], x01_FD[29], x01_FD[30], x01_FD[31], x01_FD[32],
+                x01_FD[33], x01_FD[34], x01_FD[35], x01_FD[36], x01_FD[37], x01_FD[38], x01_FD[39], x01_FD[40],
+                x01_FD[41], x01_FD[42], x01_FD[43], x01_FD[44], x01_FD[45], x01_FD[46], x01_FD[47], x01_FD[48],
+                x01_FD[49], x01_FD[50], x01_FD[51], x01_FD[52], x01_FD[53], x01_FD[54], x01_FD[55], x01_FD[56],
+                x01_FD[57], x01_FD[58], x01_FD[59], x01_FD[60], x01_FD[61], x01_FD[62], x01_FD[63], x01_FD[64],
+                x01_FD[65], x01_FD[66], x01_FD[67], x01_FD[68], x01_FD[69], x01_FD[70], x01_FD[71], x01_FD[72],
+                x01_FD[73], x01_FD[74], x01_FD[75], x01_FD[76], x01_FD[77], x01_FD[78], x01_FD[79], x01_FD[80],
+                x01_FD[81], x01_FD[82], x01_FD[83], x01_FD[84], x01_FD[85], x01_FD[86], x01_FD[87], x01_FD[88],
+                x01_FD[89], x01_FD[90], x01_FD[91], x01_FD[92], x01_FD[93], x01_FD[94], x01_FD[95], x01_FD[96],
+                x01_FD[97], x01_FD[98], x01_FD[99], x01_FD[100], x01_FD[101], x01_FD[102], x01_FD[103], x01_FD[104],
+                x01_FD[105], x01_FD[106], x01_FD[107], x01_FD[108], x01_FD[109], x01_FD[110], x01_FD[111], x01_FD[112],
+                x01_FD[113], x01_FD[114], x01_FD[115], x01_FD[116], x01_FD[117], x01_FD[118], x01_FD[119], x01_FD[120],
+                x01_FD[121], x01_FD[122], x01_FD[123], x01_FD[124], x01_FD[125], x01_FD[126], x01_FD[127], x01_FD[128],
+                x01_FD[129], x01_FD[130], x01_FD[131], x01_FD[132], x01_FD[133], x01_FD[134], x01_FD[135], x01_FD[136],
+                x01_FD[137], x01_FD[138], x01_FD[139], x01_FD[140], x01_FD[141], x01_FD[142], x01_FD[143], x01_FD[144],
+                x01_FD[145], x01_FD[146], x01_FD[147], x01_FD[148], x01_FD[149], x01_FD[150], x01_FD[151], x01_FD[152],
+                x01_FD[153], x01_FD[154], x01_FD[155], x01_FD[156], x01_FD[157], x01_FD[158], x01_FD[159], x01_FD[160],
+                x01_FD[161], x01_FD[162], x01_FD[163], x01_FD[164], x01_FD[165], x01_FD[166], x01_FD[167], x01_FD[168],
+                x01_FD[169], x01_FD[170], x01_FD[171], x01_FD[172], x01_FD[173], x01_FD[174], x01_FD[175], x01_FD[176],
+                x01_FD[177], x01_FD[178], x01_FD[179], x01_FD[180], x01_FD[181], x01_FD[182], x01_FD[183], x01_FD[184],
+                x01_FD[185], x01_FD[186], x01_FD[187], x01_FD[188], x01_FD[189], x01_FD[190], x01_FD[191], x01_FD[192],
+                x01_FD[193], x01_FD[194], x01_FD[195], x01_FD[196], x01_FD[197], x01_FD[198], x01_FD[199], x01_FD[200],
+                x01_FD[201], x01_FD[202], x01_FD[203], x01_FD[204], x01_FD[205], x01_FD[206], x01_FD[207], x01_FD[208],
+                x01_FD[209], x01_FD[210], x01_FD[211], x01_FD[212], x01_FD[213], x01_FD[214], x01_FD[215], x01_FD[216],
+                x01_FD[217], x01_FD[218], x01_FD[219], x01_FD[220], x01_FD[221], x01_FD[222], x01_FD[223], x01_FD[224],
+                x01_FD[225], x01_FD[226], x01_FD[227], x01_FD[228], x01_FD[229], x01_FD[230], x01_FD[231], x01_FD[232],
+                x01_FD[233], x01_FD[234], x01_FD[235], x01_FD[236], x01_FD[237], x01_FD[238], x01_FD[239], x01_FD[240],
+                x01_FD[241], x01_FD[242], x01_FD[243], x01_FD[244], x01_FD[245], x01_FD[246], x01_FD[247], x01_FD[248],
+                // </editor-fold>
+                x01_FD[249], x01_FD[250], x01_FD[251], x01_FD[252], xFE);
+    }
 
     @Test
     public void test255() throws Throwable {
@@ -1163,5 +1398,38 @@
         } catch (IllegalArgumentException ex) {
             System.out.println("OK: "+ex);
         }
+        MethodHandle mh_a;
+        try {
+            mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2);
+            throw new AssertionError("should not create an arity 255 collector method handle");
+        } catch (IllegalArgumentException ex) {
+            System.out.println("OK: "+ex);
+            mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-3);
+        }
+        try {
+            r = mh_a.invokeExact(
+                    // <editor-fold defaultstate="collapsed" desc="a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], ...">
+                    a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], a[0x05], a[0x06], a[0x07], a[0x08], a[0x09], a[0x0A], a[0x0B], a[0x0C], a[0x0D], a[0x0E], a[0x0F],
+                    a[0x10], a[0x11], a[0x12], a[0x13], a[0x14], a[0x15], a[0x16], a[0x17], a[0x18], a[0x19], a[0x1A], a[0x1B], a[0x1C], a[0x1D], a[0x1E], a[0x1F],
+                    a[0x20], a[0x21], a[0x22], a[0x23], a[0x24], a[0x25], a[0x26], a[0x27], a[0x28], a[0x29], a[0x2A], a[0x2B], a[0x2C], a[0x2D], a[0x2E], a[0x2F],
+                    a[0x30], a[0x31], a[0x32], a[0x33], a[0x34], a[0x35], a[0x36], a[0x37], a[0x38], a[0x39], a[0x3A], a[0x3B], a[0x3C], a[0x3D], a[0x3E], a[0x3F],
+                    a[0x40], a[0x41], a[0x42], a[0x43], a[0x44], a[0x45], a[0x46], a[0x47], a[0x48], a[0x49], a[0x4A], a[0x4B], a[0x4C], a[0x4D], a[0x4E], a[0x4F],
+                    a[0x50], a[0x51], a[0x52], a[0x53], a[0x54], a[0x55], a[0x56], a[0x57], a[0x58], a[0x59], a[0x5A], a[0x5B], a[0x5C], a[0x5D], a[0x5E], a[0x5F],
+                    a[0x60], a[0x61], a[0x62], a[0x63], a[0x64], a[0x65], a[0x66], a[0x67], a[0x68], a[0x69], a[0x6A], a[0x6B], a[0x6C], a[0x6D], a[0x6E], a[0x6F],
+                    a[0x70], a[0x71], a[0x72], a[0x73], a[0x74], a[0x75], a[0x76], a[0x77], a[0x78], a[0x79], a[0x7A], a[0x7B], a[0x7C], a[0x7D], a[0x7E], a[0x7F],
+                    a[0x80], a[0x81], a[0x82], a[0x83], a[0x84], a[0x85], a[0x86], a[0x87], a[0x88], a[0x89], a[0x8A], a[0x8B], a[0x8C], a[0x8D], a[0x8E], a[0x8F],
+                    a[0x90], a[0x91], a[0x92], a[0x93], a[0x94], a[0x95], a[0x96], a[0x97], a[0x98], a[0x99], a[0x9A], a[0x9B], a[0x9C], a[0x9D], a[0x9E], a[0x9F],
+                    a[0xA0], a[0xA1], a[0xA2], a[0xA3], a[0xA4], a[0xA5], a[0xA6], a[0xA7], a[0xA8], a[0xA9], a[0xAA], a[0xAB], a[0xAC], a[0xAD], a[0xAE], a[0xAF],
+                    a[0xB0], a[0xB1], a[0xB2], a[0xB3], a[0xB4], a[0xB5], a[0xB6], a[0xB7], a[0xB8], a[0xB9], a[0xBA], a[0xBB], a[0xBC], a[0xBD], a[0xBE], a[0xBF],
+                    a[0xC0], a[0xC1], a[0xC2], a[0xC3], a[0xC4], a[0xC5], a[0xC6], a[0xC7], a[0xC8], a[0xC9], a[0xCA], a[0xCB], a[0xCC], a[0xCD], a[0xCE], a[0xCF],
+                    a[0xD0], a[0xD1], a[0xD2], a[0xD3], a[0xD4], a[0xD5], a[0xD6], a[0xD7], a[0xD8], a[0xD9], a[0xDA], a[0xDB], a[0xDC], a[0xDD], a[0xDE], a[0xDF],
+                    a[0xE0], a[0xE1], a[0xE2], a[0xE3], a[0xE4], a[0xE5], a[0xE6], a[0xE7], a[0xE8], a[0xE9], a[0xEA], a[0xEB], a[0xEC], a[0xED], a[0xEE], a[0xEF],
+                    a[0xF0], a[0xF1], a[0xF2], a[0xF3], a[0xF4], a[0xF5], a[0xF6], a[0xF7],
+                    // </editor-fold>
+                    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD], a[0xFE]);
+            throw new AssertionError("should not call an arity 255 collector method handle");
+        } catch (LinkageError ex) {
+            System.out.println("OK: "+ex);
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8143232
+ * @summary Test verifies that LF bootstraps properly when run with COMPILE_THRESHOLD set
+ * @compile CompileThresholdBootstrapTest.java
+ * @run testng/othervm -Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=30 test.java.lang.invoke.CompileThresholdBootstrapTest
+ */
+package test.java.lang.invoke;
+
+import java.lang.invoke.MethodHandles;
+import org.testng.*;
+import org.testng.annotations.*;
+
+public final class CompileThresholdBootstrapTest {
+
+    @Test
+    public void testBootstrap() throws Throwable {
+        Assert.assertEquals(0, (int)MethodHandles.constant(int.class, (int)0).invokeExact());
+    }
+
+    public static void main(String ... args) {
+        try {
+            CompileThresholdBootstrapTest test = new CompileThresholdBootstrapTest();
+            test.testBootstrap();
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/FindClassSecurityManager.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/* @test
+ * @run main/othervm/policy=findclass.security.policy/secure=java.lang.SecurityManager -ea -esa test.java.lang.invoke.FindClassSecurityManager
+ */
+
+package test.java.lang.invoke;
+
+import java.lang.invoke.MethodHandles;
+
+public class FindClassSecurityManager {
+    public static void main(String[] args) throws Throwable {
+        assert null != System.getSecurityManager();
+        Class<?> thisClass = FindClassSecurityManager.class;
+        MethodHandles.Lookup lookup = MethodHandles.lookup();
+        Class<?> lookedUp = lookup.findClass(thisClass.getName());
+        assert thisClass == lookedUp;
+        Class<?> accessed = lookup.accessClass(thisClass);
+        assert thisClass == accessed;
+    }
+}
--- a/jdk/test/java/lang/invoke/MethodHandlesTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/java/lang/invoke/MethodHandlesTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -32,6 +32,7 @@
 
 import test.java.lang.invoke.remote.RemoteExample;
 import java.lang.invoke.*;
+import static java.lang.invoke.MethodType.methodType;
 import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.*;
 import java.util.*;
@@ -448,6 +449,7 @@
     }
     public static interface IntExample {
         public void            v0();
+        public default void    vd() { called("vd", this); }
         public static class Impl implements IntExample {
             public void        v0()     { called("Int/v0", this); }
             final String name;
@@ -719,9 +721,10 @@
     public void testFindSpecial0() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("findSpecial");
-        testFindSpecial(SubExample.class, Example.class, void.class, "v0");
-        testFindSpecial(SubExample.class, Example.class, void.class, "pkg_v0");
-        testFindSpecial(RemoteExample.class, PubExample.class, void.class, "Pub/pro_v0");
+        testFindSpecial(SubExample.class, Example.class, void.class, false, "v0");
+        testFindSpecial(SubExample.class, Example.class, void.class, false, "pkg_v0");
+        testFindSpecial(RemoteExample.class, PubExample.class, void.class, false, "Pub/pro_v0");
+        testFindSpecial(Example.class, IntExample.class, void.class, true, "vd");
         // Do some negative testing:
         for (Lookup lookup : new Lookup[]{ PRIVATE, EXAMPLE, PACKAGE, PUBLIC }) {
             testFindSpecial(false, lookup, Object.class, Example.class, void.class, "v0");
@@ -729,11 +732,12 @@
             testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "<init>", int.class);
             testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "<init>", Void.class);
             testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "s0");
+            testFindSpecial(false, lookup, Example.class, IntExample.class, void.class, "v0");
         }
     }
 
     void testFindSpecial(Class<?> specialCaller,
-                         Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
+                         Class<?> defc, Class<?> ret, boolean dflt, String name, Class<?>... params) throws Throwable {
         if (specialCaller == RemoteExample.class) {
             testFindSpecial(false, EXAMPLE,  specialCaller, defc, ret, name, params);
             testFindSpecial(false, PRIVATE,  specialCaller, defc, ret, name, params);
@@ -742,11 +746,11 @@
             testFindSpecial(false, PUBLIC,   specialCaller, defc, ret, name, params);
             return;
         }
-        testFindSpecial(true,  EXAMPLE,  specialCaller, defc, ret, name, params);
-        testFindSpecial(true,  PRIVATE,  specialCaller, defc, ret, name, params);
-        testFindSpecial(false, PACKAGE,  specialCaller, defc, ret, name, params);
-        testFindSpecial(false, SUBCLASS, specialCaller, defc, ret, name, params);
-        testFindSpecial(false, PUBLIC,   specialCaller, defc, ret, name, params);
+        testFindSpecial(true,          EXAMPLE,  specialCaller, defc, ret, name, params);
+        testFindSpecial(true,          PRIVATE,  specialCaller, defc, ret, name, params);
+        testFindSpecial(false || dflt, PACKAGE,  specialCaller, defc, ret, name, params);
+        testFindSpecial(false,         SUBCLASS, specialCaller, defc, ret, name, params);
+        testFindSpecial(false,         PUBLIC,   specialCaller, defc, ret, name, params);
     }
     void testFindSpecial(boolean positive, Lookup lookup, Class<?> specialCaller,
                          Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
@@ -1834,6 +1838,7 @@
     @Test // SLOW
     public void testSpreadArguments() throws Throwable {
         CodeCacheOverflowProcessor.runMHTest(this::testSpreadArguments0);
+        CodeCacheOverflowProcessor.runMHTest(this::testSpreadArguments1);
     }
 
     public void testSpreadArguments0() throws Throwable {
@@ -1842,26 +1847,97 @@
         for (Class<?> argType : new Class<?>[]{Object.class, Integer.class, int.class}) {
             if (verbosity >= 3)
                 System.out.println("spreadArguments "+argType);
+            Class<?> arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass();
             for (int nargs = 0; nargs < 50; nargs++) {
                 if (CAN_TEST_LIGHTLY && nargs > 11)  break;
                 for (int pos = 0; pos <= nargs; pos++) {
                     if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2)  continue;
                     if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
                         continue;
-                    testSpreadArguments(argType, pos, nargs);
+                    testSpreadArguments(argType, arrayType, pos, nargs);
                 }
             }
         }
     }
-    public void testSpreadArguments(Class<?> argType, int pos, int nargs) throws Throwable {
+    public void testSpreadArguments(Class<?> argType, Class<?> arrayType, int pos, int nargs) throws Throwable {
         countTest();
-        Class<?> arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass();
         MethodHandle target2 = varargsArray(arrayType, nargs);
         MethodHandle target = target2.asType(target2.type().generic());
         if (verbosity >= 3)
             System.out.println("spread into "+target2+" ["+pos+".."+nargs+"]");
         Object[] args = randomArgs(target2.type().parameterArray());
         // make sure the target does what we think it does:
+        checkTarget(argType, pos, nargs, target, args);
+        List<Class<?>> newParams = new ArrayList<>(target2.type().parameterList());
+        {   // modify newParams in place
+            List<Class<?>> spreadParams = newParams.subList(pos, nargs);
+            spreadParams.clear(); spreadParams.add(arrayType);
+        }
+        MethodType newType = MethodType.methodType(arrayType, newParams);
+        MethodHandle result = target2.asSpreader(arrayType, nargs-pos);
+        assert(result.type() == newType) : Arrays.asList(result, newType);
+        result = result.asType(newType.generic());
+        Object returnValue;
+        if (pos == 0) {
+            Object args2 = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length));
+            returnValue = result.invokeExact(args2);
+        } else {
+            Object[] args1 = Arrays.copyOfRange(args, 0, pos+1);
+            args1[pos] = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length));
+            returnValue = result.invokeWithArguments(args1);
+        }
+        checkReturnValue(argType, args, result, returnValue);
+    }
+    public void testSpreadArguments1() throws Throwable {
+        if (CAN_SKIP_WORKING)  return;
+        startTest("spreadArguments/pos");
+        for (Class<?> argType : new Class<?>[]{Object.class, Integer.class, int.class}) {
+            if (verbosity >= 3)
+                System.out.println("spreadArguments "+argType);
+            Class<?> arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass();
+            for (int nargs = 0; nargs < 50; nargs++) {
+                if (CAN_TEST_LIGHTLY && nargs > 11)  break;
+                for (int pos = 0; pos <= nargs; pos++) {
+                    if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2)  continue;
+                    if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
+                        continue;
+                    for (int spr = 1; spr < nargs - pos; ++spr) {
+                        if (spr > 4 && spr != 7 && spr != 11 && spr != 20 && spr < nargs - pos - 4) continue;
+                        testSpreadArguments(argType, arrayType, pos, spr, nargs);
+                    }
+                }
+            }
+        }
+    }
+    public void testSpreadArguments(Class<?> argType, Class<?> arrayType, int pos, int spread, int nargs) throws Throwable {
+        countTest();
+        MethodHandle target2 = varargsArray(arrayType, nargs);
+        MethodHandle target = target2.asType(target2.type().generic());
+        if (verbosity >= 3)
+            System.out.println("spread into " + target2 + " [" + pos + ".." + (pos + spread) + "[");
+        Object[] args = randomArgs(target2.type().parameterArray());
+        // make sure the target does what we think it does:
+        checkTarget(argType, pos, nargs, target, args);
+        List<Class<?>> newParams = new ArrayList<>(target2.type().parameterList());
+        {   // modify newParams in place
+            List<Class<?>> spreadParams = newParams.subList(pos, pos + spread);
+            spreadParams.clear();
+            spreadParams.add(arrayType);
+        }
+        MethodType newType = MethodType.methodType(arrayType, newParams);
+        MethodHandle result = target2.asSpreader(pos, arrayType, spread);
+        assert (result.type() == newType) : Arrays.asList(result, newType);
+        result = result.asType(newType.generic());
+        // args1 has nargs-spread entries, plus one for the to-be-spread array
+        int args1Length = nargs - (spread - 1);
+        Object[] args1 = new Object[args1Length];
+        System.arraycopy(args, 0, args1, 0, pos);
+        args1[pos] = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, pos + spread));
+        System.arraycopy(args, pos + spread, args1, pos + 1, nargs - spread - pos);
+        Object returnValue = result.invokeWithArguments(args1);
+        checkReturnValue(argType, args, result, returnValue);
+    }
+    private static void checkTarget(Class<?> argType, int pos, int nargs, MethodHandle target, Object[] args) throws Throwable {
         if (pos == 0 && nargs < 5 && !argType.isPrimitive()) {
             Object[] check = (Object[]) target.invokeWithArguments(args);
             assertArrayEquals(args, check);
@@ -1880,24 +1956,8 @@
                     break;
             }
         }
-        List<Class<?>> newParams = new ArrayList<>(target2.type().parameterList());
-        {   // modify newParams in place
-            List<Class<?>> spreadParams = newParams.subList(pos, nargs);
-            spreadParams.clear(); spreadParams.add(arrayType);
-        }
-        MethodType newType = MethodType.methodType(arrayType, newParams);
-        MethodHandle result = target2.asSpreader(arrayType, nargs-pos);
-        assert(result.type() == newType) : Arrays.asList(result, newType);
-        result = result.asType(newType.generic());
-        Object returnValue;
-        if (pos == 0) {
-            Object args2 = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length));
-            returnValue = result.invokeExact(args2);
-        } else {
-            Object[] args1 = Arrays.copyOfRange(args, 0, pos+1);
-            args1[pos] = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length));
-            returnValue = result.invokeWithArguments(args1);
-        }
+    }
+    private static void checkReturnValue(Class<?> argType, Object[] args, MethodHandle result, Object returnValue) {
         String argstr = Arrays.toString(args);
         if (!argType.isPrimitive()) {
             Object[] rv = (Object[]) returnValue;
@@ -1932,6 +1992,7 @@
     @Test // SLOW
     public void testAsCollector() throws Throwable {
         CodeCacheOverflowProcessor.runMHTest(this::testAsCollector0);
+        CodeCacheOverflowProcessor.runMHTest(this::testAsCollector1);
     }
 
     public void testAsCollector0() throws Throwable {
@@ -1974,6 +2035,51 @@
 //        collectedArgs[pos] = Arrays.asList((Object[]) collectedArgs[pos]);
         assertArrayEquals(collectedArgs, returnValue);
     }
+    public void testAsCollector1() throws Throwable {
+        if (CAN_SKIP_WORKING)  return;
+        startTest("asCollector/pos");
+        for (Class<?> argType : new Class<?>[]{Object.class, Integer.class, int.class}) {
+            if (verbosity >= 3)
+                System.out.println("asCollector/pos "+argType);
+            for (int nargs = 0; nargs < 50; nargs++) {
+                if (CAN_TEST_LIGHTLY && nargs > 11)  break;
+                for (int pos = 0; pos <= nargs; pos++) {
+                    if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2)  continue;
+                    if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
+                        continue;
+                    for (int coll = 1; coll < nargs - pos; ++coll) {
+                        if (coll > 4 && coll != 7 && coll != 11 && coll != 20 && coll < nargs - pos - 4) continue;
+                        testAsCollector(argType, pos, coll, nargs);
+                    }
+                }
+            }
+        }
+    }
+    public void testAsCollector(Class<?> argType, int pos, int collect, int nargs) throws Throwable {
+        countTest();
+        // fake up a MH with the same type as the desired adapter:
+        MethodHandle fake = varargsArray(nargs);
+        fake = changeArgTypes(fake, argType);
+        MethodType newType = fake.type();
+        Object[] args = randomArgs(newType.parameterArray());
+        // here is what should happen:
+        // new arg list has "collect" less arguments, but one extra for collected arguments array
+        int collectedLength = nargs-(collect-1);
+        Object[] collectedArgs = new Object[collectedLength];
+        System.arraycopy(args, 0, collectedArgs, 0, pos);
+        collectedArgs[pos] = Arrays.copyOfRange(args, pos, pos+collect);
+        System.arraycopy(args, pos+collect, collectedArgs, pos+1, args.length-(pos+collect));
+        // here is the MH which will witness the collected argument part (not tail!):
+        MethodHandle target = varargsArray(collectedLength);
+        target = changeArgTypes(target, 0, pos, argType);
+        target = changeArgTypes(target, pos, pos+1, Object[].class);
+        target = changeArgTypes(target, pos+1, collectedLength, argType);
+        if (verbosity >= 3)
+            System.out.println("collect "+collect+" from "+Arrays.asList(args)+" ["+pos+".."+(pos+collect)+"[");
+        MethodHandle result = target.asCollector(pos, Object[].class, collect).asType(newType);
+        Object[] returnValue = (Object[]) result.invokeWithArguments(args);
+        assertArrayEquals(collectedArgs, returnValue);
+    }
 
     @Test // SLOW
     public void testInsertArguments() throws Throwable {
@@ -2117,21 +2223,29 @@
     public void testCollectArguments0() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("collectArguments");
-        testFoldOrCollectArguments(true);
+        testFoldOrCollectArguments(true, false);
     }
 
     @Test
     public void testFoldArguments() throws Throwable {
         CodeCacheOverflowProcessor.runMHTest(this::testFoldArguments0);
+        CodeCacheOverflowProcessor.runMHTest(this::testFoldArguments1);
     }
 
     public void testFoldArguments0() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("foldArguments");
-        testFoldOrCollectArguments(false);
+        testFoldOrCollectArguments(false, false);
     }
 
-    void testFoldOrCollectArguments(boolean isCollect) throws Throwable {
+    public void testFoldArguments1() throws Throwable {
+        if (CAN_SKIP_WORKING) return;
+        startTest("foldArguments/pos");
+        testFoldOrCollectArguments(false, true);
+    }
+
+    void testFoldOrCollectArguments(boolean isCollect, boolean withFoldPos) throws Throwable {
+        assert !(isCollect && withFoldPos); // exclude illegal argument combination
         for (Class<?> lastType : new Class<?>[]{ Object.class, String.class, int.class }) {
             for (Class<?> collectType : new Class<?>[]{ Object.class, String.class, int.class, void.class }) {
                 int maxArity = 10;
@@ -2146,7 +2260,7 @@
                         if (!mixArgs(argTypes, mix, argTypesSeen))  continue;
                         for (int collect = 0; collect <= nargs; collect++) {
                             for (int pos = 0; pos <= nargs - collect; pos++) {
-                                testFoldOrCollectArguments(argTypes, pos, collect, collectType, lastType, isCollect);
+                                testFoldOrCollectArguments(argTypes, pos, collect, collectType, lastType, isCollect, withFoldPos);
                             }
                         }
                     }
@@ -2186,13 +2300,14 @@
                                     int pos, int fold, // position and length of the folded arguments
                                     Class<?> combineType, // type returned from the combiner
                                     Class<?> lastType,  // type returned from the target
-                                    boolean isCollect) throws Throwable {
+                                    boolean isCollect,
+                                    boolean withFoldPos) throws Throwable {
         int nargs = argTypes.size();
-        if (pos != 0 && !isCollect)  return;  // can fold only at pos=0 for now
+        if (pos != 0 && !isCollect && !withFoldPos)  return;  // test MethodHandles.foldArguments(MH,MH) only for pos=0
         countTest();
         List<Class<?>> combineArgTypes = argTypes.subList(pos, pos + fold);
         List<Class<?>> targetArgTypes = new ArrayList<>(argTypes);
-        if (isCollect)  // does targret see arg[pos..pos+cc-1]?
+        if (isCollect)  // does target see arg[pos..pos+cc-1]?
             targetArgTypes.subList(pos, pos + fold).clear();
         if (combineType != void.class)
             targetArgTypes.add(pos, combineType);
@@ -2205,7 +2320,7 @@
         if (isCollect)
             target2 = MethodHandles.collectArguments(target, pos, combine);
         else
-            target2 = MethodHandles.foldArguments(target, combine);
+            target2 = withFoldPos ? MethodHandles.foldArguments(target, pos, combine) : MethodHandles.foldArguments(target, combine);
         // Simulate expected effect of combiner on arglist:
         List<Object> expectedList = new ArrayList<>(argsToPass);
         List<Object> argsToFold = expectedList.subList(pos, pos + fold);
@@ -2541,6 +2656,203 @@
     }
 
     @Test
+    public void testGenericLoopCombinator() throws Throwable {
+        CodeCacheOverflowProcessor.runMHTest(this::testGenericLoopCombinator0);
+    }
+    public void testGenericLoopCombinator0() throws Throwable {
+        if (CAN_SKIP_WORKING) return;
+        startTest("loop");
+        // Test as follows:
+        // * Have an increasing number of loop-local state. Local state type diversity grows with the number.
+        // * Initializers set the starting value of loop-local state from the corresponding loop argument.
+        // * For each local state element, there is a predicate - for all state combinations, exercise all predicates.
+        // * Steps modify each local state element in each iteration.
+        // * Finalizers group all local state elements into a resulting array. Verify end values.
+        // * Exercise both pre- and post-checked loops.
+        // Local state types, start values, predicates, and steps:
+        // * int a, 0, a < 7, a = a + 1
+        // * double b, 7.0, b > 0.5, b = b / 2.0
+        // * String c, "start", c.length <= 9, c = c + a
+        final Class<?>[] argTypes = new Class<?>[] {int.class, double.class, String.class};
+        final Object[][] args = new Object[][] {
+            new Object[]{0              },
+            new Object[]{0, 7.0         },
+            new Object[]{0, 7.0, "start"}
+        };
+        // These are the expected final state tuples for argument type tuple / predicate combinations, for pre- and
+        // post-checked loops:
+        final Object[][] preCheckedResults = new Object[][] {
+            new Object[]{7                           }, // (int) / int
+            new Object[]{7, 0.0546875                }, // (int,double) / int
+            new Object[]{5, 0.4375                   }, // (int,double) / double
+            new Object[]{7, 0.0546875, "start1234567"}, // (int,double,String) / int
+            new Object[]{5, 0.4375,    "start1234"   }, // (int,double,String) / double
+            new Object[]{6, 0.109375,  "start12345"  }  // (int,double,String) / String
+        };
+        final Object[][] postCheckedResults = new Object[][] {
+            new Object[]{7                         }, // (int) / int
+            new Object[]{7, 0.109375               }, // (int,double) / int
+            new Object[]{4, 0.4375                 }, // (int,double) / double
+            new Object[]{7, 0.109375, "start123456"}, // (int,double,String) / int
+            new Object[]{4, 0.4375,   "start123"   }, // (int,double,String) / double
+            new Object[]{5, 0.21875,  "start12345" }  // (int,double,String) / String
+        };
+        final Lookup l = MethodHandles.lookup();
+        final Class<?> MHT = MethodHandlesTest.class;
+        final Class<?> B = boolean.class;
+        final Class<?> I = int.class;
+        final Class<?> D = double.class;
+        final Class<?> S = String.class;
+        final MethodHandle hip = l.findStatic(MHT, "loopIntPred", methodType(B, I));
+        final MethodHandle hdp = l.findStatic(MHT, "loopDoublePred", methodType(B, I, D));
+        final MethodHandle hsp = l.findStatic(MHT, "loopStringPred", methodType(B, I, D, S));
+        final MethodHandle his = l.findStatic(MHT, "loopIntStep", methodType(I, I));
+        final MethodHandle hds = l.findStatic(MHT, "loopDoubleStep", methodType(D, I, D));
+        final MethodHandle hss = l.findStatic(MHT, "loopStringStep", methodType(S, I, D, S));
+        final MethodHandle[] preds = new MethodHandle[] {hip, hdp, hsp};
+        final MethodHandle[] steps = new MethodHandle[] {his, hds, hss};
+        for (int nargs = 1, useResultsStart = 0; nargs <= argTypes.length; useResultsStart += nargs++) {
+            Class<?>[] useArgTypes = Arrays.copyOf(argTypes, nargs, Class[].class);
+            MethodHandle[] usePreds = Arrays.copyOf(preds, nargs, MethodHandle[].class);
+            MethodHandle[] useSteps = Arrays.copyOf(steps, nargs, MethodHandle[].class);
+            Object[] useArgs = args[nargs - 1];
+            Object[][] usePreCheckedResults = new Object[nargs][];
+            Object[][] usePostCheckedResults = new Object[nargs][];
+            System.arraycopy(preCheckedResults, useResultsStart, usePreCheckedResults, 0, nargs);
+            System.arraycopy(postCheckedResults, useResultsStart, usePostCheckedResults, 0, nargs);
+            testGenericLoopCombinator(nargs, useArgTypes, usePreds, useSteps, useArgs, usePreCheckedResults,
+                    usePostCheckedResults);
+        }
+    }
+    void testGenericLoopCombinator(int nargs, Class<?>[] argTypes, MethodHandle[] preds, MethodHandle[] steps,
+                                   Object[] args, Object[][] preCheckedResults, Object[][] postCheckedResults)
+            throws Throwable {
+        List<Class<?>> lArgTypes = Arrays.asList(argTypes);
+        // Predicate and step handles are passed in as arguments, initializer and finalizer handles are constructed here
+        // from the available information.
+        MethodHandle[] inits = new MethodHandle[nargs];
+        for (int i = 0; i < nargs; ++i) {
+            MethodHandle h;
+            // Initializers are meant to return whatever they are passed at a given argument position. This means that
+            // additional arguments may have to be appended and prepended.
+            h = MethodHandles.identity(argTypes[i]);
+            if (i < nargs - 1) {
+                h = MethodHandles.dropArguments(h, 1, lArgTypes.subList(i + 1, nargs));
+            }
+            if (i > 0) {
+                h = MethodHandles.dropArguments(h, 0, lArgTypes.subList(0, i));
+            }
+            inits[i] = h;
+        }
+        // Finalizers are all meant to collect all of the loop-local state in a single array and return that. Local
+        // state is passed before the loop args. Construct such a finalizer by first taking a varargsArray collector for
+        // the number of local state arguments, and then appending the loop args as to-be-dropped arguments.
+        MethodHandle[] finis = new MethodHandle[nargs];
+        MethodHandle genericFini = MethodHandles.dropArguments(
+                varargsArray(nargs).asType(methodType(Object[].class, lArgTypes)), nargs, lArgTypes);
+        Arrays.fill(finis, genericFini);
+        // The predicate and step handles' signatures need to be extended. They currently just accept local state args;
+        // append possibly missing local state args and loop args using dropArguments.
+        for (int i = 0; i < nargs; ++i) {
+            List<Class<?>> additionalLocalStateArgTypes = lArgTypes.subList(i + 1, nargs);
+            preds[i] = MethodHandles.dropArguments(
+                    MethodHandles.dropArguments(preds[i], i + 1, additionalLocalStateArgTypes), nargs, lArgTypes);
+            steps[i] = MethodHandles.dropArguments(
+                    MethodHandles.dropArguments(steps[i], i + 1, additionalLocalStateArgTypes), nargs, lArgTypes);
+        }
+        // Iterate over all of the predicates, using only one of them at a time.
+        for (int i = 0; i < nargs; ++i) {
+            MethodHandle[] usePreds;
+            if (nargs == 1) {
+                usePreds = preds;
+            } else {
+                // Create an all-null preds array, and only use one predicate in this iteration. The null entries will
+                // be substituted with true predicates by the loop combinator.
+                usePreds = new MethodHandle[nargs];
+                usePreds[i] = preds[i];
+            }
+            // Go for it.
+            if (verbosity >= 3) {
+                System.out.println("calling loop for argument types " + lArgTypes + " with predicate at index " + i);
+                if (verbosity >= 5) {
+                    System.out.println("predicates: " + Arrays.asList(usePreds));
+                }
+            }
+            MethodHandle[] preInits = new MethodHandle[nargs + 1];
+            MethodHandle[] prePreds = new MethodHandle[nargs + 1];
+            MethodHandle[] preSteps = new MethodHandle[nargs + 1];
+            MethodHandle[] preFinis = new MethodHandle[nargs + 1];
+            System.arraycopy(inits, 0, preInits, 1, nargs);
+            System.arraycopy(usePreds, 0, prePreds, 0, nargs); // preds are offset by 1 for pre-checked loops
+            System.arraycopy(steps, 0, preSteps, 1, nargs);
+            System.arraycopy(finis, 0, preFinis, 0, nargs); // finis are also offset by 1 for pre-checked loops
+            // Convert to clause-major form.
+            MethodHandle[][] preClauses = new MethodHandle[nargs+1][4];
+            MethodHandle[][] postClauses = new MethodHandle[nargs][4];
+            toClauseMajor(preClauses, preInits, preSteps, prePreds, preFinis);
+            toClauseMajor(postClauses, inits, steps, usePreds, finis);
+            MethodHandle pre = MethodHandles.loop(preClauses);
+            MethodHandle post = MethodHandles.loop(postClauses);
+            Object[] preResults = (Object[]) pre.invokeWithArguments(args);
+            if (verbosity >= 4) {
+                System.out.println("pre-checked: expected " + Arrays.asList(preCheckedResults[i]) + ", actual " +
+                        Arrays.asList(preResults));
+            }
+            Object[] postResults = (Object[]) post.invokeWithArguments(args);
+            if (verbosity >= 4) {
+                System.out.println("post-checked: expected " + Arrays.asList(postCheckedResults[i]) + ", actual " +
+                        Arrays.asList(postResults));
+            }
+            assertArrayEquals(preCheckedResults[i], preResults);
+            assertArrayEquals(postCheckedResults[i], postResults);
+        }
+    }
+    static void toClauseMajor(MethodHandle[][] clauses, MethodHandle[] init, MethodHandle[] step, MethodHandle[] pred, MethodHandle[] fini) {
+        for (int i = 0; i < clauses.length; ++i) {
+            clauses[i][0] = init[i];
+            clauses[i][1] = step[i];
+            clauses[i][2] = pred[i];
+            clauses[i][3] = fini[i];
+        }
+    }
+    static boolean loopIntPred(int a) {
+        if (verbosity >= 5) {
+            System.out.println("int pred " + a + " -> " + (a < 7));
+        }
+        return a < 7;
+    }
+    static boolean loopDoublePred(int a, double b) {
+        if (verbosity >= 5) {
+            System.out.println("double pred (a=" + a + ") " + b + " -> " + (b > 0.5));
+        }
+        return b > 0.5;
+    }
+    static boolean loopStringPred(int a, double b, String c) {
+        if (verbosity >= 5) {
+            System.out.println("String pred (a=" + a + ",b=" + b + ") " + c + " -> " + (c.length() <= 9));
+        }
+        return c.length() <= 9;
+    }
+    static int loopIntStep(int a) {
+        if (verbosity >= 5) {
+            System.out.println("int step " + a + " -> " + (a + 1));
+        }
+        return a + 1;
+    }
+    static double loopDoubleStep(int a, double b) {
+        if (verbosity >= 5) {
+            System.out.println("double step (a=" + a + ") " + b + " -> " + (b / 2.0));
+        }
+        return b / 2.0;
+    }
+    static String loopStringStep(int a, double b, String c) {
+        if (verbosity >= 5) {
+            System.out.println("String step (a=" + a + ",b=" + b + ") " + c + " -> " + (c + a));
+        }
+        return c + a;
+    }
+
+    @Test
     public void testThrowException() throws Throwable {
         CodeCacheOverflowProcessor.runMHTest(this::testThrowException0);
     }
@@ -2576,12 +2888,107 @@
     }
 
     @Test
+    public void testTryFinally() throws Throwable {
+        CodeCacheOverflowProcessor.runMHTest(this::testTryFinally0);
+    }
+    public void testTryFinally0() throws Throwable {
+        if (CAN_SKIP_WORKING) return;
+        startTest("tryFinally");
+        String inputMessage = "returned";
+        String augmentedMessage = "augmented";
+        String thrownMessage = "thrown";
+        String rethrownMessage = "rethrown";
+        // Test these cases:
+        // * target returns, cleanup passes through
+        // * target returns, cleanup augments
+        // * target throws, cleanup augments and returns
+        // * target throws, cleanup augments and rethrows
+        MethodHandle target = MethodHandles.identity(String.class);
+        MethodHandle targetThrow = MethodHandles.dropArguments(
+                MethodHandles.throwException(String.class, Exception.class).bindTo(new Exception(thrownMessage)), 0, String.class);
+        MethodHandle cleanupPassThrough = MethodHandles.dropArguments(MethodHandles.identity(String.class), 0,
+                Throwable.class, String.class);
+        MethodHandle cleanupAugment = MethodHandles.dropArguments(MethodHandles.constant(String.class, augmentedMessage),
+                0, Throwable.class, String.class, String.class);
+        MethodHandle cleanupCatch = MethodHandles.dropArguments(MethodHandles.constant(String.class, thrownMessage), 0,
+                Throwable.class, String.class, String.class);
+        MethodHandle cleanupThrow = MethodHandles.dropArguments(MethodHandles.throwException(String.class, Exception.class).
+                bindTo(new Exception(rethrownMessage)), 0, Throwable.class, String.class, String.class);
+        testTryFinally(target, cleanupPassThrough, inputMessage, inputMessage, false);
+        testTryFinally(target, cleanupAugment, inputMessage, augmentedMessage, false);
+        testTryFinally(targetThrow, cleanupCatch, inputMessage, thrownMessage, true);
+        testTryFinally(targetThrow, cleanupThrow, inputMessage, rethrownMessage, true);
+        // Test the same cases as above for void targets and cleanups.
+        MethodHandles.Lookup lookup = MethodHandles.lookup();
+        Class<?> C = this.getClass();
+        MethodType targetType = methodType(void.class, String[].class);
+        MethodType cleanupType = methodType(void.class, Throwable.class, String[].class);
+        MethodHandle vtarget = lookup.findStatic(C, "vtarget", targetType);
+        MethodHandle vtargetThrow = lookup.findStatic(C, "vtargetThrow", targetType);
+        MethodHandle vcleanupPassThrough = lookup.findStatic(C, "vcleanupPassThrough", cleanupType);
+        MethodHandle vcleanupAugment = lookup.findStatic(C, "vcleanupAugment", cleanupType);
+        MethodHandle vcleanupCatch = lookup.findStatic(C, "vcleanupCatch", cleanupType);
+        MethodHandle vcleanupThrow = lookup.findStatic(C, "vcleanupThrow", cleanupType);
+        testTryFinally(vtarget, vcleanupPassThrough, inputMessage, inputMessage, false);
+        testTryFinally(vtarget, vcleanupAugment, inputMessage, augmentedMessage, false);
+        testTryFinally(vtargetThrow, vcleanupCatch, inputMessage, thrownMessage, true);
+        testTryFinally(vtargetThrow, vcleanupThrow, inputMessage, rethrownMessage, true);
+    }
+    void testTryFinally(MethodHandle target, MethodHandle cleanup, String input, String msg, boolean mustCatch)
+            throws Throwable {
+        countTest();
+        MethodHandle tf = MethodHandles.tryFinally(target, cleanup);
+        String result = null;
+        boolean isVoid = target.type().returnType() == void.class;
+        String[] argArray = new String[]{input};
+        try {
+            if (isVoid) {
+                tf.invoke(argArray);
+            } else {
+                result = (String) tf.invoke(input);
+            }
+        } catch (Throwable t) {
+            assertTrue(mustCatch);
+            assertEquals(msg, t.getMessage());
+            return;
+        }
+        assertFalse(mustCatch);
+        if (isVoid) {
+            assertEquals(msg, argArray[0]);
+        } else {
+            assertEquals(msg, result);
+        }
+    }
+    static void vtarget(String[] a) {
+        // naught, akin to identity
+    }
+    static void vtargetThrow(String[] a) throws Exception {
+        throw new Exception("thrown");
+    }
+    static void vcleanupPassThrough(Throwable t, String[] a) {
+        assertNull(t);
+        // naught, akin to identity
+    }
+    static void vcleanupAugment(Throwable t, String[] a) {
+        assertNull(t);
+        a[0] = "augmented";
+    }
+    static void vcleanupCatch(Throwable t, String[] a) {
+        assertNotNull(t);
+        a[0] = "caught";
+    }
+    static void vcleanupThrow(Throwable t, String[] a) throws Exception {
+        assertNotNull(t);
+        throw new Exception("rethrown");
+    }
+
+    @Test
     public void testInterfaceCast() throws Throwable {
         CodeCacheOverflowProcessor.runMHTest(this::testInterfaceCast0);
     }
 
     public void testInterfaceCast0() throws Throwable {
-        //if (CAN_SKIP_WORKING)  return;
+        if (CAN_SKIP_WORKING)  return;
         startTest("interfaceCast");
         assert( (((Object)"foo") instanceof CharSequence));
         assert(!(((Object)"foo") instanceof Iterable));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/T8139885.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,1082 @@
+/*
+ * 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.
+ */
+
+/* @test
+ * @run testng/othervm -ea -esa test.java.lang.invoke.T8139885
+ */
+
+package test.java.lang.invoke;
+
+import java.io.StringWriter;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.util.*;
+
+import static java.lang.invoke.MethodType.methodType;
+
+import static org.testng.AssertJUnit.*;
+
+import org.testng.annotations.*;
+
+/**
+ * Example-scale and negative tests for JEP 274 extensions.
+ */
+public class T8139885 {
+
+    static final Lookup LOOKUP = MethodHandles.lookup();
+
+    //
+    // Tests.
+    //
+
+    @Test
+    public static void testLoopFac() throws Throwable {
+        MethodHandle[] counterClause = new MethodHandle[]{Fac.MH_zero, Fac.MH_inc};
+        MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin};
+        MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
+        assertEquals(Fac.MT_fac, loop.type());
+        assertEquals(120, loop.invoke(5));
+    }
+
+    @Test
+    public static void testLoopFacNullInit() throws Throwable {
+        // null initializer for counter, should initialize to 0
+        MethodHandle[] counterClause = new MethodHandle[]{null, Fac.MH_inc};
+        MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin};
+        MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
+        assertEquals(Fac.MT_fac, loop.type());
+        assertEquals(120, loop.invoke(5));
+    }
+
+    @Test
+    public static void testLoopVoid1() throws Throwable {
+        // construct a post-checked loop that only does one iteration and has a void body and void local state
+        MethodHandle loop = MethodHandles.loop(new MethodHandle[]{Empty.MH_f, Empty.MH_f, Empty.MH_pred, null});
+        assertEquals(MethodType.methodType(void.class), loop.type());
+        loop.invoke();
+    }
+
+    @Test
+    public static void testLoopVoid2() throws Throwable {
+        // construct a post-checked loop that only does one iteration and has a void body and void local state,
+        // initialized implicitly from the step type
+        MethodHandle loop = MethodHandles.loop(new MethodHandle[]{null, Empty.MH_f, Empty.MH_pred, null});
+        assertEquals(MethodType.methodType(void.class), loop.type());
+        loop.invoke();
+    }
+
+    @Test
+    public static void testLoopFacWithVoidState() throws Throwable {
+        // like testLoopFac, but with additional void state that outputs a dot
+        MethodHandle[] counterClause = new MethodHandle[]{Fac.MH_zero, Fac.MH_inc};
+        MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin};
+        MethodHandle[] dotClause = new MethodHandle[]{null, Fac.MH_dot};
+        MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause, dotClause);
+        assertEquals(Fac.MT_fac, loop.type());
+        assertEquals(120, loop.invoke(5));
+    }
+
+    @Test
+    public static void testLoopNegative() throws Throwable {
+        MethodHandle mh_loop =
+                LOOKUP.findStatic(MethodHandles.class, "loop", methodType(MethodHandle.class, MethodHandle[][].class));
+        MethodHandle i0 = MethodHandles.constant(int.class, 0);
+        MethodHandle ii = MethodHandles.dropArguments(i0, 0, int.class, int.class);
+        MethodHandle id = MethodHandles.dropArguments(i0, 0, int.class, double.class);
+        MethodHandle i3 = MethodHandles.dropArguments(i0, 0, int.class, int.class, int.class);
+        List<MethodHandle> inits = Arrays.asList(ii, id, i3);
+        List<Class<?>> ints = Arrays.asList(int.class, int.class, int.class);
+        List<MethodHandle> finis = Arrays.asList(Fac.MH_fin, Fac.MH_inc, Counted.MH_step);
+        List<MethodHandle> preds1 = Arrays.asList(null, null, null);
+        List<MethodHandle> preds2 = Arrays.asList(null, Fac.MH_fin, null);
+        MethodHandle eek = MethodHandles.dropArguments(i0, 0, int.class, int.class, double.class);
+        List<MethodHandle> nesteps = Arrays.asList(Fac.MH_inc, eek, Fac.MH_dot);
+        List<MethodHandle> nepreds = Arrays.asList(null, Fac.MH_pred, null);
+        List<MethodHandle> nefinis = Arrays.asList(null, Fac.MH_fin, null);
+        MethodHandle[][][] cases = {
+                null,
+                {},
+                {{null, Fac.MH_inc}, {Fac.MH_one, null, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin}},
+                {{null, Fac.MH_inc}, null},
+                {{Fac.MH_zero, Fac.MH_dot}},
+                {{ii}, {id}, {i3}},
+                {{null, Fac.MH_inc, null, Fac.MH_fin}, {null, Fac.MH_inc, null, Fac.MH_inc},
+                        {null, Counted.MH_start, null, Counted.MH_step}},
+                {{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, Fac.MH_mult, null, Fac.MH_fin}, {null, Fac.MH_dot}},
+                {{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, Fac.MH_mult, Fac.MH_fin, Fac.MH_fin}, {null, Fac.MH_dot}},
+                {{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, eek, Fac.MH_pred, Fac.MH_fin}, {null, Fac.MH_dot}}
+        };
+        String[] messages = {
+                "null or no clauses passed",
+                "null or no clauses passed",
+                "All loop clauses must be represented as MethodHandle arrays with at most 4 elements.",
+                "null clauses are not allowed",
+                "clause 0: init and step return types must match: int != void",
+                "found non-effectively identical init parameter type lists: " + inits + " (common suffix: " + ints + ")",
+                "found non-identical finalizer return types: " + finis + " (return type: int)",
+                "no predicate found: " + preds1,
+                "predicates must have boolean return type: " + preds2,
+                "found non-effectively identical parameter type lists:\nstep: " + nesteps + "\npred: " + nepreds +
+                        "\nfini: " + nefinis + " (common parameter sequence: " + ints + ")"
+        };
+        for (int i = 0; i < cases.length; ++i) {
+            boolean caught = false;
+            try {
+                mh_loop.invokeWithArguments(cases[i]);
+            } catch (IllegalArgumentException iae) {
+                assertEquals(messages[i], iae.getMessage());
+                caught = true;
+            }
+            assertTrue(caught);
+        }
+    }
+
+    @Test
+    public static void testWhileLoop() throws Throwable {
+        // int i = 0; while (i < limit) { ++i; } return i; => limit
+        MethodHandle loop = MethodHandles.whileLoop(While.MH_zero, While.MH_pred, While.MH_step);
+        assertEquals(While.MT_while, loop.type());
+        assertEquals(23, loop.invoke(23));
+    }
+
+    @Test
+    public static void testWhileLoopNoIteration() throws Throwable {
+        // a while loop that never executes its body because the predicate evaluates to false immediately
+        MethodHandle loop = MethodHandles.whileLoop(While.MH_initString, While.MH_predString, While.MH_stepString);
+        assertEquals(While.MT_string, loop.type());
+        assertEquals("a", loop.invoke());
+    }
+
+    @Test
+    public static void testDoWhileLoop() throws Throwable {
+        // int i = 0; do { ++i; } while (i < limit); return i; => limit
+        MethodHandle loop = MethodHandles.doWhileLoop(While.MH_zero, While.MH_step, While.MH_pred);
+        assertEquals(While.MT_while, loop.type());
+        assertEquals(23, loop.invoke(23));
+    }
+
+    @Test
+    public static void testWhileZip() throws Throwable {
+        MethodHandle loop = MethodHandles.doWhileLoop(While.MH_zipInitZip, While.MH_zipStep, While.MH_zipPred);
+        assertEquals(While.MT_zip, loop.type());
+        List<String> a = Arrays.asList("a", "b", "c", "d");
+        List<String> b = Arrays.asList("e", "f", "g", "h");
+        List<String> zipped = Arrays.asList("a", "e", "b", "f", "c", "g", "d", "h");
+        assertEquals(zipped, (List<String>) loop.invoke(a.iterator(), b.iterator()));
+    }
+
+    @Test
+    public static void testCountedLoop() throws Throwable {
+        // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s; => a variation on a well known theme
+        MethodHandle fit13 = MethodHandles.constant(int.class, 13);
+        MethodHandle loop = MethodHandles.countedLoop(fit13, Counted.MH_start, Counted.MH_step);
+        assertEquals(Counted.MT_counted, loop.type());
+        assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
+    }
+
+    @Test
+    public static void testCountedArrayLoop() throws Throwable {
+        // int[] a = new int[]{0}; for (int i = 0; i < 13; ++i) { ++a[0]; } => a[0] == 13
+        MethodHandle fit13 = MethodHandles.dropArguments(MethodHandles.constant(int.class, 13), 0, int[].class);
+        MethodHandle loop = MethodHandles.countedLoop(fit13, null, Counted.MH_stepUpdateArray);
+        assertEquals(Counted.MT_arrayCounted, loop.type());
+        int[] a = new int[]{0};
+        loop.invoke(a);
+        assertEquals(13, a[0]);
+    }
+
+    @Test
+    public static void testCountedPrintingLoop() throws Throwable {
+        MethodHandle fit5 = MethodHandles.constant(int.class, 5);
+        MethodHandle loop = MethodHandles.countedLoop(fit5, null, Counted.MH_printHello);
+        assertEquals(Counted.MT_countedPrinting, loop.type());
+        loop.invoke();
+    }
+
+    @Test
+    public static void testCountedRangeLoop() throws Throwable {
+        // String s = "Lambdaman!"; for (int i = -5; i < 8; ++i) { s = "na " + s; } return s; => a well known theme
+        MethodHandle fitm5 = MethodHandles.dropArguments(Counted.MH_m5, 0, String.class);
+        MethodHandle fit8 = MethodHandles.dropArguments(Counted.MH_8, 0, String.class);
+        MethodHandle loop = MethodHandles.countedLoop(fitm5, fit8, Counted.MH_start, Counted.MH_step);
+        assertEquals(Counted.MT_counted, loop.type());
+        assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
+    }
+
+    @Test
+    public static void testIterateSum() throws Throwable {
+        // Integer[] a = new Integer[]{1,2,3,4,5,6}; int sum = 0; for (int e : a) { sum += e; } return sum; => 21
+        MethodHandle loop = MethodHandles.iteratedLoop(Iterate.MH_sumIterator, Iterate.MH_sumInit, Iterate.MH_sumStep);
+        assertEquals(Iterate.MT_sum, loop.type());
+        assertEquals(21, loop.invoke(new Integer[]{1, 2, 3, 4, 5, 6}));
+    }
+
+    @Test
+    public static void testIterateReverse() throws Throwable {
+        MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_reverseInit, Iterate.MH_reverseStep);
+        assertEquals(Iterate.MT_reverse, loop.type());
+        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
+        List<String> reversedList = Arrays.asList("e", "d", "c", "b", "a");
+        assertEquals(reversedList, (List<String>) loop.invoke(list));
+    }
+
+    @Test
+    public static void testIterateLength() throws Throwable {
+        MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_lengthInit, Iterate.MH_lengthStep);
+        assertEquals(Iterate.MT_length, loop.type());
+        List<Double> list = Arrays.asList(23.0, 148.0, 42.0);
+        assertEquals(list.size(), (int) loop.invoke(list));
+    }
+
+    @Test
+    public static void testIterateMap() throws Throwable {
+        MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_mapInit, Iterate.MH_mapStep);
+        assertEquals(Iterate.MT_map, loop.type());
+        List<String> list = Arrays.asList("Hello", "world", "!");
+        List<String> upList = Arrays.asList("HELLO", "WORLD", "!");
+        assertEquals(upList, (List<String>) loop.invoke(list));
+    }
+
+    @Test
+    public static void testIteratePrint() throws Throwable {
+        MethodHandle loop = MethodHandles.iteratedLoop(null, null, Iterate.MH_printStep);
+        assertEquals(Iterate.MT_print, loop.type());
+        loop.invoke(Arrays.asList("hello", "world"));
+    }
+
+    @Test
+    public static void testIterateNullBody() {
+        boolean caught = false;
+        try {
+            MethodHandles.iteratedLoop(MethodHandles.identity(int.class), MethodHandles.identity(int.class), null);
+        } catch (IllegalArgumentException iae) {
+            assertEquals("iterated loop body must not be null", iae.getMessage());
+            caught = true;
+        }
+        assertTrue(caught);
+    }
+
+    @Test
+    public static void testTryFinally() throws Throwable {
+        MethodHandle hello = MethodHandles.tryFinally(TryFinally.MH_greet, TryFinally.MH_exclaim);
+        assertEquals(TryFinally.MT_hello, hello.type());
+        assertEquals("Hello, world!", hello.invoke("world"));
+    }
+
+    @Test
+    public static void testTryFinallyVoid() throws Throwable {
+        MethodHandle tfVoid = MethodHandles.tryFinally(TryFinally.MH_print, TryFinally.MH_printMore);
+        assertEquals(TryFinally.MT_printHello, tfVoid.type());
+        tfVoid.invoke("world");
+    }
+
+    @Test
+    public static void testTryFinallySublist() throws Throwable {
+        MethodHandle helloMore = MethodHandles.tryFinally(TryFinally.MH_greetMore, TryFinally.MH_exclaimMore);
+        assertEquals(TryFinally.MT_moreHello, helloMore.type());
+        assertEquals("Hello, world and universe (but world first)!", helloMore.invoke("world", "universe"));
+    }
+
+    @Test
+    public static void testTryFinallyNegative() {
+        MethodHandle intid = MethodHandles.identity(int.class);
+        MethodHandle intco = MethodHandles.constant(int.class, 0);
+        MethodHandle errTarget = MethodHandles.dropArguments(intco, 0, int.class, double.class, String.class, int.class);
+        MethodHandle errCleanup = MethodHandles.dropArguments(MethodHandles.constant(int.class, 0), 0, Throwable.class,
+                int.class, double.class, Object.class);
+        MethodHandle[][] cases = {
+                {intid, MethodHandles.identity(double.class)},
+                {intid, MethodHandles.dropArguments(intid, 0, String.class)},
+                {intid, MethodHandles.dropArguments(intid, 0, Throwable.class, double.class)},
+                {errTarget, errCleanup}
+        };
+        String[] messages = {
+                "target and return types must match: double != int",
+                "cleanup first argument and Throwable must match: (String,int)int != class java.lang.Throwable",
+                "cleanup second argument and target return type must match: (Throwable,double,int)int != int",
+                "cleanup parameters after (Throwable,result) and target parameter list prefix must match: " +
+                        errCleanup.type() + " != " + errTarget.type()
+        };
+        for (int i = 0; i < cases.length; ++i) {
+            boolean caught = false;
+            try {
+                MethodHandles.tryFinally(cases[i][0], cases[i][1]);
+            } catch (IllegalArgumentException iae) {
+                assertEquals(messages[i], iae.getMessage());
+                caught = true;
+            }
+            assertTrue(caught);
+        }
+    }
+
+    @Test
+    public static void testFold0a() throws Throwable {
+        // equivalence to foldArguments(MethodHandle,MethodHandle)
+        MethodHandle fold = MethodHandles.foldArguments(Fold.MH_multer, 0, Fold.MH_adder);
+        assertEquals(Fold.MT_folded1, fold.type());
+        assertEquals(720, (int) fold.invoke(3, 4, 5));
+    }
+
+    @Test
+    public static void testFold1a() throws Throwable {
+        // test foldArguments for folding position 1
+        MethodHandle fold = MethodHandles.foldArguments(Fold.MH_multer, 1, Fold.MH_adder1);
+        assertEquals(Fold.MT_folded1, fold.type());
+        assertEquals(540, (int) fold.invoke(3, 4, 5));
+    }
+
+    @Test
+    public static void testFold0b() throws Throwable {
+        // test foldArguments equivalence with multiple types
+        MethodHandle fold = MethodHandles.foldArguments(Fold.MH_str, 0, Fold.MH_comb);
+        assertEquals(Fold.MT_folded2, fold.type());
+        assertEquals(23, (int) fold.invoke("true", true, 23));
+    }
+
+    @Test
+    public static void testFold1b() throws Throwable {
+        // test folgArguments for folding position 1, with multiple types
+        MethodHandle fold = MethodHandles.foldArguments(Fold.MH_str, 1, Fold.MH_comb2);
+        assertEquals(Fold.MT_folded3, fold.type());
+        assertEquals(1, (int) fold.invoke(true, true, 1));
+        assertEquals(-1, (int) fold.invoke(true, false, -1));
+    }
+
+    @Test
+    public static void testFoldArgumentsExample() throws Throwable {
+        // test the JavaDoc foldArguments-with-pos example
+        StringWriter swr = new StringWriter();
+        MethodHandle trace = LOOKUP.findVirtual(StringWriter.class, "write", methodType(void.class, String.class)).bindTo(swr);
+        MethodHandle cat = LOOKUP.findVirtual(String.class, "concat", methodType(String.class, String.class));
+        assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+        MethodHandle catTrace = MethodHandles.foldArguments(cat, 1, trace);
+        assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+        assertEquals("jum", swr.toString());
+    }
+
+    @Test
+    public static void testAsSpreader() throws Throwable {
+        MethodHandle spreader = SpreadCollect.MH_forSpreading.asSpreader(1, int[].class, 3);
+        assertEquals(SpreadCollect.MT_spreader, spreader.type());
+        assertEquals("A456B", (String) spreader.invoke("A", new int[]{4, 5, 6}, "B"));
+    }
+
+    @Test
+    public static void testAsSpreaderExample() throws Throwable {
+        // test the JavaDoc asSpreader-with-pos example
+        MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class));
+        MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2);
+        Object[] ints = new Object[]{3, 9, 7, 7};
+        Comparator<Integer> cmp = (a, b) -> a - b;
+        assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0);
+        assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0);
+        assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0);
+    }
+
+    @Test
+    public static void testAsSpreaderIllegalPos() throws Throwable {
+        int[] illegalPos = {-7, 3, 19};
+        int caught = 0;
+        for (int p : illegalPos) {
+            try {
+                SpreadCollect.MH_forSpreading.asSpreader(p, Object[].class, 3);
+            } catch (IllegalArgumentException iae) {
+                assertEquals("bad spread position", iae.getMessage());
+                ++caught;
+            }
+        }
+        assertEquals(illegalPos.length, caught);
+    }
+
+    @Test
+    public static void testAsCollector() throws Throwable {
+        MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 1);
+        assertEquals(SpreadCollect.MT_collector1, collector.type());
+        assertEquals("A4B", (String) collector.invoke("A", 4, "B"));
+        collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 2);
+        assertEquals(SpreadCollect.MT_collector2, collector.type());
+        assertEquals("A45B", (String) collector.invoke("A", 4, 5, "B"));
+        collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 3);
+        assertEquals(SpreadCollect.MT_collector3, collector.type());
+        assertEquals("A456B", (String) collector.invoke("A", 4, 5, 6, "B"));
+    }
+
+    @Test
+    public static void testAsCollectorInvokeWithArguments() throws Throwable {
+        MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 1);
+        assertEquals(SpreadCollect.MT_collector1, collector.type());
+        assertEquals("A4B", (String) collector.invokeWithArguments("A", 4, "B"));
+        collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 2);
+        assertEquals(SpreadCollect.MT_collector2, collector.type());
+        assertEquals("A45B", (String) collector.invokeWithArguments("A", 4, 5, "B"));
+        collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 3);
+        assertEquals(SpreadCollect.MT_collector3, collector.type());
+        assertEquals("A456B", (String) collector.invokeWithArguments("A", 4, 5, 6, "B"));
+    }
+
+    @Test
+    public static void testAsCollectorLeading() throws Throwable {
+        MethodHandle collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 1);
+        assertEquals(SpreadCollect.MT_collectorLeading1, collector.type());
+        assertEquals("7Q", (String) collector.invoke(7, "Q"));
+        collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 2);
+        assertEquals(SpreadCollect.MT_collectorLeading2, collector.type());
+        assertEquals("78Q", (String) collector.invoke(7, 8, "Q"));
+        collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 3);
+        assertEquals(SpreadCollect.MT_collectorLeading3, collector.type());
+        assertEquals("789Q", (String) collector.invoke(7, 8, 9, "Q"));
+    }
+
+    @Test
+    public static void testAsCollectorLeadingInvokeWithArguments() throws Throwable {
+        MethodHandle collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 1);
+        assertEquals(SpreadCollect.MT_collectorLeading1, collector.type());
+        assertEquals("7Q", (String) collector.invokeWithArguments(7, "Q"));
+        collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 2);
+        assertEquals(SpreadCollect.MT_collectorLeading2, collector.type());
+        assertEquals("78Q", (String) collector.invokeWithArguments(7, 8, "Q"));
+        collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 3);
+        assertEquals(SpreadCollect.MT_collectorLeading3, collector.type());
+        assertEquals("789Q", (String) collector.invokeWithArguments(7, 8, 9, "Q"));
+    }
+
+    @Test
+    public static void testAsCollectorNone() throws Throwable {
+        MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 0);
+        assertEquals(SpreadCollect.MT_collector0, collector.type());
+        assertEquals("AB", (String) collector.invoke("A", "B"));
+    }
+
+    @Test
+    public static void testAsCollectorIllegalPos() throws Throwable {
+        int[] illegalPos = {-1, 17};
+        int caught = 0;
+        for (int p : illegalPos) {
+            try {
+                SpreadCollect.MH_forCollecting.asCollector(p, int[].class, 0);
+            } catch (IllegalArgumentException iae) {
+                assertEquals("bad collect position", iae.getMessage());
+                ++caught;
+            }
+        }
+        assertEquals(illegalPos.length, caught);
+    }
+
+    @Test
+    public static void testAsCollectorExample() throws Throwable {
+        // test the JavaDoc asCollector-with-pos example
+        StringWriter swr = new StringWriter();
+        MethodHandle swWrite = LOOKUP.
+                findVirtual(StringWriter.class, "write", methodType(void.class, char[].class, int.class, int.class)).
+                bindTo(swr);
+        MethodHandle swWrite4 = swWrite.asCollector(0, char[].class, 4);
+        swWrite4.invoke('A', 'B', 'C', 'D', 1, 2);
+        assertEquals("BC", swr.toString());
+        swWrite4.invoke('P', 'Q', 'R', 'S', 0, 4);
+        assertEquals("BCPQRS", swr.toString());
+        swWrite4.invoke('W', 'X', 'Y', 'Z', 3, 1);
+        assertEquals("BCPQRSZ", swr.toString());
+    }
+
+    @Test
+    public static void testFindSpecial() throws Throwable {
+        FindSpecial.C c = new FindSpecial.C();
+        assertEquals("I1.m", c.m());
+        MethodType t = MethodType.methodType(String.class);
+        MethodHandle ci1m = LOOKUP.findSpecial(FindSpecial.I1.class, "m", t, FindSpecial.C.class);
+        assertEquals("I1.m", (String) ci1m.invoke(c));
+    }
+
+    @Test
+    public static void testFindSpecialAbstract() throws Throwable {
+        FindSpecial.C c = new FindSpecial.C();
+        assertEquals("q", c.q());
+        MethodType t = MethodType.methodType(String.class);
+        boolean caught = false;
+        try {
+            MethodHandle ci3q = LOOKUP.findSpecial(FindSpecial.I3.class, "q", t, FindSpecial.C.class);
+        } catch (Throwable thrown) {
+            if (!(thrown instanceof IllegalAccessException) || !FindSpecial.ABSTRACT_ERROR.equals(thrown.getMessage())) {
+                throw new AssertionError(thrown.getMessage(), thrown);
+            }
+            caught = true;
+        }
+        assertTrue(caught);
+    }
+
+    @Test
+    public static void testFindClassCNFE() throws Throwable {
+        boolean caught = false;
+        try {
+            LOOKUP.findClass("does.not.Exist");
+        } catch (ClassNotFoundException cnfe) {
+            caught = true;
+        }
+        assertTrue(caught);
+    }
+
+    //
+    // Methods used to assemble tests.
+    //
+
+    static class Empty {
+
+        static void f() { }
+
+        static boolean pred() {
+            return false;
+        }
+
+        static final Class<Empty> EMPTY = Empty.class;
+
+        static final MethodType MT_f = methodType(void.class);
+        static final MethodType MT_pred = methodType(boolean.class);
+
+        static final MethodHandle MH_f;
+        static final MethodHandle MH_pred;
+
+        static {
+            try {
+                MH_f = LOOKUP.findStatic(EMPTY, "f", MT_f);
+                MH_pred = LOOKUP.findStatic(EMPTY, "pred", MT_pred);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+    }
+
+    static class Fac {
+
+        static int zero(int k) {
+            return 0;
+        }
+
+        static int one(int k) {
+            return 1;
+        }
+
+        static boolean pred(int i, int acc, int k) {
+            return i < k;
+        }
+
+        static int inc(int i, int acc, int k) {
+            return i + 1;
+        }
+
+        static int mult(int i, int acc, int k) {
+            return i * acc;
+        }
+
+        static void dot(int i, int acc, int k) {
+            System.out.print('.');
+        }
+
+        static int fin(int i, int acc, int k) {
+            return acc;
+        }
+
+        static final Class<Fac> FAC = Fac.class;
+
+        static final MethodType MT_init = methodType(int.class, int.class);
+        static final MethodType MT_fn = methodType(int.class, int.class, int.class, int.class);
+        static final MethodType MT_dot = methodType(void.class, int.class, int.class, int.class);
+        static final MethodType MT_pred = methodType(boolean.class, int.class, int.class, int.class);
+
+        static final MethodHandle MH_zero;
+        static final MethodHandle MH_one;
+        static final MethodHandle MH_pred;
+        static final MethodHandle MH_inc;
+        static final MethodHandle MH_mult;
+        static final MethodHandle MH_dot;
+        static final MethodHandle MH_fin;
+
+        static final MethodType MT_fac = methodType(int.class, int.class);
+
+        static {
+            try {
+                MH_zero = LOOKUP.findStatic(FAC, "zero", MT_init);
+                MH_one = LOOKUP.findStatic(FAC, "one", MT_init);
+                MH_pred = LOOKUP.findStatic(FAC, "pred", MT_pred);
+                MH_inc = LOOKUP.findStatic(FAC, "inc", MT_fn);
+                MH_mult = LOOKUP.findStatic(FAC, "mult", MT_fn);
+                MH_dot = LOOKUP.findStatic(FAC, "dot", MT_dot);
+                MH_fin = LOOKUP.findStatic(FAC, "fin", MT_fn);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+    }
+
+    static class While {
+
+        static int zero(int limit) {
+            return 0;
+        }
+
+        static boolean pred(int i, int limit) {
+            return i < limit;
+        }
+
+        static int step(int i, int limit) {
+            return i + 1;
+        }
+
+        static String initString() {
+            return "a";
+        }
+
+        static boolean predString(String s) {
+            return s.length() != 1;
+        }
+
+        static String stepString(String s) {
+            return s + "a";
+        }
+
+        static List<String> zipInitZip(Iterator<String> a, Iterator<String> b) {
+            return new ArrayList<>();
+        }
+
+        static boolean zipPred(List<String> zip, Iterator<String> a, Iterator<String> b) {
+            return a.hasNext() && b.hasNext();
+        }
+
+        static List<String> zipStep(List<String> zip, Iterator<String> a, Iterator<String> b) {
+            zip.add(a.next());
+            zip.add(b.next());
+            return zip;
+        }
+
+        static final Class<While> WHILE = While.class;
+
+        static final MethodType MT_zero = methodType(int.class, int.class);
+        static final MethodType MT_pred = methodType(boolean.class, int.class, int.class);
+        static final MethodType MT_fn = methodType(int.class, int.class, int.class);
+        static final MethodType MT_initString = methodType(String.class);
+        static final MethodType MT_predString = methodType(boolean.class, String.class);
+        static final MethodType MT_stepString = methodType(String.class, String.class);
+        static final MethodType MT_zipInitZip = methodType(List.class, Iterator.class, Iterator.class);
+        static final MethodType MT_zipPred = methodType(boolean.class, List.class, Iterator.class, Iterator.class);
+        static final MethodType MT_zipStep = methodType(List.class, List.class, Iterator.class, Iterator.class);
+
+        static final MethodHandle MH_zero;
+        static final MethodHandle MH_pred;
+        static final MethodHandle MH_step;
+        static final MethodHandle MH_initString;
+        static final MethodHandle MH_predString;
+        static final MethodHandle MH_stepString;
+        static final MethodHandle MH_zipInitZip;
+        static final MethodHandle MH_zipPred;
+        static final MethodHandle MH_zipStep;
+
+        static final MethodType MT_while = methodType(int.class, int.class);
+        static final MethodType MT_string = methodType(String.class);
+        static final MethodType MT_zip = methodType(List.class, Iterator.class, Iterator.class);
+
+        static {
+            try {
+                MH_zero = LOOKUP.findStatic(WHILE, "zero", MT_zero);
+                MH_pred = LOOKUP.findStatic(WHILE, "pred", MT_pred);
+                MH_step = LOOKUP.findStatic(WHILE, "step", MT_fn);
+                MH_initString = LOOKUP.findStatic(WHILE, "initString", MT_initString);
+                MH_predString = LOOKUP.findStatic(WHILE, "predString", MT_predString);
+                MH_stepString = LOOKUP.findStatic(WHILE, "stepString", MT_stepString);
+                MH_zipInitZip = LOOKUP.findStatic(WHILE, "zipInitZip", MT_zipInitZip);
+                MH_zipPred = LOOKUP.findStatic(WHILE, "zipPred", MT_zipPred);
+                MH_zipStep = LOOKUP.findStatic(WHILE, "zipStep", MT_zipStep);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+    }
+
+    static class Counted {
+
+        static String start(String arg) {
+            return arg;
+        }
+
+        static String step(int counter, String v, String arg) {
+            return "na " + v;
+        }
+
+        static void stepUpdateArray(int counter, int[] a) {
+            ++a[0];
+        }
+
+        static void printHello(int counter) {
+            System.out.print("hello");
+        }
+
+        static final Class<Counted> COUNTED = Counted.class;
+
+        static final MethodType MT_start = methodType(String.class, String.class);
+        static final MethodType MT_step = methodType(String.class, int.class, String.class, String.class);
+        static final MethodType MT_stepUpdateArray = methodType(void.class, int.class, int[].class);
+        static final MethodType MT_printHello = methodType(void.class, int.class);
+
+        static final MethodHandle MH_13;
+        static final MethodHandle MH_m5;
+        static final MethodHandle MH_8;
+        static final MethodHandle MH_start;
+        static final MethodHandle MH_step;
+        static final MethodHandle MH_stepUpdateArray;
+        static final MethodHandle MH_printHello;
+
+        static final MethodType MT_counted = methodType(String.class, String.class);
+        static final MethodType MT_arrayCounted = methodType(void.class, int[].class);
+        static final MethodType MT_countedPrinting = methodType(void.class);
+
+        static {
+            try {
+                MH_13 = MethodHandles.constant(int.class, 13);
+                MH_m5 = MethodHandles.constant(int.class, -5);
+                MH_8 = MethodHandles.constant(int.class, 8);
+                MH_start = LOOKUP.findStatic(COUNTED, "start", MT_start);
+                MH_step = LOOKUP.findStatic(COUNTED, "step", MT_step);
+                MH_stepUpdateArray = LOOKUP.findStatic(COUNTED, "stepUpdateArray", MT_stepUpdateArray);
+                MH_printHello = LOOKUP.findStatic(COUNTED, "printHello", MT_printHello);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+    }
+
+    static class Iterate {
+
+        static Iterator<Integer> sumIterator(Integer[] a) {
+            return Arrays.asList(a).iterator();
+        }
+
+        static int sumInit(Integer[] a) {
+            return 0;
+        }
+
+        static int sumStep(int s, int e, Integer[] a) {
+            return s + e;
+        }
+
+        static List<String> reverseInit(List<String> l) {
+            return new ArrayList<>();
+        }
+
+        static List<String> reverseStep(String e, List<String> r, List<String> l) {
+            r.add(0, e);
+            return r;
+        }
+
+        static int lengthInit(List<Double> l) {
+            return 0;
+        }
+
+        static int lengthStep(Object o, int len, List<Double> l) {
+            return len + 1;
+        }
+
+        static List<String> mapInit(List<String> l) {
+            return new ArrayList<>();
+        }
+
+        static List<String> mapStep(String e, List<String> r, List<String> l) {
+            r.add(e.toUpperCase());
+            return r;
+        }
+
+        static void printStep(String s, List<String> l) {
+            System.out.print(s);
+        }
+
+        static final Class<Iterate> ITERATE = Iterate.class;
+
+        static final MethodType MT_sumIterator = methodType(Iterator.class, Integer[].class);
+
+        static final MethodType MT_sumInit = methodType(int.class, Integer[].class);
+        static final MethodType MT_reverseInit = methodType(List.class, List.class);
+        static final MethodType MT_lenghInit = methodType(int.class, List.class);
+        static final MethodType MT_mapInit = methodType(List.class, List.class);
+
+        static final MethodType MT_sumStep = methodType(int.class, int.class, int.class, Integer[].class);
+        static final MethodType MT_reverseStep = methodType(List.class, String.class, List.class, List.class);
+        static final MethodType MT_lengthStep = methodType(int.class, Object.class, int.class, List.class);
+        static final MethodType MT_mapStep = methodType(List.class, String.class, List.class, List.class);
+        static final MethodType MT_printStep = methodType(void.class, String.class, List.class);
+
+        static final MethodHandle MH_sumIterator;
+        static final MethodHandle MH_sumInit;
+        static final MethodHandle MH_sumStep;
+        static final MethodHandle MH_printStep;
+
+        static final MethodHandle MH_reverseInit;
+        static final MethodHandle MH_reverseStep;
+
+        static final MethodHandle MH_lengthInit;
+        static final MethodHandle MH_lengthStep;
+
+        static final MethodHandle MH_mapInit;
+        static final MethodHandle MH_mapStep;
+
+        static final MethodType MT_sum = methodType(int.class, Integer[].class);
+        static final MethodType MT_reverse = methodType(List.class, List.class);
+        static final MethodType MT_length = methodType(int.class, List.class);
+        static final MethodType MT_map = methodType(List.class, List.class);
+        static final MethodType MT_print = methodType(void.class, List.class);
+
+        static {
+            try {
+                MH_sumIterator = LOOKUP.findStatic(ITERATE, "sumIterator", MT_sumIterator);
+                MH_sumInit = LOOKUP.findStatic(ITERATE, "sumInit", MT_sumInit);
+                MH_sumStep = LOOKUP.findStatic(ITERATE, "sumStep", MT_sumStep);
+                MH_reverseInit = LOOKUP.findStatic(ITERATE, "reverseInit", MT_reverseInit);
+                MH_reverseStep = LOOKUP.findStatic(ITERATE, "reverseStep", MT_reverseStep);
+                MH_lengthInit = LOOKUP.findStatic(ITERATE, "lengthInit", MT_lenghInit);
+                MH_lengthStep = LOOKUP.findStatic(ITERATE, "lengthStep", MT_lengthStep);
+                MH_mapInit = LOOKUP.findStatic(ITERATE, "mapInit", MT_mapInit);
+                MH_mapStep = LOOKUP.findStatic(ITERATE, "mapStep", MT_mapStep);
+                MH_printStep = LOOKUP.findStatic(ITERATE, "printStep", MT_printStep);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+    }
+
+    static class TryFinally {
+
+        static String greet(String whom) {
+            return "Hello, " + whom;
+        }
+
+        static String exclaim(Throwable t, String r, String whom) {
+            return r + "!";
+        }
+
+        static void print(String what) {
+            System.out.print("Hello, " + what);
+        }
+
+        static void printMore(Throwable t, String what) {
+            System.out.println("!");
+        }
+
+        static String greetMore(String first, String second) {
+            return "Hello, " + first + " and " + second;
+        }
+
+        static String exclaimMore(Throwable t, String r, String first) {
+            return r + " (but " + first + " first)!";
+        }
+
+        static final Class<TryFinally> TRY_FINALLY = TryFinally.class;
+
+        static final MethodType MT_greet = methodType(String.class, String.class);
+        static final MethodType MT_exclaim = methodType(String.class, Throwable.class, String.class, String.class);
+        static final MethodType MT_print = methodType(void.class, String.class);
+        static final MethodType MT_printMore = methodType(void.class, Throwable.class, String.class);
+        static final MethodType MT_greetMore = methodType(String.class, String.class, String.class);
+        static final MethodType MT_exclaimMore = methodType(String.class, Throwable.class, String.class, String.class);
+
+        static final MethodHandle MH_greet;
+        static final MethodHandle MH_exclaim;
+        static final MethodHandle MH_print;
+        static final MethodHandle MH_printMore;
+        static final MethodHandle MH_greetMore;
+        static final MethodHandle MH_exclaimMore;
+
+        static final MethodType MT_hello = methodType(String.class, String.class);
+        static final MethodType MT_printHello = methodType(void.class, String.class);
+        static final MethodType MT_moreHello = methodType(String.class, String.class, String.class);
+
+        static {
+            try {
+                MH_greet = LOOKUP.findStatic(TRY_FINALLY, "greet", MT_greet);
+                MH_exclaim = LOOKUP.findStatic(TRY_FINALLY, "exclaim", MT_exclaim);
+                MH_print = LOOKUP.findStatic(TRY_FINALLY, "print", MT_print);
+                MH_printMore = LOOKUP.findStatic(TRY_FINALLY, "printMore", MT_printMore);
+                MH_greetMore = LOOKUP.findStatic(TRY_FINALLY, "greetMore", MT_greetMore);
+                MH_exclaimMore = LOOKUP.findStatic(TRY_FINALLY, "exclaimMore", MT_exclaimMore);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+    }
+
+    static class Fold {
+
+        static int adder(int a, int b, int c) {
+            return a + b + c;
+        }
+
+        static int adder1(int a, int b) {
+            return a + b;
+        }
+
+        static int multer(int x, int q, int r, int s) {
+            return x * q * r * s;
+        }
+
+        static int str(boolean b1, String s, boolean b2, int x) {
+            return b1 && s.equals(String.valueOf(b2)) ? x : -x;
+        }
+
+        static boolean comb(String s, boolean b2) {
+            return !s.equals(b2);
+        }
+
+        static String comb2(boolean b2, int x) {
+            int ib = b2 ? 1 : 0;
+            return ib == x ? "true" : "false";
+        }
+
+        static final Class<Fold> FOLD = Fold.class;
+
+        static final MethodType MT_adder = methodType(int.class, int.class, int.class, int.class);
+        static final MethodType MT_adder1 = methodType(int.class, int.class, int.class);
+        static final MethodType MT_multer = methodType(int.class, int.class, int.class, int.class, int.class);
+        static final MethodType MT_str = methodType(int.class, boolean.class, String.class, boolean.class, int.class);
+        static final MethodType MT_comb = methodType(boolean.class, String.class, boolean.class);
+        static final MethodType MT_comb2 = methodType(String.class, boolean.class, int.class);
+
+        static final MethodHandle MH_adder;
+        static final MethodHandle MH_adder1;
+        static final MethodHandle MH_multer;
+        static final MethodHandle MH_str;
+        static final MethodHandle MH_comb;
+        static final MethodHandle MH_comb2;
+
+        static final MethodType MT_folded1 = methodType(int.class, int.class, int.class, int.class);
+        static final MethodType MT_folded2 = methodType(int.class, String.class, boolean.class, int.class);
+        static final MethodType MT_folded3 = methodType(int.class, boolean.class, boolean.class, int.class);
+
+        static {
+            try {
+                MH_adder = LOOKUP.findStatic(FOLD, "adder", MT_adder);
+                MH_adder1 = LOOKUP.findStatic(FOLD, "adder1", MT_adder1);
+                MH_multer = LOOKUP.findStatic(FOLD, "multer", MT_multer);
+                MH_str = LOOKUP.findStatic(FOLD, "str", MT_str);
+                MH_comb = LOOKUP.findStatic(FOLD, "comb", MT_comb);
+                MH_comb2 = LOOKUP.findStatic(FOLD, "comb2", MT_comb2);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+    }
+
+    static class SpreadCollect {
+
+        static String forSpreading(String s1, int i1, int i2, int i3, String s2) {
+            return s1 + i1 + i2 + i3 + s2;
+        }
+
+        static String forCollecting(String s1, int[] is, String s2) {
+            StringBuilder sb = new StringBuilder(s1);
+            for (int i : is) {
+                sb.append(i);
+            }
+            return sb.append(s2).toString();
+        }
+
+        static String forCollectingLeading(int[] is, String s) {
+            return forCollecting("", is, s);
+        }
+
+        static final Class<SpreadCollect> SPREAD_COLLECT = SpreadCollect.class;
+
+        static final MethodType MT_forSpreading = methodType(String.class, String.class, int.class, int.class, int.class, String.class);
+        static final MethodType MT_forCollecting = methodType(String.class, String.class, int[].class, String.class);
+        static final MethodType MT_forCollectingLeading = methodType(String.class, int[].class, String.class);
+
+        static final MethodHandle MH_forSpreading;
+        static final MethodHandle MH_forCollecting;
+        static final MethodHandle MH_forCollectingLeading;
+
+        static final MethodType MT_spreader = methodType(String.class, String.class, int[].class, String.class);
+        static final MethodType MT_collector0 = methodType(String.class, String.class, String.class);
+        static final MethodType MT_collector1 = methodType(String.class, String.class, int.class, String.class);
+        static final MethodType MT_collector2 = methodType(String.class, String.class, int.class, int.class, String.class);
+        static final MethodType MT_collector3 = methodType(String.class, String.class, int.class, int.class, int.class, String.class);
+        static final MethodType MT_collectorLeading1 = methodType(String.class, int.class, String.class);
+        static final MethodType MT_collectorLeading2 = methodType(String.class, int.class, int.class, String.class);
+        static final MethodType MT_collectorLeading3 = methodType(String.class, int.class, int.class, int.class, String.class);
+
+        static final String NONE_ERROR = "zero array length in MethodHandle.asCollector";
+
+        static {
+            try {
+                MH_forSpreading = LOOKUP.findStatic(SPREAD_COLLECT, "forSpreading", MT_forSpreading);
+                MH_forCollecting = LOOKUP.findStatic(SPREAD_COLLECT, "forCollecting", MT_forCollecting);
+                MH_forCollectingLeading = LOOKUP.findStatic(SPREAD_COLLECT, "forCollectingLeading", MT_forCollectingLeading);
+            } catch (Exception e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+    }
+
+    static class FindSpecial {
+
+        interface I1 {
+            default String m() {
+                return "I1.m";
+            }
+        }
+
+        interface I2 {
+            default String m() {
+                return "I2.m";
+            }
+        }
+
+        interface I3 {
+            String q();
+        }
+
+        static class C implements I1, I2, I3 {
+            public String m() {
+                return I1.super.m();
+            }
+            public String q() {
+                return "q";
+            }
+        }
+
+        static final String ABSTRACT_ERROR = "no such method: test.java.lang.invoke.T8139885$FindSpecial$I3.q()String/invokeSpecial";
+
+    }
+
+    //
+    // Auxiliary methods.
+    //
+
+    static MethodHandle[] mha(MethodHandle... mhs) {
+        return mhs;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/findclass.security.policy	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,9 @@
+/*
+ * Security policy used by the FindClassSecurityManager test.
+ * Must allow file reads so that jtreg itself can run, and getting class loaders.
+ */
+
+grant {
+  permission java.io.FilePermission "*", "read";
+  permission java.lang.RuntimePermission "getClassLoader";
+};
--- a/jdk/test/java/util/logging/LoggerSubclass.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/java/util/logging/LoggerSubclass.java	Mon Nov 23 10:00:50 2015 -0800
@@ -92,7 +92,7 @@
         else fail(x + " not equal to " + y);}
     public static void main(String[] args) throws Throwable {
         try {new LoggerSubclass().instanceMain(args);}
-        catch (Throwable e) {throw e.getCause();}}
+        catch (Throwable e) {throw e.getCause() == null ? e : e.getCause();}}
     public void instanceMain(String[] args) throws Throwable {
         try {test(args);} catch (Throwable t) {unexpected(t);}
         System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/security/jarsigner/Function.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8056174
+ * @summary test the functions of JarSigner API
+ * @modules java.base/sun.security.tools.keytool
+ *          jdk.jartool
+ */
+
+import jdk.security.jarsigner.JarSigner;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class Function {
+    public static void main(String[] args) throws Exception {
+
+        try (FileOutputStream fout =new FileOutputStream("src.zip");
+                ZipOutputStream zout = new ZipOutputStream(fout)) {
+            zout.putNextEntry(new ZipEntry("x"));
+            zout.write(new byte[10]);
+            zout.closeEntry();
+        }
+
+        sun.security.tools.keytool.Main.main(
+                ("-storetype jks -keystore ks -storepass changeit" +
+                        " -keypass changeit -dname" +
+                        " CN=RSA -alias r -genkeypair -keyalg rsa").split(" "));
+
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(new FileInputStream("ks"), "changeit".toCharArray());
+        PrivateKey key = (PrivateKey)ks.getKey("r", "changeit".toCharArray());
+        Certificate cert = ks.getCertificate("r");
+        JarSigner.Builder jsb = new JarSigner.Builder(key,
+                CertificateFactory.getInstance("X.509").generateCertPath(
+                        Collections.singletonList(cert)));
+
+        jsb.digestAlgorithm("SHA1");
+        jsb.signatureAlgorithm("SHA1withRSA");
+
+        AtomicInteger counter = new AtomicInteger(0);
+        StringBuilder sb = new StringBuilder();
+        jsb.eventHandler(
+                (a, f)->{
+                    counter.incrementAndGet();
+                    sb.append(a).append(' ').append(f).append('\n');
+                });
+
+        OutputStream blackHole = new OutputStream() {
+            @Override
+            public void write(int b) throws IOException { }
+        };
+
+        try (ZipFile src = new ZipFile("src.zip")) {
+            jsb.build().sign(src, blackHole);
+        }
+
+        if (counter.get() != 4) {
+            throw new Exception("Event number is " + counter.get()
+                    + ":\n" + sb.toString());
+        }
+
+        // Provider test.
+        Provider p = new MyProvider();
+        jsb.digestAlgorithm("Five", p);
+        jsb.signatureAlgorithm("SHA1WithRSA", p);
+        try (ZipFile src = new ZipFile("src.zip");
+                FileOutputStream out = new FileOutputStream("out.jar")) {
+            jsb.build().sign(src, out);
+        }
+
+        try (JarFile signed = new JarFile("out.jar")) {
+            Manifest man = signed.getManifest();
+            assertTrue(man.getAttributes("x").getValue("Five-Digest").equals("FAKE"));
+
+            Manifest sf = new Manifest(signed.getInputStream(
+                    signed.getJarEntry("META-INF/SIGNER.SF")));
+            assertTrue(sf.getMainAttributes().getValue("Five-Digest-Manifest")
+                    .equals("FAKE"));
+            assertTrue(sf.getAttributes("x").getValue("Five-Digest").equals("FAKE"));
+
+            try (InputStream sig = signed.getInputStream(
+                    signed.getJarEntry("META-INF/SIGNER.RSA"))) {
+                byte[] data = sig.readAllBytes();
+                assertTrue(Arrays.equals(
+                        Arrays.copyOfRange(data, data.length-8, data.length),
+                        "FAKEFAKE".getBytes()));
+            }
+        }
+    }
+
+    private static void assertTrue(boolean v) {
+        if (!v) {
+            throw new AssertionError();
+        }
+    }
+
+    public static class MyProvider extends Provider {
+        MyProvider() {
+            super("MY", 1.0d, null);
+            put("MessageDigest.Five", Five.class.getName());
+            put("Signature.SHA1WithRSA", SHA1WithRSA.class.getName());
+        }
+    }
+
+    // "Five" is a MessageDigest always returns the same value
+    public static class Five extends MessageDigest {
+        static final byte[] dig = {0x14, 0x02, (byte)0x84}; //base64 -> FAKE
+        public Five() { super("Five"); }
+        protected void engineUpdate(byte input) { }
+        protected void engineUpdate(byte[] input, int offset, int len) { }
+        protected byte[] engineDigest() { return dig; }
+        protected void engineReset() { }
+    }
+
+    // This fake "SHA1withRSA" is a Signature always returns the same value.
+    // An existing name must be used otherwise PKCS7 does not which OID to use.
+    public static class SHA1WithRSA extends Signature {
+        static final byte[] sig = "FAKEFAKE".getBytes();
+        public SHA1WithRSA() { super("SHA1WithRSA"); }
+        protected void engineInitVerify(PublicKey publicKey)
+                throws InvalidKeyException { }
+        protected void engineInitSign(PrivateKey privateKey)
+                throws InvalidKeyException { }
+        protected void engineUpdate(byte b) throws SignatureException { }
+        protected void engineUpdate(byte[] b, int off, int len)
+                throws SignatureException { }
+        protected byte[] engineSign() throws SignatureException { return sig; }
+        protected boolean engineVerify(byte[] sigBytes)
+                throws SignatureException {
+            return Arrays.equals(sigBytes, sig);
+        }
+        protected void engineSetParameter(String param, Object value)
+                throws InvalidParameterException { }
+        protected Object engineGetParameter(String param)
+                throws InvalidParameterException { return null; }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/security/jarsigner/Spec.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8056174
+ * @summary Make sure JarSigner impl conforms to spec
+ * @library /lib/testlibrary
+ * @modules java.base/sun.security.tools.keytool
+ *          java.base/sun.security.provider.certpath
+ *          jdk.jartool
+ */
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import jdk.security.jarsigner.JarSigner;
+import jdk.testlibrary.JarUtils;
+import sun.security.provider.certpath.X509CertPath;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+
+public class Spec {
+
+    public static void main(String[] args) throws Exception {
+
+        // Prepares raw file
+        Files.write(Paths.get("a"), "a".getBytes());
+
+        // Pack
+        JarUtils.createJar("a.jar", "a");
+
+        // Prepare a keystore
+        sun.security.tools.keytool.Main.main(
+                ("-keystore ks -storepass changeit -keypass changeit -dname" +
+                        " CN=RSA -alias r -genkeypair -keyalg rsa").split(" "));
+        sun.security.tools.keytool.Main.main(
+                ("-keystore ks -storepass changeit -keypass changeit -dname" +
+                        " CN=DSA -alias d -genkeypair -keyalg dsa").split(" "));
+
+        char[] pass = "changeit".toCharArray();
+
+        KeyStore ks = KeyStore.getInstance(
+                new File("ks"), pass);
+        PrivateKey pkr = (PrivateKey)ks.getKey("r", pass);
+        PrivateKey pkd = (PrivateKey)ks.getKey("d", pass);
+        CertPath cp = CertificateFactory.getInstance("X.509")
+                .generateCertPath(Arrays.asList(ks.getCertificateChain("r")));
+
+        Provider sun = Security.getProvider("SUN");
+
+        // throws
+        npe(()->new JarSigner.Builder(null));
+        npe(()->new JarSigner.Builder(null, cp));
+        iae(()->new JarSigner.Builder(
+                pkr, new X509CertPath(Collections.emptyList())));
+        iae(()->new JarSigner.Builder(pkd, cp));    // unmatched certs alg
+
+        JarSigner.Builder b1 = new JarSigner.Builder(pkr, cp);
+
+        npe(()->b1.digestAlgorithm(null));
+        nsae(()->b1.digestAlgorithm("HAHA"));
+        b1.digestAlgorithm("SHA-256");
+
+        npe(()->b1.digestAlgorithm("SHA-256", null));
+        npe(()->b1.digestAlgorithm(null, sun));
+        nsae(()->b1.digestAlgorithm("HAHA", sun));
+        b1.digestAlgorithm("SHA-256", sun);
+
+        npe(()->b1.signatureAlgorithm(null));
+        nsae(()->b1.signatureAlgorithm("HAHAwithHEHE"));
+        iae(()->b1.signatureAlgorithm("SHA256withECDSA"));
+
+        npe(()->b1.signatureAlgorithm(null, sun));
+        npe(()->b1.signatureAlgorithm("SHA256withRSA", null));
+        nsae(()->b1.signatureAlgorithm("HAHAwithHEHE", sun));
+        iae(()->b1.signatureAlgorithm("SHA256withDSA", sun));
+
+        npe(()->b1.tsa(null));
+
+        npe(()->b1.signerName(null));
+        iae(()->b1.signerName(""));
+        iae(()->b1.signerName("123456789"));
+        iae(()->b1.signerName("a+b"));
+
+        npe(()->b1.setProperty(null, ""));
+        uoe(()->b1.setProperty("what", ""));
+        npe(()->b1.setProperty("tsadigestalg", null));
+        iae(()->b1.setProperty("tsadigestalg", "HAHA"));
+        npe(()->b1.setProperty("tsapolicyid", null));
+        npe(()->b1.setProperty("internalsf", null));
+        iae(()->b1.setProperty("internalsf", "Hello"));
+        npe(()->b1.setProperty("sectionsonly", null));
+        iae(()->b1.setProperty("sectionsonly", "OK"));
+        npe(()->b1.setProperty("altsigner", null));
+        npe(()->b1.eventHandler(null));
+
+        // default values
+        JarSigner.Builder b2 = new JarSigner.Builder(pkr, cp);
+        JarSigner js2 = b2.build();
+
+        assertTrue(js2.getDigestAlgorithm().equals(
+                JarSigner.Builder.getDefaultDigestAlgorithm()));
+        assertTrue(js2.getSignatureAlgorithm().equals(
+                JarSigner.Builder.getDefaultSignatureAlgorithm(pkr)));
+        assertTrue(js2.getTsa() == null);
+        assertTrue(js2.getSignerName().equals("SIGNER"));
+        assertTrue(js2.getProperty("tsadigestalg").equals(
+                JarSigner.Builder.getDefaultDigestAlgorithm()));
+        assertTrue(js2.getProperty("tsapolicyid") == null);
+        assertTrue(js2.getProperty("internalsf").equals("false"));
+        assertTrue(js2.getProperty("sectionsonly").equals("false"));
+        assertTrue(js2.getProperty("altsigner") == null);
+        uoe(()->js2.getProperty("invalid"));
+
+        // default values
+        BiConsumer<String,String> myeh = (a,s)->{};
+        URI tsa = new URI("https://tsa.com");
+
+        JarSigner.Builder b3 = new JarSigner.Builder(pkr, cp)
+                .digestAlgorithm("SHA-1")
+                .signatureAlgorithm("SHA1withRSA")
+                .signerName("Duke")
+                .tsa(tsa)
+                .setProperty("tsadigestalg", "SHA-512")
+                .setProperty("tsapolicyid", "1.2.3.4")
+                .setProperty("internalsf", "true")
+                .setProperty("sectionsonly", "true")
+                .setProperty("altsigner", "MyContentSigner")
+                .eventHandler(myeh);
+        JarSigner js3 = b3.build();
+
+        assertTrue(js3.getDigestAlgorithm().equals("SHA-1"));
+        assertTrue(js3.getSignatureAlgorithm().equals("SHA1withRSA"));
+        assertTrue(js3.getTsa().equals(tsa));
+        assertTrue(js3.getSignerName().equals("DUKE"));
+        assertTrue(js3.getProperty("tsadigestalg").equals("SHA-512"));
+        assertTrue(js3.getProperty("tsapolicyid").equals("1.2.3.4"));
+        assertTrue(js3.getProperty("internalsf").equals("true"));
+        assertTrue(js3.getProperty("sectionsonly").equals("true"));
+        assertTrue(js3.getProperty("altsigner").equals("MyContentSigner"));
+        assertTrue(js3.getProperty("altsignerpath") == null);
+
+        assertTrue(JarSigner.Builder.getDefaultDigestAlgorithm().equals("SHA-256"));
+
+        // Calculating large DSA and RSA keys are too slow.
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        kpg.initialize(1024);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                    .equals("SHA256withRSA"));
+
+        kpg = KeyPairGenerator.getInstance("DSA");
+        kpg.initialize(1024);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA256withDSA"));
+
+        kpg = KeyPairGenerator.getInstance("EC");
+        kpg.initialize(192);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA256withECDSA"));
+        kpg.initialize(384);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA384withECDSA"));
+        kpg.initialize(571);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA512withECDSA"));
+    }
+
+    interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    static void uoe(RunnableWithException r) throws Exception {
+        checkException(r, UnsupportedOperationException.class);
+    }
+
+    static void nsae(RunnableWithException r) throws Exception {
+        checkException(r, NoSuchAlgorithmException.class);
+    }
+
+    static void npe(RunnableWithException r) throws Exception {
+        checkException(r, NullPointerException.class);
+    }
+
+    static void iae(RunnableWithException r) throws Exception {
+        checkException(r, IllegalArgumentException.class);
+    }
+
+    static void checkException(RunnableWithException r, Class ex)
+            throws Exception {
+        try {
+            r.run();
+        } catch (Exception e) {
+            if (ex.isAssignableFrom(e.getClass())) {
+                return;
+            }
+            throw e;
+        }
+        throw new Exception("No exception thrown");
+    }
+
+    static void assertTrue(boolean x) throws Exception {
+        if (!x) throw new Exception("Not true");
+    }
+
+    static class MyContentSigner extends ContentSigner {
+        @Override
+        public byte[] generateSignedData(
+                ContentSignerParameters parameters,
+                boolean omitContent,
+                boolean applyTimestamp) throws NoSuchAlgorithmException,
+                CertificateException, IOException {
+            return new byte[0];
+        }
+    }
+}
--- a/jdk/test/sun/security/mscapi/AccessKeyStore.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/AccessKeyStore.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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
@@ -36,17 +36,6 @@
 
     public static void main(String[] args) throws Exception {
 
-        // Check if the provider is available
-        try {
-            Class.forName("sun.security.mscapi.SunMSCAPI");
-
-        } catch (Exception e) {
-            System.out.println(
-                "The SunMSCAPI provider is not available on this platform: " +
-                e);
-            return;
-        }
-
         // Check that a security manager has been installed
         if (System.getSecurityManager() == null) {
             throw new Exception("A security manager has not been installed");
@@ -86,8 +75,8 @@
         }
 
         int i = 0;
-        for (Enumeration e = keyStore.aliases(); e.hasMoreElements(); ) {
-            String alias = (String) e.nextElement();
+        for (Enumeration<String> e = keyStore.aliases(); e.hasMoreElements(); ) {
+            String alias = e.nextElement();
             displayEntry(keyStore, alias, i++);
         }
     }
--- a/jdk/test/sun/security/mscapi/AccessKeyStore.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/AccessKeyStore.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 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
@@ -26,6 +26,7 @@
 
 # @test
 # @bug 6324295 6931562
+# @requires os.family == "windows"
 # @run shell AccessKeyStore.sh
 # @summary Confirm that permission must be granted to access keystores.
 
--- a/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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
@@ -33,16 +33,6 @@
 
     public static void main(String[] args) throws Exception {
 
-        // Check if the provider is available
-        try {
-            Class.forName("sun.security.mscapi.SunMSCAPI");
-
-        } catch (Exception e) {
-            System.out.println(
-                "The SunMSCAPI provider is not available on this platform");
-            return;
-        }
-
         // Dynamically register the SunMSCAPI provider
         Security.addProvider(new sun.security.mscapi.SunMSCAPI());
 
@@ -58,7 +48,6 @@
         /*
          * Secure Random
          */
-
         SecureRandom random = SecureRandom.getInstance("Windows-PRNG", p);
         System.out.println("    Windows-PRNG is implemented by: " +
             random.getClass().getName());
--- a/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 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
@@ -26,6 +26,7 @@
 
 # @test
 # @bug 6318171 6931562
+# @requires os.family == "windows"
 # @run shell IsSunMSCAPIAvailable.sh
 # @summary Basic test of the Microsoft CryptoAPI provider.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/mscapi/IterateWindowsRootStore.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactorySpi;
+import java.util.Collection;
+import java.util.Enumeration;
+
+/*
+ * @test
+ * @bug 8139436
+ * @summary This test validates an iteration over the Windows-ROOT certificate store
+ *          and retrieving all certificates.
+ *          Bug 8139436 reports an issue when 3rd party JCE providers would throw exceptions
+ *          upon creating Certificate objects.
+ *          This would for instance happen when using IAIK 3.15 and Elliptic Curve certificates
+ *          are contained in the Windows-ROOT certificate store.
+ *          The test uses a simple dummy provider which just throws Exceptions in its CertificateFactory.
+ *          To test an external provider, you can use property sun.security.mscapi.testprovider and
+ *          set it to the provider class name which has to be constructible by a constructor without
+ *          arguments. The provider jar has to be added to the classpath.
+ *          E.g. run jtreg with -javaoption:-Dsun.security.mscapi.testprovider=iaik.security.provider.IAIK and
+ *          -cpa:<path to iaik_jce.jar>
+ *
+ * @requires os.family == "windows"
+ * @author Christoph Langer
+ * @run main IterateWindowsRootStore
+ */
+public class IterateWindowsRootStore {
+    public static class TestFactory extends CertificateFactorySpi {
+        @Override
+        public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException {
+            throw new CertificateException("unimplemented");
+        }
+
+        @Override
+        public Collection<? extends Certificate> engineGenerateCertificates(InputStream inStream) throws CertificateException {
+            throw new CertificateException("unimplemented");
+        }
+
+        @Override
+        public CRL engineGenerateCRL(InputStream inStream) throws CRLException {
+            throw new CRLException("unimplemented");
+        }
+
+        @Override
+        public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException {
+            throw new CRLException("unimplemented");
+        }
+    }
+
+    public static class TestProvider extends Provider {
+        private static final long serialVersionUID = 1L;
+
+        public TestProvider() {
+            super("TestProvider", 0.1, "Test provider for IterateWindowsRootStore");
+
+            /*
+             * Certificates
+             */
+            this.put("CertificateFactory.X.509", "IterateWindowsRootStore$TestFactory");
+            this.put("Alg.Alias.CertificateFactory.X509", "X.509");
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        // Try to register a JCE provider from property sun.security.mscapi.testprovider in the first slot
+        // otherwise register a dummy provider which would provoke the issue of bug 8139436
+        boolean providerPrepended = false;
+        String testprovider = System.getProperty("sun.security.mscapi.testprovider");
+        if (testprovider != null && !testprovider.isEmpty()) {
+            try {
+                System.out.println("Trying to prepend external JCE provider " + testprovider);
+                Class<?> providerclass = Class.forName(testprovider);
+                Object provider = providerclass.newInstance();
+                Security.insertProviderAt((Provider)provider, 1);
+            } catch (Exception e) {
+                System.out.println("Could not load JCE provider " + testprovider +". Exception is:");
+                e.printStackTrace(System.out);
+            }
+            providerPrepended = true;
+            System.out.println("Sucessfully prepended JCE provider " + testprovider);
+        }
+        if (!providerPrepended) {
+            System.out.println("Trying to prepend dummy JCE provider");
+            Security.insertProviderAt(new TestProvider(), 1);
+            System.out.println("Sucessfully prepended dummy JCE provider");
+        }
+
+        // load Windows-ROOT KeyStore
+        KeyStore keyStore = KeyStore.getInstance("Windows-ROOT", "SunMSCAPI");
+        keyStore.load(null, null);
+
+        // iterate KeyStore
+        Enumeration<String> aliases = keyStore.aliases();
+        while (aliases.hasMoreElements()) {
+            String alias = aliases.nextElement();
+            System.out.print("Reading certificate for alias: " + alias + "...");
+            keyStore.getCertificate(alias);
+            System.out.println(" done.");
+        }
+    }
+}
--- a/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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
@@ -38,17 +38,6 @@
 
     public static void main(String[] args) throws Exception {
 
-        // Check if the provider is available
-        try {
-            Class.forName("sun.security.mscapi.SunMSCAPI");
-
-        } catch (Exception e) {
-            System.out.println(
-                "The SunMSCAPI provider is not available on this platform: " +
-                e);
-            return;
-        }
-
         if (args.length > 0 && "-disable".equals(args[0])) {
             mode = false;
         } else {
--- a/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2005, 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
@@ -23,9 +23,9 @@
 # questions.
 #
 
-
 # @test
 # @bug 6324294 6931562
+# @requires os.family == "windows"
 # @run shell KeyStoreCompatibilityMode.sh
 # @summary Confirm that a null stream or password is not permitted when 
 #          compatibility mode is enabled (and vice versa).
--- a/jdk/test/sun/security/mscapi/KeytoolChangeAlias.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/KeytoolChangeAlias.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2006, 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
@@ -26,6 +26,7 @@
 
 # @test
 # @bug 6415696 6931562
+# @requires os.family == "windows"
 # @run shell KeytoolChangeAlias.sh
 # @summary Test "keytool -changealias" using the Microsoft CryptoAPI provider.
 
--- a/jdk/test/sun/security/mscapi/PrngSlow.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/PrngSlow.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 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
@@ -24,6 +24,7 @@
 /**
  * @test
  * @bug 6449335
+ * @requires os.family == "windows"
  * @summary MSCAPI's PRNG is too slow
  * @key randomness
  */
@@ -34,23 +35,15 @@
 
     public static void main(String[] args) throws Exception {
         double t = 0.0;
-        try {
-            SecureRandom sr = null;
-            sr = SecureRandom.getInstance("PRNG", "SunMSCAPI");
-            long start = System.nanoTime();
-            int x = 0;
-            for(int i = 0; i < 10000; i++) {
-                if (i % 100 == 0) System.err.print(".");
-                if (sr.nextBoolean()) x++;
-            };
-            t = (System.nanoTime() - start) / 1000000000.0;
-            System.err.println("\nSpend " + t + " seconds");
-        } catch (Exception e) {
-            // Not supported here, maybe not a Win32
-            System.err.println("Cannot find PRNG for SunMSCAPI or other mysterious bugs");
-            e.printStackTrace();
-            return;
-        }
+        SecureRandom sr = null;
+        sr = SecureRandom.getInstance("Windows-PRNG", "SunMSCAPI");
+        long start = System.nanoTime();
+        for (int i = 0; i < 10000; i++) {
+            if (i % 100 == 0) System.err.print(".");
+            sr.nextBoolean();
+        };
+        t = (System.nanoTime() - start) / 1000000000.0;
+        System.err.println("\nSpend " + t + " seconds");
         if (t > 5)
             throw new RuntimeException("Still too slow");
     }
--- a/jdk/test/sun/security/mscapi/PublicKeyInterop.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/PublicKeyInterop.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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
@@ -38,8 +38,6 @@
 public class PublicKeyInterop {
 
     public static void main(String[] arg) throws Exception {
-        PrivateKey privKey = null;
-        Certificate cert = null;
         KeyStore ks = KeyStore.getInstance("Windows-MY");
         ks.load(null, null);
         System.out.println("Loaded keystore: Windows-MY");
--- a/jdk/test/sun/security/mscapi/PublicKeyInterop.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/PublicKeyInterop.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
@@ -25,6 +25,7 @@
 
 # @test
 # @bug 6888925
+# @requires os.family == "windows"
 # @run shell PublicKeyInterop.sh
 # @summary SunMSCAPI's Cipher can't use RSA public keys obtained from other
 #          sources.
--- a/jdk/test/sun/security/mscapi/RSAEncryptDecrypt.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/RSAEncryptDecrypt.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2006, 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
@@ -25,6 +25,7 @@
 
 # @test
 # @bug 6457422 6931562
+# @requires os.family == "windows"
 # @run shell RSAEncryptDecrypt.sh
 # @summary Confirm that plaintext can be encrypted and then decrypted using the
 #	   RSA cipher in the SunMSCAPI crypto provider. NOTE: The RSA cipher is 
--- a/jdk/test/sun/security/mscapi/ShortRSAKey1024.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/ShortRSAKey1024.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 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
@@ -27,6 +27,7 @@
 # @test
 # @bug 7106773
 # @summary 512 bits RSA key cannot work with SHA384 and SHA512
+# @requires os.family == "windows"
 # @run shell ShortRSAKey1024.sh 1024
 # @run shell ShortRSAKey1024.sh 768
 # @run shell ShortRSAKey1024.sh 512
--- a/jdk/test/sun/security/mscapi/ShortRSAKeyWithinTLS.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/ShortRSAKeyWithinTLS.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 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
@@ -22,12 +22,9 @@
  */
 
 import java.io.*;
-import java.net.*;
-import java.util.*;
 import java.security.*;
 import javax.net.*;
 import javax.net.ssl.*;
-import java.lang.reflect.*;
 
 import sun.security.util.KeyUtil;
 
--- a/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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
@@ -118,12 +118,12 @@
         ks.load(null, null);
         System.out.println("Loaded keystore: Windows-MY");
 
-        Enumeration e = ks.aliases();
+        Enumeration<String> e = ks.aliases();
         PrivateKey privateKey = null;
         PublicKey publicKey = null;
 
         while (e.hasMoreElements()) {
-            String alias = (String) e.nextElement();
+            String alias = e.nextElement();
             if (alias.equals("6578658")) {
                 System.out.println("Loaded entry: " + alias);
                 privateKey = (PrivateKey) ks.getKey(alias, null);
--- a/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
@@ -26,6 +26,7 @@
 
 # @test
 # @bug 6578658
+# @requires os.family == "windows"
 # @run shell SignUsingNONEwithRSA.sh
 # @summary Sign using the NONEwithRSA signature algorithm from SunMSCAPI
 # @key intermittent
--- a/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.java	Mon Nov 23 10:00:50 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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
@@ -58,12 +58,12 @@
         ks.load(null, null);
         System.out.println("Loaded keystore: Windows-MY");
 
-        Enumeration e = ks.aliases();
+        Enumeration<String> e = ks.aliases();
         PrivateKey privateKey = null;
         PublicKey publicKey = null;
 
         while (e.hasMoreElements()) {
-            String alias = (String) e.nextElement();
+            String alias = e.nextElement();
             if (alias.equals("6753664")) {
                 System.out.println("Loaded entry: " + alias);
                 privateKey = (PrivateKey) ks.getKey(alias, null);
--- a/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.sh	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.sh	Mon Nov 23 10:00:50 2015 -0800
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
@@ -26,6 +26,7 @@
 
 # @test
 # @bug 6753664
+# @requires os.family == "windows"
 # @run shell SignUsingSHA2withRSA.sh
 # @summary Support SHA256 (and higher) in SunMSCAPI
 # @key intermittent
--- a/jdk/test/sun/security/mscapi/SignatureOffsets.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SignatureOffsets.java	Mon Nov 23 10:00:50 2015 -0800
@@ -36,6 +36,7 @@
  *          and passing in different signature offset (0, 33, 66, 99).
  * @library /lib/testlibrary
  * @compile ../../../java/security/Signature/Offsets.java
+ * @requires os.family == "windows"
  * @run main SignatureOffsets SunMSCAPI NONEwithRSA
  * @run main SignatureOffsets SunMSCAPI MD2withRSA
  * @run main SignatureOffsets SunMSCAPI MD5withRSA
--- a/jdk/test/sun/security/mscapi/SignedObjectChain.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SignedObjectChain.java	Mon Nov 23 10:00:50 2015 -0800
@@ -25,6 +25,7 @@
  * @test
  * @bug 8050374
  * @compile ../../../java/security/SignedObject/Chain.java
+ * @requires os.family == "windows"
  * @summary Verify a chain of signed objects
  */
 public class SignedObjectChain {
--- a/jdk/test/sun/security/mscapi/SmallPrimeExponentP.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/security/mscapi/SmallPrimeExponentP.java	Mon Nov 23 10:00:50 2015 -0800
@@ -28,8 +28,6 @@
 import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPrivateCrtKey;
-import java.util.HashSet;
-import java.util.Set;
 
 /*
  * @test
@@ -37,6 +35,7 @@
  * @modules java.base/sun.security.x509
  *          java.base/sun.security.tools.keytool
  * @summary sun/security/mscapi/ShortRSAKey1024.sh fails intermittently
+ * @requires os.family == "windows"
  */
 public class SmallPrimeExponentP {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttrEncoding.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8048357
+ * @summary test DER encoding of PKCS10 attributes
+ * @modules java.base/sun.security.pkcs
+ *          java.base/sun.security.pkcs10
+ *          java.base/sun.security.util
+ *          java.base/sun.security.x509
+ * @compile -XDignore.symbol.file PKCS10AttrEncoding.java
+ * @run main PKCS10AttrEncoding
+ */
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import sun.security.pkcs.PKCS9Attribute;
+import sun.security.pkcs10.PKCS10;
+import sun.security.pkcs10.PKCS10Attribute;
+import sun.security.pkcs10.PKCS10Attributes;
+import sun.security.util.ObjectIdentifier;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509Key;
+
+public class PKCS10AttrEncoding {
+
+    static final ObjectIdentifier[] ids = {
+        PKCS9Attribute.CONTENT_TYPE_OID, // ContentType
+        PKCS9Attribute.SIGNING_TIME_OID, // SigningTime
+        PKCS9Attribute.CHALLENGE_PASSWORD_OID // ChallengePassword
+    };
+    static int failedCount = 0;
+    static HashMap<ObjectIdentifier, Object> constructedMap = new HashMap<>();
+
+    public static void main(String[] args) throws Exception {
+
+        // initializations
+        int len = ids.length;
+        Object[] values = {
+            new ObjectIdentifier("1.2.3.4"),
+            new GregorianCalendar(1970, 1, 25, 8, 56, 7).getTime(),
+            "challenging"
+        };
+        for (int j = 0; j < len; j++) {
+            constructedMap.put(ids[j], values[j]);
+        }
+
+        X500Name subject = new X500Name("cn=Test");
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
+        String sigAlg = "DSA";
+
+        keyGen.initialize(512);
+
+        KeyPair pair = keyGen.generateKeyPair();
+        X509Key publicKey = (X509Key) pair.getPublic();
+        PrivateKey privateKey = pair.getPrivate();
+
+        Signature signature = Signature.getInstance(sigAlg);
+        signature.initSign(privateKey);
+
+        // Create the PKCS10 request
+        PKCS10Attribute[] attrs = new PKCS10Attribute[len];
+        for (int j = 0; j < len; j++) {
+            attrs[j] = new PKCS10Attribute(ids[j], values[j]);
+        }
+        PKCS10 req = new PKCS10(publicKey, new PKCS10Attributes(attrs));
+        System.out.println("List of attributes in constructed PKCS10 "
+                + "request: ");
+        checkAttributes(req.getAttributes().getElements());
+
+        // Encode the PKCS10 request and generate another PKCS10 request from
+        // the encoded byte array
+        req.encodeAndSign(subject, signature);
+        PKCS10 resp = new PKCS10(req.getEncoded());
+        System.out.println("List of attributes in DER encoded PKCS10 Request:");
+        checkAttributes(resp.getAttributes().getElements());
+
+        if (failedCount > 0) {
+            throw new RuntimeException("Attributes Compared : Failed");
+        }
+        System.out.println("Attributes Compared : Pass");
+    }
+
+    static void checkAttributes(Enumeration attrs) {
+        int numOfAttrs = 0;
+        while (attrs.hasMoreElements()) {
+            numOfAttrs ++;
+            PKCS10Attribute attr = (PKCS10Attribute) attrs.nextElement();
+
+            if (constructedMap.containsKey(attr.getAttributeId())) {
+                if (constructedMap.get(attr.getAttributeId()).
+                        equals(attr.getAttributeValue())) {
+                    System.out.print("AttributeId: " + attr.getAttributeId());
+                    System.out.println(" AttributeValue: "
+                            + attr.getAttributeValue());
+                } else {
+                    failedCount++;
+                    System.out.print("< AttributeId: " + attr.getAttributeId());
+                    System.out.println("  AttributeValue: " + constructedMap.
+                            get(attr.getAttributeId()));
+                    System.out.print("< AttributeId: " + attr.getAttributeId());
+                    System.out.println("  AttributeValue: "
+                            + attr.getAttributeValue());
+                }
+            } else {
+                failedCount++;
+                System.out.println("No " + attr.getAttributeId()
+                        + " in DER encoded PKCS10 Request");
+            }
+        }
+        if(numOfAttrs != constructedMap.size()){
+            failedCount++;
+            System.out.println("Incorrect number of attributes.");
+
+        }
+        System.out.println();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttributeReader.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8048357
+ * @summary Read in a file containing a DER encoded PKCS10 certificate request,
+ * flanked with "begin" and "end" lines.
+ * @modules java.base/sun.security.pkcs
+ *          java.base/sun.security.pkcs10
+ *          java.base/sun.security.util
+ * @compile -XDignore.symbol.file PKCS10AttributeReader.java
+ * @run main PKCS10AttributeReader
+ */
+import java.util.Base64;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Date;
+import sun.security.pkcs.PKCS9Attribute;
+import sun.security.pkcs10.PKCS10Attribute;
+import sun.security.pkcs10.PKCS10Attributes;
+import sun.security.util.DerInputStream;
+import sun.security.util.ObjectIdentifier;
+
+/*
+ Tests only reads DER encoding files, contents of corresponding asn.1 files
+ are copied below for reference.
+
+ # An attribute set for testing with PKCS10.
+
+ {A0  # implicit tag
+    {SEQ  # Content Type
+        {OID 1.2.840.113549.1.9.3}
+        {SET
+            {OID "1234"}
+        }
+    }
+     {SEQ  # Challenge Password
+         {OID 1.2.840.113549.1.9.7}
+         {SET
+             {T61String "GuessWhoAmI"}
+         }
+     }
+     {SEQ  # Signing Time
+        {OID 1.2.840.113549.1.9.5}
+        {SET
+            {UTCTime "970422145010Z"}
+        }
+     }
+ }
+ */
+public class PKCS10AttributeReader {
+    // DER encoded files are binary files, to avoid attaching binary files,
+    // DER files were encoded in base64
+    static final String ATTRIBS = "oE8wEwYJKoZIhvcNAQkDMQYGBDEyMzQwGgYJKoZIhv"
+            + "cNAQkHMQ0UC0d1ZXNzV2hv\nQW1JMBwGCSqGSIb3DQEJBTEPFw05NzA0MjIxND"
+            + "UwMTBa";
+
+    public static void main(String[] args) throws Exception {
+
+        // Decode base64 encoded DER file
+        byte[] pkcs10Bytes = Base64.getMimeDecoder().decode(ATTRIBS.getBytes());
+
+        HashMap<ObjectIdentifier, Object> RequestStander = new HashMap() {
+            {
+                put(PKCS9Attribute.CHALLENGE_PASSWORD_OID, "GuessWhoAmI");
+                put(PKCS9Attribute.SIGNING_TIME_OID, new Date(861720610000L));
+                put(PKCS9Attribute.CONTENT_TYPE_OID,
+                        new ObjectIdentifier("1.9.50.51.52"));
+            }
+        };
+
+        int invalidNum = 0;
+        PKCS10Attributes resp = new PKCS10Attributes(
+                new DerInputStream(pkcs10Bytes));
+        Enumeration eReq = resp.getElements();
+        int numOfAttrs = 0;
+        while (eReq.hasMoreElements()) {
+            numOfAttrs++;
+            PKCS10Attribute attr = (PKCS10Attribute) eReq.nextElement();
+            if (RequestStander.containsKey(attr.getAttributeId())) {
+                if (RequestStander.get(attr.getAttributeId())
+                        .equals(attr.getAttributeValue())) {
+                    System.out.println(attr.getAttributeId() + " "
+                            + attr.getAttributeValue());
+                } else {
+                    invalidNum++;
+                    System.out.println("< " + attr.getAttributeId() + " "
+                            + attr.getAttributeValue());
+                    System.out.println("< " + attr.getAttributeId() + " "
+                            + RequestStander.get(attr.getAttributeId()));
+                }
+            } else {
+                invalidNum++;
+                System.out.println("No" + attr.getAttributeId()
+                        + "in Certificate Request list");
+            }
+        }
+        if (numOfAttrs != RequestStander.size()) {
+            invalidNum++;
+            System.out.println("Incorrect number of attributes.");
+        }
+        System.out.println();
+        if (invalidNum > 0) {
+            throw new RuntimeException(
+                    "Attributes Compared with Stander :" + " Failed");
+        }
+        System.out.println("Attributes Compared with Stander: Pass");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs7/PKCS7VerifyTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8048357
+ * @summary Read signed data in one or more PKCS7 objects from individual files,
+ * verify SignerInfos and certificate chain.
+ * @modules java.base/sun.security.pkcs
+ * @run main PKCS7VerifyTest PKCS7TEST.DSA.base64
+ * @run main PKCS7VerifyTest PKCS7TEST.DSA.base64 PKCS7TEST.SF
+ */
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.SignerInfo;
+
+public class PKCS7VerifyTest {
+
+    static final String TESTSRC = System.getProperty("test.src", ".");
+    static final String FS = File.separator;
+    static final String FILEPATH = TESTSRC + FS + "jarsigner" + FS + "META-INF"
+            + FS;
+
+    public static void main(String[] args) throws Exception {
+        if (args.length == 0) {
+            throw new RuntimeException("usage: java JarVerify <file1> <file2>");
+        }
+
+        // The command " java PKCS7VerifyTest file1 [file2] "
+        // treats file1 as containing the DER encoding of a PKCS7 signed data
+        // object. If file2 is absent, the program verifies that some signature
+        // (SignerInfo) file1 correctly signs the data contained in the
+        // ContentInfo component of the PKCS7 object encoded by file1. If file2
+        // is present, the program verifies file1 contains a correct signature
+        // for the contents of file2.
+
+        PKCS7 pkcs7;
+        byte[] data;
+
+        // to avoid attaching binary DSA file, the DSA file was encoded
+        // in Base64, decode encoded Base64 DSA file below
+        byte[] base64Bytes = Files.readAllBytes(Paths.get(FILEPATH + args[0]));
+        pkcs7 = new PKCS7(new ByteArrayInputStream(
+                Base64.getMimeDecoder().decode(base64Bytes)));
+        if (args.length < 2) {
+            data = null;
+        } else {
+            data = Files.readAllBytes(Paths.get(FILEPATH + args[1]));
+
+        }
+
+        SignerInfo[] signerInfos = pkcs7.verify(data);
+
+        if (signerInfos == null) {
+            throw new RuntimeException("no signers verify");
+        }
+        System.out.println("Verifying SignerInfos:");
+        for (SignerInfo signerInfo : signerInfos) {
+            System.out.println(signerInfo.toString());
+        }
+
+        X509Certificate certs[] = pkcs7.getCertificates();
+
+        HashMap<String, X509Certificate> certTable = new HashMap(certs.length);
+        for (X509Certificate cert : certs) {
+            certTable.put(cert.getSubjectDN().toString(), cert);
+        }
+
+        // try to verify all the certs
+        for (Map.Entry<String, X509Certificate> entry : certTable.entrySet()) {
+
+            X509Certificate cert = entry.getValue();
+            X509Certificate issuerCert = certTable
+                    .get(cert.getIssuerDN().toString());
+
+            System.out.println("Subject: " + cert.getSubjectDN());
+            if (issuerCert == null) {
+                System.out.println("Issuer certificate not found");
+            } else {
+                System.out.println("Issuer:  " + cert.getIssuerDN());
+                cert.verify(issuerCert.getPublicKey());
+                System.out.println("Cert verifies.");
+            }
+            System.out.println();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs7/SignerOrder.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8048357
+ * @summary test PKCS7 data signing, encoding and verification
+ * @modules java.base/sun.security.pkcs
+ *          java.base/sun.security.util
+ *          java.base/sun.security.x509
+ * @run main SignerOrder
+ */
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import sun.misc.HexDumpEncoder;
+import sun.security.pkcs.ContentInfo;
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.SignerInfo;
+import sun.security.util.DerOutputStream;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertificateAlgorithmId;
+import sun.security.x509.CertificateSerialNumber;
+import sun.security.x509.CertificateValidity;
+import sun.security.x509.CertificateVersion;
+import sun.security.x509.CertificateX509Key;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509CertImpl;
+import sun.security.x509.X509CertInfo;
+import sun.security.x509.X509Key;
+
+public class SignerOrder {
+
+    static final HexDumpEncoder hexDump = new HexDumpEncoder();
+
+    //signer infos
+    static final byte[] data1 = "12345".getBytes();
+    static final byte[] data2 = "abcde".getBytes();
+
+    public static void main(String[] argv) throws Exception {
+
+        SignerInfo[] signerInfos = new SignerInfo[9];
+        SimpleSigner signer1 = new SimpleSigner(null, null, null, null);
+        signerInfos[8] = signer1.genSignerInfo(data1);
+        signerInfos[7] = signer1.genSignerInfo(new byte[]{});
+        signerInfos[6] = signer1.genSignerInfo(data2);
+
+        SimpleSigner signer2 = new SimpleSigner(null, null, null, null);
+        signerInfos[5] = signer2.genSignerInfo(data1);
+        signerInfos[4] = signer2.genSignerInfo(new byte[]{});
+        signerInfos[3] = signer2.genSignerInfo(data2);
+
+        SimpleSigner signer3 = new SimpleSigner(null, null, null, null);
+        signerInfos[2] = signer3.genSignerInfo(data1);
+        signerInfos[1] = signer3.genSignerInfo(new byte[]{});
+        signerInfos[0] = signer3.genSignerInfo(data2);
+
+        ContentInfo contentInfo = new ContentInfo(data1);
+
+        AlgorithmId[] algIds = {new AlgorithmId(AlgorithmId.SHA256_oid)};
+
+        X509Certificate[] certs = {signer3.getCert(), signer2.getCert(),
+            signer1.getCert()};
+
+        PKCS7 pkcs71 = new PKCS7(algIds, contentInfo,
+                certs,
+                signerInfos);
+
+        System.out.println("SignerInfos in original.");
+        printSignerInfos(pkcs71.getSignerInfos());
+
+        DerOutputStream out = new DerOutputStream();
+        pkcs71.encodeSignedData(out);
+
+        PKCS7 pkcs72 = new PKCS7(out.toByteArray());
+        System.out.println("\nSignerInfos read back in:");
+        printSignerInfos(pkcs72.getSignerInfos());
+
+        System.out.println("Verified signers of original:");
+        SignerInfo[] verifs1 = pkcs71.verify();
+
+        System.out.println("Verified signers of after read-in:");
+        SignerInfo[] verifs2 = pkcs72.verify();
+
+        if (verifs1.length != verifs2.length) {
+            throw new RuntimeException("Length or Original vs read-in "
+                    + "should be same");
+        }
+    }
+
+    static void printSignerInfos(SignerInfo signerInfo) throws IOException {
+        ByteArrayOutputStream strm = new ByteArrayOutputStream();
+        signerInfo.derEncode(strm);
+        System.out.println("SignerInfo, length: "
+                + strm.toByteArray().length);
+        System.out.println(hexDump.encode(strm.toByteArray()));
+        System.out.println("\n");
+        strm.reset();
+    }
+
+    static void printSignerInfos(SignerInfo[] signerInfos) throws IOException {
+        ByteArrayOutputStream strm = new ByteArrayOutputStream();
+        for (int i = 0; i < signerInfos.length; i++) {
+            signerInfos[i].derEncode(strm);
+            System.out.println("SignerInfo[" + i + "], length: "
+                    + strm.toByteArray().length);
+            System.out.println(hexDump.encode(strm.toByteArray()));
+            System.out.println("\n");
+            strm.reset();
+        }
+    }
+
+}
+
+/**
+ * A simple extension of sun.security.x509.X500Signer that adds a no-fuss
+ * signing algorithm.
+ */
+class SimpleSigner {
+
+    private final Signature sig;
+    private final X500Name agent;
+    private final AlgorithmId digestAlgId;
+    private final AlgorithmId encryptionAlgId;
+    private final AlgorithmId algId; // signature algid;
+                                     //combines digest + encryption
+    private final X509Key publicKey;
+    private final PrivateKey privateKey;
+    private final X509Certificate cert;
+
+    public SimpleSigner(String digestAlg,
+            String encryptionAlg,
+            KeyPair keyPair,
+            X500Name agent) throws Exception {
+
+        if (agent == null) {
+            agent = new X500Name("cn=test");
+        }
+        if (digestAlg == null) {
+            digestAlg = "SHA";
+        }
+        if (encryptionAlg == null) {
+            encryptionAlg = "DSA";
+        }
+        if (keyPair == null) {
+            KeyPairGenerator keyGen =
+                    KeyPairGenerator.getInstance(encryptionAlg);
+            keyGen.initialize(1024);
+            keyPair = keyGen.generateKeyPair();
+        }
+        publicKey = (X509Key) keyPair.getPublic();
+        privateKey = keyPair.getPrivate();
+
+        if ("DSA".equals(encryptionAlg)) {
+            this.sig = Signature.getInstance(encryptionAlg);
+        } else { // RSA
+            this.sig = Signature.getInstance(digestAlg + "/" + encryptionAlg);
+        }
+        this.sig.initSign(privateKey);
+
+        this.agent = agent;
+        this.digestAlgId = AlgorithmId.get(digestAlg);
+        this.encryptionAlgId = AlgorithmId.get(encryptionAlg);
+        this.algId = AlgorithmId.get(this.sig.getAlgorithm());
+
+        this.cert = getSelfCert();
+    }
+
+    /**
+     * Take the data and sign it.
+     *
+     * @param buf buffer holding the next chunk of the data to be signed
+     * @param offset starting point of to-be-signed data
+     * @param len how many bytes of data are to be signed
+     * @return the signature for the input data.
+     * @exception SignatureException on errors.
+     */
+    public byte[] simpleSign(byte[] buf, int offset, int len)
+            throws SignatureException {
+        sig.update(buf, offset, len);
+        return sig.sign();
+    }
+
+    /**
+     * Returns the digest algorithm used to sign.
+     */
+    public AlgorithmId getDigestAlgId() {
+        return digestAlgId;
+    }
+
+    /**
+     * Returns the encryption algorithm used to sign.
+     */
+    public AlgorithmId getEncryptionAlgId() {
+        return encryptionAlgId;
+    }
+
+    /**
+     * Returns the name of the signing agent.
+     */
+    public X500Name getSigner() {
+        return agent;
+    }
+
+    public X509Certificate getCert() {
+        return cert;
+    }
+
+    private X509Certificate getSelfCert() throws Exception {
+        long validity = 1000;
+        X509CertImpl certLocal;
+        Date firstDate, lastDate;
+
+        firstDate = new Date();
+        lastDate = new Date();
+        lastDate.setTime(lastDate.getTime() + validity + 1000);
+
+        CertificateValidity interval = new CertificateValidity(firstDate,
+                lastDate);
+
+        X509CertInfo info = new X509CertInfo();
+        // Add all mandatory attributes
+        info.set(X509CertInfo.VERSION,
+                new CertificateVersion(CertificateVersion.V1));
+        info.set(X509CertInfo.SERIAL_NUMBER,
+                new CertificateSerialNumber(
+                        (int) (firstDate.getTime() / 1000)));
+        info.set(X509CertInfo.ALGORITHM_ID,
+                new CertificateAlgorithmId(algId));
+        info.set(X509CertInfo.SUBJECT, agent);
+        info.set(X509CertInfo.KEY, new CertificateX509Key(publicKey));
+        info.set(X509CertInfo.VALIDITY, interval);
+        info.set(X509CertInfo.ISSUER, agent);
+
+        certLocal = new X509CertImpl(info);
+        certLocal.sign(privateKey, algId.getName());
+
+        return certLocal;
+    }
+
+    public SignerInfo genSignerInfo(byte[] data) throws SignatureException {
+        return new SignerInfo((X500Name) cert.getIssuerDN(),
+                new BigInteger("" + cert.getSerialNumber()),
+                getDigestAlgId(), algId,
+                simpleSign(data, 0, data.length));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/MANIFEST.MF	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,82 @@
+Manifest-Version: 1.0
+
+Name: CheckCerts.class
+Digest-Algorithms: SHA
+SHA-Digest: xLygljhRro6990piIVEilVI8szQ=
+
+Name: ContentInfoTest.class
+Digest-Algorithms: SHA
+SHA-Digest: TSVdEMQW2gdFi6qeba+UixdHSdo=
+
+Name: JarVerify.class
+Digest-Algorithms: SHA
+SHA-Digest: Wg+PiDzunNGH4KrWAp00/okp39s=
+
+Name: JarVerify2.class
+Digest-Algorithms: SHA
+SHA-Digest: 5uYBQxwGWgYmNBwhnWRbymeXmWM=
+
+Name: PKCS7Read.class
+Digest-Algorithms: SHA
+SHA-Digest: JPIxttHBfRpQaFyiQJ2Wfkvj/ls=
+
+Name: PKCS7Test.class
+Digest-Algorithms: SHA
+SHA-Digest: R64SXXgZrOvGiO/eMsfG/T1Vn30=
+
+Name: PKCS7Test10.class
+Digest-Algorithms: SHA
+SHA-Digest: 2R0yxuxRHTPqdAzJJcrvqkpbQgo=
+
+Name: PKCS7Test11.class
+Digest-Algorithms: SHA
+SHA-Digest: /0HcwnpQi0hwJsJtvt5peWFGvtc=
+
+Name: PKCS7Test12.class
+Digest-Algorithms: SHA
+SHA-Digest: s5CcqimfRqR9CW25tFBY0JK3RVU=
+
+Name: PKCS7Test2.class
+Digest-Algorithms: SHA
+SHA-Digest: 71VkFEMUle5sjXNFbSW31F1ZJ58=
+
+Name: PKCS7Test3.class
+Digest-Algorithms: SHA
+SHA-Digest: mU/D5C6SgPRmwoLQzwF5VnN3aqM=
+
+Name: PKCS7Test4.class
+Digest-Algorithms: SHA
+SHA-Digest: ss9NFvxF8emaEjdKdvtzWXfs0/E=
+
+Name: PKCS7Test5.class
+Digest-Algorithms: SHA
+SHA-Digest: DHvQ20UAXoYgfCPAOeCOrglsJwU=
+
+Name: PKCS7Test6.class
+Digest-Algorithms: SHA
+SHA-Digest: aiCb8chroH7XDaNfAz6wr57lXsA=
+
+Name: PKCS7Test7.class
+Digest-Algorithms: SHA
+SHA-Digest: UoieXLC68alFgfD/Q1NW9/r2kaY=
+
+Name: PKCS7Test8.class
+Digest-Algorithms: SHA
+SHA-Digest: eMW7mq5b/KVB1M5L76wcV1+uFQs=
+
+Name: PKCS7Test9.class
+Digest-Algorithms: SHA
+SHA-Digest: EEWCZG1creWjqVZVIEgr0on3y6A=
+
+Name: SignerInfoTest.class
+Digest-Algorithms: SHA
+SHA-Digest: l6SNfpnFipGg8gy4XqY3HhA0RrY=
+
+Name: SignerInfoTest2.class
+Digest-Algorithms: SHA
+SHA-Digest: 5jbzlkZqXKNmmmE+pcjQka8D6WE=
+
+Name: SimpleSigner.class
+Digest-Algorithms: SHA
+SHA-Digest: l9ODQHY4wxhIvLw4/B0qe9NjwxQ=
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.DSA.base64	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,60 @@
+MIILKAYJKoZIhvcNAQcCoIILGTCCCxUCAQExCzAJBgUrDgMCGgUAMIIHbQYJKoZI
+hvcNAQcBoIIHXgSCB1pTaWduYXR1cmUtVmVyc2lvbjogMS4wDQoNCk5hbWU6IENo
+ZWNrQ2VydHMuY2xhc3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdl
+c3Q6IHlhMXh3dnNRTytEUnBRYnczRmgyblJCMkpRYz0NCg0KTmFtZTogQ29udGVu
+dEluZm9UZXN0LmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGln
+ZXN0OiBDYStFSmFrVTZ6dzRLQWhvcWNuQ3BOcWsyTEk9DQoNCk5hbWU6IEphclZl
+cmlmeS5jbGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDog
+K0RHYVdXa25md2U0Wk9wc29NVEZ6ZldSdmhRPQ0KDQpOYW1lOiBKYXJWZXJpZnky
+LmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGlnZXN0OiBHcUR6
+WXlZNFAvV0g1SEt2aVdxWHR0UGc1ckU9DQoNCk5hbWU6IFBLQ1M3UmVhZC5jbGFz
+cw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDogUW1mOEs5aFhW
+bHdJZFBZNm52MmpGUGZHcWtBPQ0KDQpOYW1lOiBQS0NTN1Rlc3QuY2xhc3MNCkRp
+Z2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IEdiZS9nenl2MkY1OGY2
+RUVoU1oxQnFHWHRsbz0NCg0KTmFtZTogUEtDUzdUZXN0MTAuY2xhc3MNCkRpZ2Vz
+dC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IDh3QnFXLy9lVzJzTlJJOTFi
+TFlFT29kY2dhRT0NCg0KTmFtZTogUEtDUzdUZXN0MTEuY2xhc3MNCkRpZ2VzdC1B
+bGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IGJYaExLRXNsY3VFWGk0dS9haGdU
+MnE2dGNFVT0NCg0KTmFtZTogUEtDUzdUZXN0MTIuY2xhc3MNCkRpZ2VzdC1BbGdv
+cml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IDlLRVkxYjUyUUxtTjBxei81ejB3QkZy
+T216MD0NCg0KTmFtZTogUEtDUzdUZXN0Mi5jbGFzcw0KRGlnZXN0LUFsZ29yaXRo
+bXM6IFNIQQ0KU0hBLURpZ2VzdDogK1VhMzIvMlE4RjJiclFRbVNYWCtYUytNL2g0
+PQ0KDQpOYW1lOiBQS0NTN1Rlc3QzLmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczog
+U0hBDQpTSEEtRGlnZXN0OiAwSFhVWnlhU2ZkZUtlZThuWnpFalJTeXJldTQ9DQoN
+Ck5hbWU6IFBLQ1M3VGVzdDQuY2xhc3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEEN
+ClNIQS1EaWdlc3Q6IEo3eXJTMjRvS3VTZ2F1dHZkemhxQmo3ZGJjUT0NCg0KTmFt
+ZTogUEtDUzdUZXN0NS5jbGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hB
+LURpZ2VzdDogSlR2OVdTb3gxTEVTUjJMcTdzMFVxU2x0RFNRPQ0KDQpOYW1lOiBQ
+S0NTN1Rlc3Q2LmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGln
+ZXN0OiBnR3Yra05oK3UzSFExdHp4bGNBVzdTcEZUS2s9DQoNCk5hbWU6IFBLQ1M3
+VGVzdDcuY2xhc3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6
+IGZpSEYxYUExYWN6czFPd0V5OEc3VkMrcjdMST0NCg0KTmFtZTogUEtDUzdUZXN0
+OC5jbGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDogNzRU
+VzdJOVZPdzVWZ0x2aFJtRGZxRVd2ZkFRPQ0KDQpOYW1lOiBQS0NTN1Rlc3Q5LmNs
+YXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGlnZXN0OiAxY0JJbkdU
+Y08xQVFaKy8wdmhGa2laV3dsQTA9DQoNCk5hbWU6IFNpZ25lckluZm9UZXN0LmNs
+YXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGlnZXN0OiBjRlk0Q3RT
+anphMUErV2pBS05TVnF1cGpSWUU9DQoNCk5hbWU6IFNpZ25lckluZm9UZXN0Mi5j
+bGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDogYU5NMEZQ
+MHpFelF6eGxYeDZxQ0J4dWtta0hRPQ0KDQpOYW1lOiBTaW1wbGVTaWduZXIuY2xh
+c3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IC9MV0NzbkM3
+TVpNUjZHb3czeTJjdnA3STBTTT0NCg0KoIICvzCCArswggJ3AgUA59UzNDALBgcq
+hkjOOAQDBQAwdTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlD
+dXBlcnRpbm8xGTAXBgNVBAoTEFN1biBNaWNyb3N5c3RlbXMxETAPBgNVBAsTCEph
+dmFTb2Z0MRcwFQYDVQQDEw5Eb3VnbGFzIEhvb3ZlcjAeFw05NzEwMDIxODEyMDda
+Fw05NzEyMzExNzEyMDdaMHUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAG
+A1UEBxMJQ3VwZXJ0aW5vMRkwFwYDVQQKExBTdW4gTWljcm9zeXN0ZW1zMREwDwYD
+VQQLEwhKYXZhU29mdDEXMBUGA1UEAxMORG91Z2xhcyBIb292ZXIwggFRMIHoBgcq
+hkjOOAQBMIHcAmEA6eZCWZ01XzfJf/01ZxILjiXJzUPpJ7OpZw++xdiQFBki0sOz
+rSSACTeZhp0ehGqrSfqwrSbSzmoiIZ1HC859d31KIfvpwnC1f2BwAvPO+Dk2lM9F
+7jaIwRqMVqsSej2vAhUAnNvYTJ8awvOND4D0KrlS5zOL9RECYDBHCtWgBfsUzi2d
+zYfji8fRscX6y67L6V8ZCqejHSPE27y+BhdFREAaWywCCWXYwr0hcdNmhEV3H3S6
+CE0gKdg8HBWFR/Op8aJxW+I9Ua5NPlofanBk8xaTOjRtP1KSUgNkAAJhAMN5uB+B
+ZJ0W2UjXMyKoFUFXRYiLpnaSw63kl9tKnR9R5rEreiyHQ5IelPxjwCHGgTbYK0y+
+xKTGHVWiQN/YJmHLbSrcSSM/d89aR/sVbGoAwQOyYraFGUNIOTQjjXcXCjALBgcq
+hkjOOAQDBQADMQAwLgIVAJxmL029GLXDJVbk72d4cSPQ4/rvAhUAll9UPl8aOMEg
+V4egANhwbynMGSgxgc4wgcsCAQEwfjB1MQswCQYDVQQGEwJVUzELMAkGA1UECBMC
+Q0ExEjAQBgNVBAcTCUN1cGVydGlubzEZMBcGA1UEChMQU3VuIE1pY3Jvc3lzdGVt
+czERMA8GA1UECxMISmF2YVNvZnQxFzAVBgNVBAMTDkRvdWdsYXMgSG9vdmVyAgUA
+59UzNDAJBgUrDgMCGgUAMAsGByqGSM44BAMFAAQuMCwCFDmry17kzDD6Y5X1BqIS
+lq6swckPAhRtiXvBHa5CRGjbwk8yqf9hGgZfFA==
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.SF	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,82 @@
+Signature-Version: 1.0
+
+Name: CheckCerts.class
+Digest-Algorithms: SHA
+SHA-Digest: ya1xwvsQO+DRpQbw3Fh2nRB2JQc=
+
+Name: ContentInfoTest.class
+Digest-Algorithms: SHA
+SHA-Digest: Ca+EJakU6zw4KAhoqcnCpNqk2LI=
+
+Name: JarVerify.class
+Digest-Algorithms: SHA
+SHA-Digest: +DGaWWknfwe4ZOpsoMTFzfWRvhQ=
+
+Name: JarVerify2.class
+Digest-Algorithms: SHA
+SHA-Digest: GqDzYyY4P/WH5HKviWqXttPg5rE=
+
+Name: PKCS7Read.class
+Digest-Algorithms: SHA
+SHA-Digest: Qmf8K9hXVlwIdPY6nv2jFPfGqkA=
+
+Name: PKCS7Test.class
+Digest-Algorithms: SHA
+SHA-Digest: Gbe/gzyv2F58f6EEhSZ1BqGXtlo=
+
+Name: PKCS7Test10.class
+Digest-Algorithms: SHA
+SHA-Digest: 8wBqW//eW2sNRI91bLYEOodcgaE=
+
+Name: PKCS7Test11.class
+Digest-Algorithms: SHA
+SHA-Digest: bXhLKEslcuEXi4u/ahgT2q6tcEU=
+
+Name: PKCS7Test12.class
+Digest-Algorithms: SHA
+SHA-Digest: 9KEY1b52QLmN0qz/5z0wBFrOmz0=
+
+Name: PKCS7Test2.class
+Digest-Algorithms: SHA
+SHA-Digest: +Ua32/2Q8F2brQQmSXX+XS+M/h4=
+
+Name: PKCS7Test3.class
+Digest-Algorithms: SHA
+SHA-Digest: 0HXUZyaSfdeKee8nZzEjRSyreu4=
+
+Name: PKCS7Test4.class
+Digest-Algorithms: SHA
+SHA-Digest: J7yrS24oKuSgautvdzhqBj7dbcQ=
+
+Name: PKCS7Test5.class
+Digest-Algorithms: SHA
+SHA-Digest: JTv9WSox1LESR2Lq7s0UqSltDSQ=
+
+Name: PKCS7Test6.class
+Digest-Algorithms: SHA
+SHA-Digest: gGv+kNh+u3HQ1tzxlcAW7SpFTKk=
+
+Name: PKCS7Test7.class
+Digest-Algorithms: SHA
+SHA-Digest: fiHF1aA1aczs1OwEy8G7VC+r7LI=
+
+Name: PKCS7Test8.class
+Digest-Algorithms: SHA
+SHA-Digest: 74TW7I9VOw5VgLvhRmDfqEWvfAQ=
+
+Name: PKCS7Test9.class
+Digest-Algorithms: SHA
+SHA-Digest: 1cBInGTcO1AQZ+/0vhFkiZWwlA0=
+
+Name: SignerInfoTest.class
+Digest-Algorithms: SHA
+SHA-Digest: cFY4CtSjza1A+WjAKNSVqupjRYE=
+
+Name: SignerInfoTest2.class
+Digest-Algorithms: SHA
+SHA-Digest: aNM0FP0zEzQzxlXx6qCBxukmkHQ=
+
+Name: SimpleSigner.class
+Digest-Algorithms: SHA
+SHA-Digest: /LWCsnC7MZMR6Gow3y2cvp7I0SM=
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs/pkcs8/PKCS8Test.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,294 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8048357
+ * @summary PKCS8 Standards Conformance Tests
+ * @modules java.base/sun.security.pkcs
+ *          java.base/sun.security.util
+ *          java.base/sun.security.provider
+ *          java.base/sun.security.x509
+ *          java.base/sun.misc
+ * @compile -XDignore.symbol.file PKCS8Test.java
+ * @run main PKCS8Test
+ */
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+import sun.misc.HexDumpEncoder;
+import sun.security.pkcs.PKCS8Key;
+import sun.security.provider.DSAPrivateKey;
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+import sun.security.x509.AlgorithmId;
+
+import static java.lang.System.out;
+
+public class PKCS8Test {
+
+    static final HexDumpEncoder hexDump = new HexDumpEncoder();
+
+    static final DerOutputStream derOutput = new DerOutputStream();
+
+    static final String FORMAT = "PKCS#8";
+    static final String EXPECTED_ALG_ID_CHRS = "DSA\n\tp:     02\n\tq:     03\n"
+            + "\tg:     04\n";
+    static final String ALGORITHM = "DSA";
+    static final String EXCEPTION_MESSAGE = "version mismatch: (supported:     "
+            + "00, parsed:     01";
+
+    // test second branch in byte[] encode()
+    // DER encoding,include (empty) set of attributes
+    static final int[] NEW_ENCODED_KEY_INTS = { 0x30,
+            // length 30 = 0x1e
+            0x1e,
+            // first element
+            // version Version (= INTEGER)
+            0x02,
+            // length 1
+            0x01,
+            // value 0
+            0x00,
+            // second element
+            // privateKeyAlgorithmIdentifier PrivateKeyAlgorithmIdentifier
+            // (sequence)
+            // (an object identifier?)
+            0x30,
+            // length 18
+            0x12,
+            // contents
+            // object identifier, 5 bytes
+            0x06, 0x05,
+            // { 1 3 14 3 2 12 }
+            0x2b, 0x0e, 0x03, 0x02, 0x0c,
+            // sequence, 9 bytes
+            0x30, 0x09,
+            // integer 2
+            0x02, 0x01, 0x02,
+            // integer 3
+            0x02, 0x01, 0x03,
+            // integer 4
+            0x02, 0x01, 0x04,
+            // third element
+            // privateKey PrivateKey (= OCTET STRING)
+            0x04,
+            // length
+            0x03,
+            // privateKey contents
+            0x02, 0x01, 0x01,
+            // 4th (optional) element -- attributes [0] IMPLICIT Attributes
+            // OPTIONAL
+            // (Attributes = SET OF Attribute) Here, it will be empty.
+            0xA0,
+            // length
+            0x00 };
+
+    // encoding originally created, but with the version changed
+    static final int[] NEW_ENCODED_KEY_INTS_2 = {
+            // sequence
+            0x30,
+            // length 28 = 0x1c
+            0x1c,
+            // first element
+            // version Version (= INTEGER)
+            0x02,
+            // length 1
+            0x01,
+            // value 1 (illegal)
+            0x01,
+            // second element
+            // privateKeyAlgorithmIdentifier PrivateKeyAlgorithmIdentifier
+            // (sequence)
+            // (an object identifier?)
+            0x30,
+            // length 18
+            0x12,
+            // contents
+            // object identifier, 5 bytes
+            0x06, 0x05,
+            // { 1 3 14 3 2 12 }
+            0x2b, 0x0e, 0x03, 0x02, 0x0c,
+            // sequence, 9 bytes
+            0x30, 0x09,
+            // integer 2
+            0x02, 0x01, 0x02,
+            // integer 3
+            0x02, 0x01, 0x03,
+            // integer 4
+            0x02, 0x01, 0x04,
+            // third element
+            // privateKey PrivateKey (= OCTET STRING)
+            0x04,
+            // length
+            0x03,
+            // privateKey contents
+            0x02, 0x01, 0x01 };
+
+    // 0000: 30 1E 02 01 00 30 14 06 07 2A 86 48 CE 38 04 01 0....0...*.H.8..
+    // 0010: 30 09 02 01 02 02 01 03 02 01 04 04 03 02 01 01 0...............
+    static final int[] EXPECTED = { 0x30,
+            // length 30 = 0x1e
+            0x1e,
+            // first element
+            // version Version (= INTEGER)
+            0x02,
+            // length 1
+            0x01,
+            // value 0
+            0x00,
+            // second element
+            // privateKeyAlgorithmIdentifier PrivateKeyAlgorithmIdentifier
+            // (sequence)
+            // (an object identifier?)
+            0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x01,
+            // integer 2
+            0x30, 0x09, 0x02,
+            // integer 3
+            0x01, 0x02, 0x02,
+            // integer 4
+            0x01, 0x03, 0x02,
+            // third element
+            // privateKey PrivateKey (= OCTET STRING)
+            0x01,
+            // length
+            0x04,
+            // privateKey contents
+            0x04, 0x03, 0x02,
+            // 4th (optional) element -- attributes [0] IMPLICIT Attributes
+            // OPTIONAL
+            // (Attributes = SET OF Attribute) Here, it will be empty.
+            0x01,
+            // length
+            0x01 };
+
+    static void raiseException(String expected, String received) {
+        throw new RuntimeException(
+                "Expected " + expected + "; Received " + received);
+    }
+
+    public static void main(String[] args)
+            throws IOException, InvalidKeyException {
+
+        byte[] encodedKey = getEncodedKey();
+        byte[] expectedBytes = new byte[EXPECTED.length];
+        for (int i = 0; i < EXPECTED.length; i++) {
+            expectedBytes[i] = (byte) EXPECTED[i];
+        }
+
+        dumpByteArray("encodedKey :", encodedKey);
+        if (!Arrays.equals(encodedKey, expectedBytes)) {
+            raiseException(new String(expectedBytes), new String(encodedKey));
+        }
+
+        PKCS8Key decodedKey = PKCS8Key.parse(new DerValue(encodedKey));
+        String alg = decodedKey.getAlgorithm();
+        AlgorithmId algId = decodedKey.getAlgorithmId();
+        out.println("Algorithm :" + alg);
+        out.println("AlgorithmId: " + algId);
+
+        if (!ALGORITHM.equals(alg)) {
+            raiseException(ALGORITHM, alg);
+        }
+        if (!EXPECTED_ALG_ID_CHRS.equalsIgnoreCase(algId.toString())) {
+            raiseException(EXPECTED_ALG_ID_CHRS, algId.toString());
+        }
+
+        decodedKey.encode(derOutput);
+        dumpByteArray("Stream encode: ", derOutput.toByteArray());
+        if (!Arrays.equals(derOutput.toByteArray(), expectedBytes)) {
+            raiseException(new String(expectedBytes), derOutput.toString());
+        }
+
+        dumpByteArray("byte[] encoding: ", decodedKey.getEncoded());
+        if (!Arrays.equals(decodedKey.getEncoded(), expectedBytes)) {
+            raiseException(new String(expectedBytes),
+                    new String(decodedKey.getEncoded()));
+        }
+
+        if (!FORMAT.equals(decodedKey.getFormat())) {
+            raiseException(FORMAT, decodedKey.getFormat());
+        }
+
+        try {
+            byte[] newEncodedKey = new byte[NEW_ENCODED_KEY_INTS.length];
+            for (int i = 0; i < newEncodedKey.length; i++) {
+                newEncodedKey[i] = (byte) NEW_ENCODED_KEY_INTS[i];
+            }
+            PKCS8Key newDecodedKey = PKCS8Key
+                    .parse(new DerValue(newEncodedKey));
+
+            throw new RuntimeException(
+                    "key1: Expected an IOException during " + "parsing");
+        } catch (IOException e) {
+            System.out.println("newEncodedKey: should have excess data due to "
+                    + "attributes, which are not supported");
+        }
+
+        try {
+            byte[] newEncodedKey2 = new byte[NEW_ENCODED_KEY_INTS_2.length];
+            for (int i = 0; i < newEncodedKey2.length; i++) {
+                newEncodedKey2[i] = (byte) NEW_ENCODED_KEY_INTS_2[i];
+            }
+
+            PKCS8Key newDecodedKey2 = PKCS8Key
+                    .parse(new DerValue(newEncodedKey2));
+
+            throw new RuntimeException(
+                    "key2: Expected an IOException during " + "parsing");
+        } catch (IOException e) {
+            out.println("Key 2: should be illegal version");
+            out.println(e.getMessage());
+            if (!EXCEPTION_MESSAGE.equals(e.getMessage())) {
+                throw new RuntimeException("Key2: expected: "
+                        + EXCEPTION_MESSAGE + " get: " + e.getMessage());
+            }
+        }
+
+    }
+
+    // get a byte array from somewhere
+    static byte[] getEncodedKey() throws InvalidKeyException {
+        BigInteger p = BigInteger.valueOf(1);
+        BigInteger q = BigInteger.valueOf(2);
+        BigInteger g = BigInteger.valueOf(3);
+        BigInteger x = BigInteger.valueOf(4);
+
+        DSAPrivateKey priv = new DSAPrivateKey(p, q, g, x);
+        return priv.getEncoded();
+    }
+
+    static void dumpByteArray(String nm, byte[] bytes) throws IOException {
+        out.println(nm + " length: " + bytes.length);
+        hexDump.encodeBuffer(bytes, out);
+    }
+
+    static String toString(PKCS8Key key) {
+        StringBuilder builder = new StringBuilder(key.getAlgorithm());
+        builder.append('\n').append("parameters:")
+                .append(key.getAlgorithmId().toString());
+        return builder.toString();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/jarsigner/Options.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8056174
+ * @summary Make sure the jarsigner tool still works after it's modified to
+ *          be based on JarSigner API
+ * @library /lib/testlibrary
+ * @modules java.base/sun.security.tools.keytool
+ *          jdk.jartool/sun.security.tools.jarsigner
+ *          java.base/sun.security.pkcs
+ *          java.base/sun.security.x509
+ */
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import jdk.testlibrary.JarUtils;
+import sun.security.pkcs.PKCS7;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+public class Options {
+
+    public static void main(String[] args) throws Exception {
+
+        // Prepares raw file
+        Files.write(Paths.get("a"), "a".getBytes());
+
+        // Pack
+        JarUtils.createJar("a.jar", "a");
+
+        // Prepare a keystore
+        sun.security.tools.keytool.Main.main(
+                ("-keystore jks -storepass changeit -keypass changeit -dname" +
+                        " CN=A -alias a -genkeypair -keyalg rsa").split(" "));
+
+        // -altsign
+        sun.security.tools.jarsigner.Main.main(
+                ("-debug -signedjar altsign.jar -keystore jks -storepass changeit" +
+                        " -altsigner Options$X a.jar a").split(" "));
+
+        try (JarFile jf = new JarFile("altsign.jar")) {
+            JarEntry je = jf.getJarEntry("META-INF/A.RSA");
+            try (InputStream is = jf.getInputStream(je)) {
+                if (!Arrays.equals(is.readAllBytes(), "1234".getBytes())) {
+                    throw new Exception("altsign go wrong");
+                }
+            }
+        }
+
+        // -sigfile, -digestalg, -sigalg, -internalsf, -sectionsonly
+        sun.security.tools.jarsigner.Main.main(
+                ("-debug -signedjar new.jar -keystore jks -storepass changeit" +
+                " -sigfile olala -digestalg SHA1 -sigalg SHA224withRSA" +
+                " -internalsf -sectionsonly a.jar a").split(" "));
+
+        try (JarFile jf = new JarFile("new.jar")) {
+            JarEntry je = jf.getJarEntry("META-INF/OLALA.SF");
+            Objects.requireNonNull(je);     // check -sigfile
+            byte[] sf = null;               // content of .SF
+            try (InputStream is = jf.getInputStream(je)) {
+                sf = is.readAllBytes();     // save for later comparison
+                Attributes attrs = new Manifest(new ByteArrayInputStream(sf))
+                        .getMainAttributes();
+                // check -digestalg
+                if (!attrs.containsKey(new Attributes.Name(
+                        "SHA1-Digest-Manifest-Main-Attributes"))) {
+                    throw new Exception("digestalg incorrect");
+                }
+                // check -sectionsonly
+                if (attrs.containsKey(new Attributes.Name(
+                        "SHA1-Digest-Manifest"))) {
+                    throw new Exception("SF should not have file digest");
+                }
+            }
+
+            je = jf.getJarEntry("META-INF/OLALA.RSA");
+            try (InputStream is = jf.getInputStream(je)) {
+                PKCS7 p7 = new PKCS7(is.readAllBytes());
+                String alg = p7.getSignerInfos()[0]
+                        .getDigestAlgorithmId().getName();
+                if (!alg.equals("SHA-224")) {   // check -sigalg
+                    throw new Exception("PKCS7 signing is using " + alg);
+                }
+                // check -internalsf
+                if (!Arrays.equals(sf, p7.getContentInfo().getData())) {
+                    throw new Exception("SF not in RSA");
+                }
+            }
+
+        }
+
+        // TSA-related ones are checked in ts.sh
+    }
+
+    public static class X extends ContentSigner {
+        @Override
+        public byte[] generateSignedData(ContentSignerParameters parameters,
+                boolean omitContent, boolean applyTimestamp)
+                throws NoSuchAlgorithmException, CertificateException,
+                        IOException {
+            return "1234".getBytes();
+        }
+    }
+}
--- a/jdk/test/sun/tools/jmap/BasicJMapTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/tools/jmap/BasicJMapTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -42,9 +42,9 @@
  * @modules java.management
  * @build jdk.testlibrary.*
  * @build jdk.test.lib.hprof.*
- * @build jdk.test.lib.hprof.module.*
+ * @build jdk.test.lib.hprof.model.*
  * @build jdk.test.lib.hprof.parser.*
- * @build jdk.test.lib.hprof.utils.*
+ * @build jdk.test.lib.hprof.util.*
  * @run main/timeout=240 BasicJMapTest
  */
 public class BasicJMapTest {
--- a/jdk/test/sun/util/logging/PlatformLoggerTest.java	Mon Nov 23 09:58:44 2015 -0800
+++ b/jdk/test/sun/util/logging/PlatformLoggerTest.java	Mon Nov 23 10:00:50 2015 -0800
@@ -38,7 +38,6 @@
 import java.lang.reflect.Modifier;
 import java.util.logging.*;
 import sun.util.logging.PlatformLogger;
-import sun.util.logging.LoggingSupport;
 import static sun.util.logging.PlatformLogger.Level.*;
 
 public class PlatformLoggerTest {
@@ -195,7 +194,9 @@
         System.out.println("Testing Java Level with: " + level.getName());
 
         // create a brand new java logger
-        Logger javaLogger = (Logger) LoggingSupport.getLogger(logger.getName()+"."+level.getName());
+        Logger javaLogger = sun.util.logging.internal.LoggingProviderImpl.getLogManagerAccess()
+                     .demandLoggerFor(LogManager.getLogManager(),
+                          logger.getName()+"."+level.getName(), Thread.class);
 
         // Set a non standard java.util.logging.Level on the java logger
         // (except for OFF & ALL - which will remain unchanged)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/MultiRelease.java	Mon Nov 23 10:00:50 2015 -0800
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ *
+ * 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).
+ *
+ r You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * @test
+ * @bug 8066272
+ * @summary tests a simple multi-versioned jar file
+ * @compile -XDignore.symbol.file Utils.java MultiRelease.java
+ * @run main MultiRelease
+ * @author ksrini
+ */
+
+public class MultiRelease {
+    private static final File cwd = new File(".");
+    private static int pass = 0;
+    private static int fail = 0;
+    // specify alternate name via arguments to verify
+    // if permanent fix works
+
+    private static final String PropKey = "pack200.MultiRelease.META-INF";
+    private static final String MetaInfName = System.getProperty(PropKey, "META-INF");
+
+    public static void main(String... args) throws Exception {
+        new MultiRelease().run();
+    }
+
+    void run() throws Exception {
+        List<TestCase> testCases = new ArrayList<>();
+        testCases.add(new TestCase1());
+        testCases.add(new TestCase2());
+        for (TestCase tc : testCases) {
+            tc.run();
+        }
+        if (fail > 0) {
+            throw new Exception(fail + "/" + testCases.size() + " tests fails");
+        } else {
+            System.out.println("All tests(" + pass + ") passes");
+        }
+    }
+
+    /*
+     * An abstract class to eliminate test boiler plating.
+     */
+    static abstract class TestCase {
+        final File tcwd;
+        final File metaInfDir;
+        final File versionsDir;
+        final File manifestFile;
+
+        TestCase(String directory) throws IOException {
+            System.out.println("initializing directories");
+            tcwd = new File(cwd, directory);
+            metaInfDir = mkdir(new File(tcwd, MetaInfName));
+            versionsDir = mkdir(new File(metaInfDir, "versions"));
+            manifestFile = new File(tcwd, "manifest.tmp");
+            List<String> scratch = new ArrayList<>();
+            scratch.add("Multi-Release: true");
+            Utils.createFile(manifestFile, scratch);
+        }
+
+        File mkdir(File f) throws IOException {
+            if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite()) {
+                return f;
+            }
+            if (!f.mkdirs()) {
+               throw new IOException("mkdirs failed: " + f.getAbsolutePath());
+            }
+            return f;
+        }
+
+        abstract void emitClassFiles() throws Exception;
+
+        void run() {
+            try {
+                emitClassFiles();
+                // jar the file up
+                File testFile = new File(tcwd, "test" + Utils.JAR_FILE_EXT);
+                Utils.jar("cvfm",
+                        testFile.getAbsolutePath(),
+                        manifestFile.getAbsolutePath(),
+                        "-C",
+                        tcwd.getAbsolutePath(),
+                        ".");
+                File outFile = new File(tcwd, "test-repacked" + Utils.JAR_FILE_EXT);
+                List<String> cmdsList = new ArrayList<>();
+
+                cmdsList.add(Utils.getPack200Cmd());
+                cmdsList.add("-J-ea");
+                cmdsList.add("-J-esa");
+                cmdsList.add("-v");
+                cmdsList.add("--repack");
+                cmdsList.add(outFile.getAbsolutePath());
+                cmdsList.add(testFile.getAbsolutePath());
+                List<String> output = Utils.runExec(cmdsList);
+                Utils.doCompareVerify(testFile.getAbsoluteFile(), outFile.getAbsoluteFile());
+                pass++;
+            } catch (Exception e) {
+                e.printStackTrace(System.err);
+                fail++;
+            }
+        }
+    }
+
+    static class TestCase1 extends TestCase {
+        private TestCase1(String directory) throws IOException {
+            super(directory);
+        }
+
+        public TestCase1() throws Exception {
+            this("case1");
+        }
+
+        @Override
+        void emitClassFiles() throws Exception {
+            emitClassFile("");
+            emitClassFile("7");
+            emitClassFile("8");
+            emitClassFile("9");
+        }
+
+        /*
+         * Adds different variants of types
+         */
+        void emitClassFile(String version) throws IOException {
+            final File outDir = mkdir(version.isEmpty()
+                    ? tcwd
+                    : new File(versionsDir, version));
+
+            final File srcDir = mkdir(version.isEmpty()
+                            ? new File(tcwd, "src")
+                            : new File(new File(versionsDir, version), "src"));
+
+            final String fname = "Foo";
+            final File srcFile = new File(srcDir, fname + Utils.JAVA_FILE_EXT);
+            List<String> scratch = new ArrayList<>();
+
+            scratch.add("package pkg;");
+            switch (version) {
+                case "7":
+                    scratch.add("public class Foo {");
+                    scratch.add("public static final class Bar {}");
+                    break;
+                case "8":
+                    scratch.add("public abstract class Foo {");
+                    scratch.add("public final class Bar {}");
+                    break;
+                case "9":
+                    scratch.add("public interface Foo {");
+                    scratch.add("public final class Bar {}");
+                    break;
+                default:
+                    scratch.add("public class Foo {");
+                    scratch.add("public final class Bar {}");
+                    break;
+            }
+            scratch.add("}");
+
+            Utils.createFile(srcFile, scratch);
+            Utils.compiler("-d",
+                    outDir.getAbsolutePath(),
+                    srcFile.getAbsolutePath());
+        }
+    }
+
+    static class TestCase2 extends TestCase {
+        private TestCase2(String directory) throws IOException {
+            super(directory);
+        }
+
+        TestCase2() throws Exception {
+            this("case2");
+        }
+
+        @Override
+        void emitClassFiles() throws Exception {
+            emitClassFile("");
+            emitClassFile("8");
+        }
+
+        /*
+         * Adds different variants of types and tries to invoke an
+         * interface or concrete method defined by them.
+         */
+        void emitClassFile(String version) throws IOException {
+
+            final File outDir = mkdir(version.isEmpty()
+                    ? tcwd
+                    : new File(versionsDir, version));
+
+            final File srcDir = mkdir(version.isEmpty()
+                    ? new File(tcwd, "src")
+                    : new File(new File(versionsDir, version), "src"));
+
+            List<String> scratch = new ArrayList<>();
+            final String fname1 = "Ab";
+            final File srcFile1 = new File(srcDir, fname1 + Utils.JAVA_FILE_EXT);
+
+            final String fname2 = "AbNormal";
+            final File srcFile2 = new File(srcDir, fname2 + Utils.JAVA_FILE_EXT);
+            switch (version) {
+                case "8":
+                    scratch.clear();
+                    scratch.add("import java.io.IOException;");
+                    scratch.add("public interface " + fname1 + "{");
+                    scratch.add("    public abstract void close() throws IOException ;");
+                    scratch.add("}");
+                    Utils.createFile(srcFile1, scratch);
+                    break;
+                default:
+                    scratch.clear();
+                    scratch.add("import java.io.IOException;");
+                    scratch.add("public abstract class " + fname1 + "{");
+                    scratch.add("    public abstract void close() throws IOException ;");
+                    scratch.add("}");
+                    Utils.createFile(srcFile1, scratch);
+            }
+
+            scratch.clear();
+            scratch.add("import java.io.IOException;");
+            scratch.add("public class " + fname2 + "{");
+            scratch.add("    public void doSomething(Ab ab) throws IOException {");
+            scratch.add("       ab.close();");
+            scratch.add("    }");
+            scratch.add("}");
+
+            Utils.createFile(srcFile2, scratch);
+            Utils.compiler("-d",
+                    outDir.getAbsolutePath(),
+                    srcFile1.getAbsolutePath(),
+                    srcFile2.getAbsolutePath());
+        }
+    }
+}