/*
 * Copyright (c) 2015, 2024, 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * @test
 * @bug 8033661
 * @summary tests LogManager.updateConfiguration(Function) method
 * @run main/othervm SimpleUpdateConfigurationTest
 * @author danielfuchs
 */
public class SimpleUpdateConfigurationTest {

    // We will test updateConfiguration
    public static void execute(Runnable run) {
        try {
           Configure.doPrivileged(run);
        } finally {
           Configure.doPrivileged(() -> {
               try {
                   setSystemProperty("java.util.logging.config.file", null);
                   LogManager.getLogManager().readConfiguration();
                   System.gc();
               } catch (Exception x) {
                   throw new RuntimeException(x);
               }
           });
        }
    }

    public static class MyHandler extends Handler {
        static final AtomicLong seq = new AtomicLong();
        long count = seq.incrementAndGet();

        @Override
        public void publish(LogRecord record) {
        }

        @Override
        public void flush() {
        }

        @Override
        public void close() throws SecurityException {
        }

        @Override
        public String toString() {
            return super.toString() + "("+count+")";
        }

    }

    static String storePropertyToFile(String name, Properties props)
        throws Exception {
        return Configure.callPrivileged(() -> {
            String scratch = System.getProperty("user.dir", ".");
            Path p = Paths.get(scratch, name);
            try (FileOutputStream fos = new FileOutputStream(p.toFile())) {
                props.store(fos, name);
            }
            return p.toString();
        });
    }

    static void setSystemProperty(String name, String value)
        throws Exception {
        Configure.doPrivileged(() -> {
            if (value == null)
                System.clearProperty(name);
            else
                System.setProperty(name, value);
        });
    }

    static String trim(String value) {
        return value == null ? null : value.trim();
    }


