| /* | |
| * Copyright (c) 2015, 2016, 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 | |
| * java.logging | |
| * @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.getModule()); | |
| 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.getModule()); | |
| 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.getModule()); | |
| 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); | |
| } | |
| } | |
| } |