8017174: NPE when using Logger.getAnonymousLogger or LogManager.getLogManager().getLogger
authordfuchs
Tue, 02 Jul 2013 11:30:31 +0200
changeset 18593 40715ebb6681
parent 18592 80cdfecea074
child 18594 b6a3c9f71ac8
8017174: NPE when using Logger.getAnonymousLogger or LogManager.getLogManager().getLogger Summary: This patch makes sure that LoggerContext instances created for applets have a root and global logger. Reviewed-by: mchung
jdk/src/share/classes/java/util/logging/LogManager.java
jdk/test/java/util/logging/LogManagerInstanceTest.java
jdk/test/java/util/logging/TestAppletLoggerContext.java
--- a/jdk/src/share/classes/java/util/logging/LogManager.java	Mon Jul 01 17:46:12 2013 -0700
+++ b/jdk/src/share/classes/java/util/logging/LogManager.java	Tue Jul 02 11:30:31 2013 +0200
@@ -193,13 +193,22 @@
 
                     // Create and retain Logger for the root of the namespace.
                     manager.rootLogger = manager.new RootLogger();
+                    // since by design the global manager's userContext and
+                    // systemContext don't have their requiresDefaultLoggers
+                    // flag set - we make sure to add the root logger to
+                    // the global manager's default contexts here.
                     manager.addLogger(manager.rootLogger);
-                    manager.systemContext.addLocalLogger(manager.rootLogger);
+                    manager.systemContext.addLocalLogger(manager.rootLogger, false);
+                    manager.userContext.addLocalLogger(manager.rootLogger, false);
 
                     // Adding the global Logger. Doing so in the Logger.<clinit>
                     // would deadlock with the LogManager.<clinit>.
                     Logger.global.setLogManager(manager);
+                    // Make sure the global logger will be registered in the
+                    // global manager's default contexts.
                     manager.addLogger(Logger.global);
+                    manager.systemContext.addLocalLogger(Logger.global, false);
+                    manager.userContext.addLocalLogger(Logger.global, false);
 
                     // We don't call readConfiguration() here, as we may be running
                     // very early in the JVM startup sequence.  Instead readConfiguration
@@ -401,7 +410,11 @@
                         if (javaAwtAccess.isMainAppContext()) {
                             context = userContext;
                         } else {
-                            context = new LoggerContext();
+                            // Create a new LoggerContext for the applet.
+                            // The new logger context has its requiresDefaultLoggers
+                            // flag set to true - so that these loggers will be
+                            // lazily added when the context is firt accessed.
+                            context = new LoggerContext(true);
                         }
                         javaAwtAccess.put(ecx, LoggerContext.class, context);
                     }
@@ -508,9 +521,13 @@
         private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
         // Tree of named Loggers
         private final LogNode root;
-
+        private final boolean requiresDefaultLoggers;
         private LoggerContext() {
+            this(false);
+        }
+        private LoggerContext(boolean requiresDefaultLoggers) {
             this.root = new LogNode(null, this);
+            this.requiresDefaultLoggers = requiresDefaultLoggers;
         }
 
         Logger demandLogger(String name, String resourceBundleName) {
@@ -519,7 +536,27 @@
             return manager.demandLogger(name, resourceBundleName, null);
         }
 