    /**
     * Tests one of the configuration defined above.
     * <p>
     * This is the main test method (the rest is infrastructure).
     */
    static void testUpdateConfiguration() {
        String configFile = null;
        try {
            // manager initialized with default configuration.
            LogManager manager = LogManager.getLogManager();

            // Test default configuration. It should not have
            // any value for "com.foo.level" and "com.foo.handlers"
            assertEquals(null, manager.getProperty("com.foo.level"),
                "com.foo.level in default configuration");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in default configuration");

            // Create a logging configuration file that contains
            // com.foo.level=FINEST
            // and set "java.util.logging.config.file" to this file.
            Properties props = new Properties();
            props.setProperty("com.foo.level", "FINEST");
            configFile = storePropertyToFile("config1", props);
            setSystemProperty("java.util.logging.config.file", configFile);

            // Update configuration with configFile
            // then test that the new configuration has
            // com.foo.level=FINEST
            // and nothing for com.foo.handlers
            manager.updateConfiguration(null);
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level in " + configFile);
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in " + configFile);

            // clear ("java.util.logging.config.file" system property,
            // and call updateConfiguration again.
            // check that the new configuration no longer has
            // any value for com.foo.level, and still no value
            // for com.foo.handlers
            setSystemProperty("java.util.logging.config.file", null);
            manager.updateConfiguration(null);
            assertEquals(null, manager.getProperty("com.foo.level"),
                    "com.foo.level in default configuration");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in default configuration");

            // creates the com.foo logger, check it has
            // the default config: no level, and no handlers
            final Logger logger = Logger.getLogger("com.foo");
            assertEquals(null, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertDeepEquals(new Handler[0], logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");

            // set "java.util.logging.config.file" to configFile and
            // call updateConfiguration.
            // check that the configuration has
            // com.foo.level=FINEST
            // and nothing for com.foo.handlers
            // check that the logger has now a FINEST level and still
            // no handlers
            setSystemProperty("java.util.logging.config.file", configFile);
            manager.updateConfiguration(null);
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level in " + configFile);
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertDeepEquals(new Handler[0], logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in " + configFile);

            // Calls updateConfiguration with a lambda whose effect should
            // be to set the FINER level on the "com.foo" logger.
            // Check that the new configuration has
            // com.foo.level=FINER
            // and nothing for com.foo.handlers
            // check that the logger has now a FINER level and still
            // no handlers
            manager.updateConfiguration(
                    (k) -> ("com.foo.level".equals(k) ? (o, n) -> "FINER" : (o, n) -> n));
            assertEquals("FINER", manager.getProperty("com.foo.level"),
                "com.foo.level set to FINER by updateConfiguration");
            assertEquals(Level.FINER, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertDeepEquals(new Handler[0], logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in " + configFile);

            // Calls updateConfiguration with a lambda whose effect is a noop.
            // This should not change the configuration, so
            // check that the new configuration still has
            // com.foo.level=FINER
            // and nothing for com.foo.handlers
            // check that the logger still has FINER level and still
            // no handlers
            manager.updateConfiguration(
                    (k) -> ((o, n) -> o));
            assertEquals("FINER", manager.getProperty("com.foo.level"),
                "com.foo.level preserved by updateConfiguration");
            assertEquals(Level.FINER, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertDeepEquals(new Handler[0], logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in " + configFile);

            // Calls updateConfiguration with a lambda whose effect is to
            // take all values from the new configuration.
            // This should update the configuration to what is in configFile, so
            // check that the new configuration has
            // com.foo.level=FINEST
            // and nothing for com.foo.handlers
            // check that the logger now has FINEST level and still
            // no handlers
            manager.updateConfiguration(
                    (k) -> ((o, n) -> n));
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertDeepEquals(new Handler[0], logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in " + configFile);

            // now set a handler on the com.foo logger.
            MyHandler h = new MyHandler();
            logger.addHandler(h);
            assertDeepEquals(new Handler[] {h}, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");

            // Calls updateConfiguration with a lambda whose effect should
            // be to set the FINER level on the "com.foo" logger, and
            // take the value from configFile for everything else.
            // Check that the new configuration has
            // com.foo.level=FINER
            // and nothing for com.foo.handlers
            // check that the logger has now a FINER level, but that its
            // handlers are still present and have not been reset
            // since neither the old nor new configuration defined them.
            manager.updateConfiguration(
                    (k) -> ("com.foo.level".equals(k) ? (o, n) -> "FINER" : (o, n) -> n));
            assertEquals("FINER", manager.getProperty("com.foo.level"),
                "com.foo.level set to FINER by updateConfiguration");
            assertEquals(Level.FINER, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertDeepEquals(new Handler[] {h}, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals(null, manager.getProperty("com.foo.handlers"),
                "com.foo.handlers in " + configFile);

            // now add some configuration for com.foo.handlers in the
            // configuration file.
            props.setProperty("com.foo.handlers", MyHandler.class.getName());
            storePropertyToFile("config1", props);

            // we didn't call updateConfiguration, so just changing the
            // content of the file should have had no effect yet.
            assertEquals("FINER", manager.getProperty("com.foo.level"),
                "com.foo.level set to FINER by updateConfiguration");
            assertEquals(Level.FINER, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(null,
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");
            assertDeepEquals(new Handler[] {h}, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");

            // Calls updateConfiguration with a lambda whose effect is a noop.
            // This should not change the current configuration, so
            // check that the new configuration still has
            // com.foo.level=FINER
            // and nothing for com.foo.handlers
            // check that the logger still has FINER level and still
            // has its handlers and that they haven't been reset.
            manager.updateConfiguration((k) -> ((o, n) -> o));
            assertEquals("FINER", manager.getProperty("com.foo.level"),
                "com.foo.level set to FINER by updateConfiguration");
            assertEquals(Level.FINER, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(null,
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");
            assertDeepEquals(new Handler[] {h}, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");

            // Calls updateConfiguration with a lambda whose effect is to
            // take all values from the new configuration.
            // This should update the configuration to what is in configFile, so
            // check that the new configuration has
            // com.foo.level=FINEST
            // com.foo.handlers=SimpleUpdateConfigurationTest$MyHandler
            // check that the logger now has FINEST level
            // and a new handler instance, since the old config
            // had no handlers for com.foo and the new config has one.
            manager.updateConfiguration((k) -> ((o, n) -> n));
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(MyHandler.class.getName(),
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");
            Handler[] loggerHandlers = logger.getHandlers().clone();
            assertEquals(1, loggerHandlers.length,
                    "Logger.getLogger(\"com.foo\").getHandlers().length");
            assertEquals(MyHandler.class, loggerHandlers[0].getClass(),
                    "Logger.getLogger(\"com.foo\").getHandlers()[0].getClass()");
            assertEquals(h.count + 1, ((MyHandler)logger.getHandlers()[0]).count,
                    "Logger.getLogger(\"com.foo\").getHandlers()[0].count");

            // Calls updateConfiguration with a lambda whose effect is a noop.
            // This should not change the current configuration, so
            // check that the new configuration still has
            // com.foo.level=FINEST
            // com.foo.handlers=SimpleUpdateConfigurationTest$MyHandler
            // check that the logger still has FINEST level and still
            // has its handlers and that they haven't been reset.
            manager.updateConfiguration((k) -> ((o, n) -> o));
            assertDeepEquals(loggerHandlers, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(MyHandler.class.getName(),
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");

            // Calls updateConfiguration with a lambda whose effect is to
            // take all values from the new configuration.
            // Because the content of the configFile hasn't changed, then
            // it should also be a noop.
            // check that the new configuration still has
            // com.foo.level=FINEST
            // com.foo.handlers=SimpleUpdateConfigurationTest$MyHandler
            // check that the logger still has FINEST level and still
            // has its handlers and that they haven't been reset.
            manager.updateConfiguration((k) -> ((o, n) -> n));
            assertDeepEquals(loggerHandlers, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(MyHandler.class.getName(),
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");

            // Calls updateConfiguration with a null lambda, whose effect is to
            // take all values from the new configuration.
            // Because the content of the configFile hasn't changed, then
            // it should also be a noop.
            // check that the new configuration still has
            // com.foo.level=FINEST
            // com.foo.handlers=SimpleUpdateConfigurationTest$MyHandler
            // check that the logger still has FINEST level and still
            // has its handlers and that they haven't been reset.
            manager.updateConfiguration((k) -> ((o, n) -> n));
            assertDeepEquals(loggerHandlers, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(MyHandler.class.getName(),
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");

            // no remove com.foo.handlers=SimpleUpdateConfigurationTest$MyHandler
            // from the configuration file.
            props.remove("com.foo.handlers");
            storePropertyToFile("config1", props);

            // Calls updateConfiguration with a lambda whose effect is a noop.
            // This should not change the current configuration, so
            // check that the new configuration still has
            // com.foo.level=FINEST
            // com.foo.handlers=SimpleUpdateConfigurationTest$MyHandler
            // check that the logger still has FINEST level and still
            // has its handlers and that they haven't been reset.
            manager.updateConfiguration((k) -> ((o, n) -> o));
            assertDeepEquals(loggerHandlers, logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(MyHandler.class.getName(),
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");

            // Calls updateConfiguration with a lambda whose effect is to
            // take all values from the new configuration.
            // This should update the configuration to what is in configFile, so
            // check that the new configuration has
            // com.foo.level=FINEST
            // and nothing for com.foo.handlers
            // check that the logger still has FINEST level
            // and no handlers, since the old config
            // had an handler for com.foo and the new config doesn't.
            manager.updateConfiguration((k) -> ((o, n) -> n));
            assertDeepEquals(new Handler[0], logger.getHandlers(),
                    "Logger.getLogger(\"com.foo\").getHandlers()");
            assertEquals("FINEST", manager.getProperty("com.foo.level"),
                "com.foo.level updated by updateConfiguration");
            assertEquals(Level.FINEST, logger.getLevel(),
                "Logger.getLogger(\"com.foo\").getLevel()");
            assertEquals(null,
                    manager.getProperty("com.foo.handlers"),
                    "manager.getProperty(\"com.foo.handlers\")");


        } catch (RuntimeException | Error r) {
            throw r;
        } catch (Exception x) {
            throw new RuntimeException(x);
        } finally {
            if (configFile != null) {
                // cleanup
                final String file = configFile;
                Configure.doPrivileged(() -> {
                    try {
                        Files.delete(Paths.get(file));
                    } catch (RuntimeException | Error r) {
                        throw r;
                    } catch (Exception x) {
                        throw new RuntimeException(x);
                    }
                });
            }
        }
    }

    public static void main(String[] args) throws Exception {
        execute(SimpleUpdateConfigurationTest::testUpdateConfiguration);
    }

    static class Configure {
        static void updateConfigurationWith(Properties propertyFile,
                Function<String,BiFunction<String,String,String>> remapper) {
            try {
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                propertyFile.store(bytes, propertyFile.getProperty("test.name"));
                ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray());
                LogManager.getLogManager().updateConfiguration(bais, remapper);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }

        static void doPrivileged(Runnable run) {
            run.run();
        }
        static <T> T callPrivileged(Callable<T> call) throws Exception {
            return call.call();
        }
    }

    static final class TestAssertException extends RuntimeException {
        TestAssertException(String msg) {
            super(msg);
        }
    }

    private static void assertEquals(long expected, long received, String msg) {
        if (expected != received) {
            throw new TestAssertException("Unexpected result for " + msg
                    + ".\n\texpected: " + expected
                    +  "\n\tactual:   " + received);
        } else {
            System.out.println("Got expected " + msg + ": " + received);
        }
    }

    private static void assertEquals(String expected, String received, String msg) {
        if (!Objects.equals(expected, received)) {
            throw new TestAssertException("Unexpected result for " + msg
                    + ".\n\texpected: " + expected
                    +  "\n\tactual:   " + received);
        } else {
            System.out.println("Got expected " + msg + ": " + received);
        }
    }

    private static void assertEquals(Object expected, Object received, String msg) {
        if (!Objects.equals(expected, received)) {
            throw new TestAssertException("Unexpected result for " + msg
                    + ".\n\texpected: " + expected
                    +  "\n\tactual:   " + received);
        } else {
            System.out.println("Got expected " + msg + ": " + received);
        }
    }

    public static String deepToString(Object o) {
        if (o == null) {
            return "null";
        } else if (o.getClass().isArray()) {
            String s;
            if (o instanceof Object[])
                s = Arrays.deepToString((Object[]) o);
            else if (o instanceof byte[])
                s = Arrays.toString((byte[]) o);
            else if (o instanceof short[])
                s = Arrays.toString((short[]) o);
            else if (o instanceof int[])
                s = Arrays.toString((int[]) o);
            else if (o instanceof long[])
                s = Arrays.toString((long[]) o);
            else if (o instanceof char[])
                s = Arrays.toString((char[]) o);
            else if (o instanceof float[])
                s = Arrays.toString((float[]) o);
            else if (o instanceof double[])
                s = Arrays.toString((double[]) o);
            else if (o instanceof boolean[])
                s = Arrays.toString((boolean[]) o);
            else
                s = o.toString();
            return s;
        } else {
            return o.toString();
        }
    }

    private static void assertDeepEquals(Object expected, Object received, String msg) {
        if (!Objects.deepEquals(expected, received)) {
            throw new TestAssertException("Unexpected result for " + msg
                    + ".\n\texpected: " + deepToString(expected)
                    +  "\n\tactual:   " + deepToString(received));
        } else {
            System.out.println("Got expected " + msg + ": " + deepToString(received));
        }
    }
}