| /* |
| * 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); |
| } |
| } |
| |
| } |