+
+        // Due to subtle deadlock issues getUserContext() no longer
+        // calls addLocalLogger(rootLogger);
+        // Therefore - we need to add the default loggers later on.
+        // Checks that the context is properly initialized
+        // This is necessary before calling e.g. find(name)
+        // or getLoggerNames()
+        //
+        private void ensureInitialized() {
+            if (requiresDefaultLoggers) {
+                // Ensure that the root and global loggers are set.
+                ensureDefaultLogger(manager.rootLogger);
+                ensureDefaultLogger(Logger.global);
+            }
+        }
+
+
         synchronized Logger findLogger(String name) {
+            // ensure that this context is properly initialized before
+            // looking for loggers.
+            ensureInitialized();
             LoggerWeakRef ref = namedLoggers.get(name);
             if (ref == null) {
                 return null;
@@ -533,21 +570,76 @@
             return logger;
         }
 
-        synchronized void ensureRootLogger(Logger logger) {
-            if (logger.getName().isEmpty())
-                return;
+        // This method is called before adding a logger to the
+        // context.
+        // 'logger' is the context that will be added.
+        // This method will ensure that the defaults loggers are added
+        // before adding 'logger'.
+        //
+        private void ensureAllDefaultLoggers(Logger logger) {
+            if (requiresDefaultLoggers) {
+                final String name = logger.getName();
+                if (!name.isEmpty()) {
+                    ensureDefaultLogger(manager.rootLogger);
+                }
+                if (!Logger.GLOBAL_LOGGER_NAME.equals(name)) {
+                    ensureDefaultLogger(Logger.global);
+                }
+            }
+        }
+
+        private void ensureDefaultLogger(Logger logger) {
+            // Used for lazy addition of root logger and global logger
+            // to a LoggerContext.
 
-            // during initialization, rootLogger is null when
-            // instantiating itself RootLogger
-            if (findLogger("") == null && manager.rootLogger != null) {
-                addLocalLogger(manager.rootLogger);
+            // This check is simple sanity: we do not want that this
+            // method be called for anything else than Logger.global
+            // or owner.rootLogger.
+            if (!requiresDefaultLoggers || logger == null
+                    || logger != Logger.global && logger != manager.rootLogger) {
+
+                // the case where we have a non null logger which is neither
+                // Logger.global nor manager.rootLogger indicates a serious
+                // issue - as ensureDefaultLogger should never be called
+                // with any other loggers than one of these two (or null - if
+                // e.g manager.rootLogger is not yet initialized)...
+                assert logger == null;
+
+                return;
             }
+
+            // Adds the logger if it's not already there.
+            if (!namedLoggers.containsKey(logger.getName())) {
+                // It is important to prevent addLocalLogger to
+                // call ensureAllDefaultLoggers when we're in the process
+                // off adding one of those default loggers - as this would
+                // immediately cause a stack overflow.
+                // Therefore we must pass addDefaultLoggersIfNeeded=false,
+                // even if requiresDefaultLoggers is true.
+                addLocalLogger(logger, false);
+            }
+        }
+
+        boolean addLocalLogger(Logger logger) {
+            // no need to add default loggers if it's not required
+            return addLocalLogger(logger, requiresDefaultLoggers);
         }
 
         // Add a logger to this context.  This method will only set its level
         // and process parent loggers.  It doesn't set its handlers.
-        synchronized boolean addLocalLogger(Logger logger) {
-            ensureRootLogger(logger);
+        synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) {
+            // addDefaultLoggersIfNeeded serves to break recursion when adding
+            // default loggers. If we're adding one of the default loggers
+            // (we're being called from ensureDefaultLogger()) then
+            // addDefaultLoggersIfNeeded will be false: we don't want to
+            // call ensureAllDefaultLoggers again.
+            //
+            // Note: addDefaultLoggersIfNeeded can also be false when
+            //       requiresDefaultLoggers is false - since calling
+            //       ensureAllDefaultLoggers would have no effect in this case.
+            if (addDefaultLoggersIfNeeded) {
+                ensureAllDefaultLoggers(logger);
+            }
 
             final String name = logger.getName();
             if (name == null) {
@@ -615,6 +707,9 @@
         }
 
         synchronized Enumeration<String> getLoggerNames() {
+            // ensure that this context is properly initialized before
+            // returning logger names.
+            ensureInitialized();
             return namedLoggers.keys();
         }
 
--- a/jdk/test/java/util/logging/LogManagerInstanceTest.java	Mon Jul 01 17:46:12 2013 -0700
+++ b/jdk/test/java/util/logging/LogManagerInstanceTest.java	Tue Jul 02 11:30:31 2013 +0200
@@ -63,7 +63,7 @@
                 if (!super.addLogger(root))
                     throw new RuntimeException("Fail to addLogger " + root);
             } else {
-                System.out.println("Root logger already exists");
+                throw new RuntimeException("Root logger already exists");
             }
             this.base = root;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/TestAppletLoggerContext.java	Tue Jul 02 11:30:31 2013 +0200
@@ -0,0 +1,610 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import java.util.logging.LoggingPermission;
+import sun.misc.JavaAWTAccess;
+import sun.misc.SharedSecrets;
+
+/*
+ * @test
+ * @bug 8017174 8010727
+ * @summary  NPE when using Logger.getAnonymousLogger or
+ *           LogManager.getLogManager().getLogger
+ *
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext LoadingApplet
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  LoadingApplet
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext LoadingMain
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  LoadingMain
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext One
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  One
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext Two
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  Two
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext Three
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  Three
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext Four
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  Four
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext Five
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  Five
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext Six
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  Six
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext Seven
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext  Seven
+ * @run main/othervm -Dtest.security=off TestAppletLoggerContext
+ * @run main/othervm -Dtest.security=on TestAppletLoggerContext
+ */
+
+// NOTE: We run in other VM in order to 1. switch security manager and 2. cause
+// LogManager class to be loaded anew.
+public class TestAppletLoggerContext {
+
+    // Avoids the hassle of dealing with files and system props...
+    static class SimplePolicy extends Policy {
+        private final Permissions perms;
+        public SimplePolicy(Permission... permissions) {
+            perms = new Permissions();
+            for (Permission permission : permissions) {
+                perms.add(permission);
+            }
+        }
+        @Override
+        public PermissionCollection getPermissions(CodeSource cs) {
+            return perms;
+        }
+        @Override
+        public PermissionCollection getPermissions(ProtectionDomain pd) {
+            return perms;
+        }
+        @Override
+        public boolean implies(ProtectionDomain pd, Permission p) {
+           return perms.implies(p);
+        }
+    }
+
+    // The bridge class initializes the logging system.
+    // It stubs the applet context in order to simulate context changes.
+    //
+    public static class Bridge {
+
+        private static class JavaAWTAccessStub implements JavaAWTAccess {
+            boolean active = true;
+
+            private static class TestExc {
+                private final Map<Object, Object> map = new HashMap<>();
+                void put(Object key, Object v) { map.put(key, v); }
+                Object get(Object key) { return map.get(key); }
+                void remove(Object o) { map.remove(o); }
+                public static TestExc exc(Object o) {
+                    return TestExc.class.cast(o);
+                }
+            }
+
+            TestExc exc;
+            TestExc global = new TestExc();
+
+            @Override
+            public Object getContext() { return active ? global : null; }
+            @Override
+            public Object getExecutionContext() { return active ? exc : null; }
+            @Override
+            public Object get(Object o, Object o1) { return TestExc.exc(o).get(o1); }
+            @Override
+            public void put(Object o, Object o1, Object o2) { TestExc.exc(o).put(o1, o2); }
+            @Override
+            public void remove(Object o, Object o1) { TestExc.exc(o).remove(o1); }
+            @Override
+            public Object get(Object o) { return global.get(o); }
+            @Override
+            public void put(Object o, Object o1) { global.put(o, o1); }
+            @Override
+            public void remove(Object o) { global.remove(o); }
+            @Override
+            public boolean isDisposed() { return false; }
+            @Override
+            public boolean isMainAppContext() { return exc == null; }
+        }
+
+        final static JavaAWTAccessStub javaAwtAccess = new JavaAWTAccessStub();
+        public static void init() {
+            SharedSecrets.setJavaAWTAccess(javaAwtAccess);
+            if (System.getProperty("test.security", "on").equals("on")) {
+                Policy p = new SimplePolicy(new LoggingPermission("control", null),
+                    new RuntimePermission("setContextClassLoader"),
+                    new RuntimePermission("shutdownHooks"));
+                Policy.setPolicy(p);
+                System.setSecurityManager(new SecurityManager());
+            }
+        }
+
+        public static void changeContext() {
+            System.out.println("... Switching to a new applet context ...");
+            javaAwtAccess.active = true;
+            javaAwtAccess.exc = new JavaAWTAccessStub.TestExc();
+        }
+
+        public static void desactivate() {
+            System.out.println("... Running with no applet context ...");
+            javaAwtAccess.exc = null;
+            javaAwtAccess.active = false;
+        }
+
+        public static class CustomAnonymousLogger extends Logger {
+            public CustomAnonymousLogger() {
+                this("");
+            }
+            public CustomAnonymousLogger(String name) {
+                super(null, null);
+                System.out.println( " LogManager: " +LogManager.getLogManager());
+                System.out.println( " getLogger: " +LogManager.getLogManager().getLogger(name));
+                setParent(LogManager.getLogManager().getLogger(name));
+            }
+        }
+
+        public static class CustomLogger extends Logger {
+            CustomLogger(String name) {
+                super(name, null);
+            }
+        }
+    }
+
+    public static enum TestCase {
+        LoadingApplet, LoadingMain, One, Two, Three, Four, Five, Six, Seven;
+        public void test() {
+            switch(this) {
+                // When run - each of these two tests must be
+                // run before any other tests and before each other.
+                case LoadingApplet: testLoadingApplet(); break;
+                case LoadingMain:   testLoadingMain(); break;
+                case One:   testOne(); break;
+                case Two:   testTwo(); break;
+                case Three: testThree(); break;
+                case Four:  testFour(); break;
+                case Five:  testFive(); break;
+                case Six:   testSix(); break;
+                case Seven: testSeven(); break;
+            }
+        }
+        public String describe() {
+            switch(this) {
+                case LoadingApplet:
+                    return "Test that when the LogManager class is"
+                        + " loaded in  an applet thread first,"
+                        + "\n all LoggerContexts are correctly initialized";
+                case LoadingMain:
+                    return "Test that when the LogManager class is"
+                        + " loaded in  the main thread first,"
+                        + "\n all LoggerContexts are correctly initialized";
+                case One:
+                    return "Test that Logger.getAnonymousLogger()"
+                        + " and new CustomAnonymousLogger() don't throw NPE";
+                case Two:
+                    return "Test that Logger.getLogger(\"\")"
+                            + " does not return null nor throws NPE";
+                case Three:
+                    return "Test that LogManager.getLogManager().getLogger(\"\")"
+                            + " does not return null nor throws NPE";
+                case Four:
+                    return "Test that Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)"
+                            + " does not return null,\n and that"
+                            + " new CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME)"
+                            + " does not throw NPE";
+                case Five:
+                    return "Test that LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME)"
+                            + "\n does not return null nor throws NPE";
+                case Six:
+                    return "Test that manager.getLogger(Logger.GLOBAL_LOGGER_NAME)"
+                            + " returns null\n when manager is not the default"
+                            + " LogManager instance.\n"
+                            + "Test adding a new logger named \"global\" in that"
+                            + " non default instance.";
+                case Seven: return "Test that manager.getLogger(\"\")"
+                            + " returns null\n when manager is not the default"
+                            + " LogManager instance.\n"
+                            + "Test adding a new logger named \"\" in that"
+                            + " non default instance.";
+                default: return "Undefined";
+            }
+        }
+    };
+
+    /**
+     * @param args the command line arguments
+     */
+    public static void main(String[] args) {
+        Bridge.init();
+        EnumSet<TestCase> tests = EnumSet.noneOf(TestCase.class);
+        for (String arg : args) {
+            tests.add(TestCase.valueOf(arg));
+        }
+        if (args.length == 0) {
+            tests = EnumSet.complementOf(EnumSet.of(TestCase.LoadingMain));
+        }
+        final EnumSet<TestCase> loadingTests =
+            EnumSet.of(TestCase.LoadingApplet, TestCase.LoadingMain);
+        int testrun = 0;
+        for (TestCase test : tests) {
+            if (loadingTests.contains(test)) {
+                if (testrun > 0) {
+                    throw new UnsupportedOperationException("Test case "
+                          + test + " must be executed first!");
+                }
+            }
+            System.out.println("Testing "+ test+": ");
+            System.out.println(test.describe());
+            try {
+                test.test();
+            } catch (Exception x) {
+               throw new Error(String.valueOf(test)
+                   + (System.getSecurityManager() == null ? " without " : " with ")
+                   + "security failed: "+x+"\n "+"FAILED: "+test.describe()+"\n", x);
+            } finally {
+                testrun++;
+            }
+            Bridge.changeContext();
+            System.out.println("PASSED: "+ test);
+        }
+    }
+
+    public static void testLoadingApplet() {
+        Bridge.changeContext();
+
+        Logger bar = new Bridge.CustomLogger("com.foo.Bar");
+        LogManager.getLogManager().addLogger(bar);
+        assertNotNull(bar.getParent());
+        testParent(bar);
+        testParent(LogManager.getLogManager().getLogger("global"));
+        testParent(LogManager.getLogManager().getLogger(bar.getName()));
+
+        Bridge.desactivate();
+
+        Logger foo = new Bridge.CustomLogger("com.foo.Foo");
+        boolean b = LogManager.getLogManager().addLogger(foo);
+        assertEquals(Boolean.TRUE, Boolean.valueOf(b));
+        assertNotNull(foo.getParent());
+        testParent(foo);
+        testParent(LogManager.getLogManager().getLogger("global"));
+        testParent(LogManager.getLogManager().getLogger(foo.getName()));
+    }
+
+    public static void testLoadingMain() {
+        Bridge.desactivate();
+
+        Logger bar = new Bridge.CustomLogger("com.foo.Bar");
+        LogManager.getLogManager().addLogger(bar);
+        assertNotNull(bar.getParent());
+        testParent(bar);
+        testParent(LogManager.getLogManager().getLogger("global"));
+        testParent(LogManager.getLogManager().getLogger(bar.getName()));
+
+        Bridge.changeContext();
+
+        Logger foo = new Bridge.CustomLogger("com.foo.Foo");
+        boolean b = LogManager.getLogManager().addLogger(foo);
+        assertEquals(Boolean.TRUE, Boolean.valueOf(b));
+        assertNotNull(foo.getParent());
+        testParent(foo);
+        testParent(LogManager.getLogManager().getLogger("global"));
+        testParent(LogManager.getLogManager().getLogger(foo.getName()));
+
+    }
+
+    public static void testOne() {
+        for (int i=0; i<3 ; i++) {
+            Logger logger1 = Logger.getAnonymousLogger();
+            Logger logger1b = Logger.getAnonymousLogger();
+            Bridge.changeContext();
+            Logger logger2 = Logger.getAnonymousLogger();
+            Logger logger2b = Logger.getAnonymousLogger();
+            Bridge.changeContext();
+            Logger logger3 = new Bridge.CustomAnonymousLogger();
+            Logger logger3b = new Bridge.CustomAnonymousLogger();
+            Bridge.changeContext();
+            Logger logger4 = new Bridge.CustomAnonymousLogger();
+            Logger logger4b = new Bridge.CustomAnonymousLogger();
+        }
+    }
+
+
+    public static void testTwo() {
+        for (int i=0; i<3 ; i++) {
+            Logger logger1 = Logger.getLogger("");
+            Logger logger1b = Logger.getLogger("");
+            assertNotNull(logger1);
+            assertNotNull(logger1b);
+            assertEquals(logger1, logger1b);
+            Bridge.changeContext();
+            Logger logger2 = Logger.getLogger("");
+            Logger logger2b = Logger.getLogger("");
+            assertNotNull(logger2);
+            assertNotNull(logger2b);
+            assertEquals(logger2, logger2b);
+            assertEquals(logger1, logger2);
+        }
+    }
+
+    public static void testThree() {
+        for (int i=0; i<3 ; i++) {
+            Logger logger1 = LogManager.getLogManager().getLogger("");
+            Logger logger1b = LogManager.getLogManager().getLogger("");
+            assertNotNull(logger1);
+            assertNotNull(logger1b);
+            assertEquals(logger1, logger1b);
+            Bridge.changeContext();
+            Logger logger2 = LogManager.getLogManager().getLogger("");
+            Logger logger2b = LogManager.getLogManager().getLogger("");
+            assertNotNull(logger2);
+            assertNotNull(logger2b);
+            assertEquals(logger2, logger2b);
+            assertEquals(logger1, logger2);
+        }
+    }
+
+    public static void testFour() {
+        for (int i=0; i<3 ; i++) {
+            Logger logger1 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger1b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger1);
+            assertNotNull(logger1b);
+            assertEquals(logger1, logger1b);
+            Bridge.changeContext();
+
+            Logger logger2 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger2b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger2);
+            assertNotNull(logger2b);
+            assertEquals(logger2, logger2b);
+
+            assertEquals(logger1, logger2);
+
+            Bridge.changeContext();
+            Logger logger3 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger3b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME);
+            Bridge.changeContext();
+            Logger logger4 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger4b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME);
+        }
+    }
+
+    public static void testFive() {
+        for (int i=0; i<3 ; i++) {
+            Logger logger1 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger1b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger1);
+            assertNotNull(logger1b);
+            assertEquals(logger1, logger1b);
+
+            Bridge.changeContext();
+
+            Logger logger2 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger2b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger2);
+            assertNotNull(logger2b);
+            assertEquals(logger2, logger2b);
+
+            assertEquals(logger1, logger2);
+        }
+    }
+
+    /**
+     * This test is designed to test the behavior of additional LogManager instances.
+     * It must be noted that if the security manager is off, then calling
+     * Bridge.changeContext() has actually no effect - which explains why we have
+     * some differences between the cases security manager on & security manager
+     * off.
+     **/
+    public static void testSix() {
+        for (int i=0; i<3 ; i++) {
+            Bridge.desactivate();
+            LogManager manager = new LogManager() {};
+            Logger logger1 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger1b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNull(logger1);
+            assertNull(logger1b);
+            Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME);
+            manager.addLogger(global);
+            Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger2);
+            assertNotNull(logger2b);
+            assertEquals(logger2, global);
+            assertEquals(logger2b, global);
+            assertNull(manager.getLogger(""));
+            assertNull(manager.getLogger(""));
+
+            Bridge.changeContext();
+
+            // this is not a supported configuration:
+            // We are in an applet context with several log managers.
+            // We however need to check our assumptions...
+
+            // Applet context => root logger and global logger are not null.
+            //   root == LogManager.getLogManager().rootLogger
+            //   global == Logger.global
+
+            Logger logger3 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger3b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger3);
+            assertNotNull(logger3b);
+            Logger expected = (System.getSecurityManager() != null
+                  ? Logger.getGlobal()
+                  : global);
+            assertEquals(logger3, expected); // in applet context, we will not see
+                  // the LogManager's custom global logger added above...
+            assertEquals(logger3b, expected); // in applet context, we will not see
+                  // the LogManager's custom global logger added above...
+            Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME);
+            manager.addLogger(global2); // adding a global logger will not work in applet context
+               // we will always get back the global logger.
+               // this could be considered as a bug...
+            Logger logger4 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger4b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger4);
+            assertNotNull(logger4b);
+            assertEquals(logger4,  expected); // adding a global logger will not work in applet context
+            assertEquals(logger4b, expected); // adding a global logger will not work in applet context
+
+            Logger logger5 = manager.getLogger("");
+            Logger logger5b = manager.getLogger("");
+            Logger expectedRoot = (System.getSecurityManager() != null
+                  ? LogManager.getLogManager().getLogger("")
+                  : null);
+            assertEquals(logger5, expectedRoot);
+            assertEquals(logger5b, expectedRoot);
+
+        }
+    }
+
+    /**
+     * This test is designed to test the behavior of additional LogManager instances.
+     * It must be noted that if the security manager is off, then calling
+     * Bridge.changeContext() has actually no effect - which explains why we have
+     * some differences between the cases security manager on & security manager
+     * off.
+     **/
+    public static void testSeven() {
+        for (int i=0; i<3 ; i++) {
+            Bridge.desactivate();
+            LogManager manager = new LogManager() {};
+            Logger logger1 = manager.getLogger("");
+            Logger logger1b = manager.getLogger("");
+            assertNull(logger1);
+            assertNull(logger1b);
+            Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME);
+            manager.addLogger(global);
+            Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            assertNotNull(logger2);
+            assertNotNull(logger2b);
+            assertEquals(logger2, global);
+            assertEquals(logger2b, global);
+            Logger logger3 = manager.getLogger("");
+            Logger logger3b = manager.getLogger("");
+            assertNull(logger3);
+            assertNull(logger3b);
+            Logger root = new Bridge.CustomLogger("");
+            manager.addLogger(root);
+            Logger logger4 = manager.getLogger("");
+            Logger logger4b = manager.getLogger("");
+            assertNotNull(logger4);
+            assertNotNull(logger4b);
+            assertEquals(logger4, root);
+            assertEquals(logger4b, root);
+
+            Bridge.changeContext();
+
+            // this is not a supported configuration:
+            // We are in an applet context with several log managers.
+            // We haowever need to check our assumptions...
+
+            // Applet context => root logger and global logger are not null.
+            //   root == LogManager.getLogManager().rootLogger
+            //   global == Logger.global
+
+            Logger logger5 = manager.getLogger("");
+            Logger logger5b = manager.getLogger("");
+            Logger expectedRoot = (System.getSecurityManager() != null
+                  ? LogManager.getLogManager().getLogger("")
+                  : root);
+
+            assertNotNull(logger5);
+            assertNotNull(logger5b);
+            assertEquals(logger5, expectedRoot);
+            assertEquals(logger5b, expectedRoot);
+            if (System.getSecurityManager() != null) {
+                assertNotEquals(logger5, root);
+                assertNotEquals(logger5b, root);
+            }
+
+            Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME);
+            manager.addLogger(global2); // adding a global logger will not work in applet context
+               // we will always get back the global logger.
+               // this could be considered as a bug...
+            Logger logger6 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger logger6b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME);
+            Logger expectedGlobal = (System.getSecurityManager() != null
+                  ? Logger.getGlobal()
+                  : global);
+            assertNotNull(logger6);
+            assertNotNull(logger6b);
+            assertEquals(logger6, expectedGlobal); // adding a global logger will not work in applet context
+            assertEquals(logger6b, expectedGlobal); // adding a global logger will not work in applet context
+
+            Logger root2 = new Bridge.CustomLogger("");
+            manager.addLogger(root2); // adding a root logger will not work in applet context
+               // we will always get back the default manager's root logger.
+               // this could be considered as a bug...
+            Logger logger7 = manager.getLogger("");
+            Logger logger7b = manager.getLogger("");
+            assertNotNull(logger7);
+            assertNotNull(logger7b);
+            assertEquals(logger7, expectedRoot); // adding a global logger will not work in applet context
+            assertEquals(logger7b, expectedRoot); // adding a global logger will not work in applet context
+            assertNotEquals(logger7, root2);
+            assertNotEquals(logger7b, root2);
+        }
+    }
+
+    public static void testParent(Logger logger) {
+        Logger l = logger;
+        while (l.getParent() != null) {
+            l = l.getParent();
+        }
+        assertEquals("", l.getName());
+    }
+
+    public static class TestError extends RuntimeException {
+        public TestError(String msg) {
+            super(msg);
+        }
+    }
+
+    public static void assertNotNull(Object obj) {
+        if (obj == null) throw new NullPointerException();
+    }
+
+    public static void assertNull(Object obj) {
+        if (obj != null) throw new TestError("Null expected, got "+obj);
+    }
+
+    public static void assertEquals(Object o1, Object o2) {
+        if (o1 != o2) {
+            throw new TestError(o1 + " != " + o2);
+        }
+    }
+
+    public static void assertNotEquals(Object o1, Object o2) {
+        if (o1 == o2) {
+            throw new TestError(o1 + " == " + o2);
+        }
+    }
+}