bd8942b7a1
Initial implementation for JEP 264 Platform Logger API and Service Reviewed-by: mchung, psandoz, rriggs
498 lines
20 KiB
Java
498 lines
20 KiB
Java
/*
|
|
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @bug 8140364
|
|
* @author danielfuchs
|
|
* @summary JDK implementation specific unit test for JDK internal artifacts.
|
|
* Tests the consistency of the LoggerFinder and JDK extensions.
|
|
* @modules java.base/sun.util.logging
|
|
* java.base/jdk.internal.logger
|
|
* @run main LoggerFinderAPITest
|
|
*/
|
|
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.ResourceBundle;
|
|
import java.util.function.Supplier;
|
|
import java.util.logging.ConsoleHandler;
|
|
import java.util.logging.Handler;
|
|
import java.util.logging.LogRecord;
|
|
import java.util.logging.Logger;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
import sun.util.logging.PlatformLogger;
|
|
|
|
public class LoggerFinderAPITest {
|
|
|
|
static final Class<java.lang.System.Logger> spiLoggerClass
|
|
= java.lang.System.Logger.class;
|
|
static final Class<java.lang.System.Logger> jdkLoggerClass
|
|
= java.lang.System.Logger.class;
|
|
static final Class<sun.util.logging.PlatformLogger.Bridge> bridgeLoggerClass
|
|
= sun.util.logging.PlatformLogger.Bridge.class;
|
|
static final Class<java.util.logging.Logger> julLoggerClass
|
|
= java.util.logging.Logger.class;
|
|
static final Class<sun.util.logging.PlatformLogger.Bridge> julLogProducerClass
|
|
= PlatformLogger.Bridge.class;
|
|
static final Pattern julLogNames = Pattern.compile(
|
|
"^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$");
|
|
static final Collection<Method> julLoggerIgnores;
|
|
static {
|
|
List<Method> ignores = new ArrayList<>();
|
|
try {
|
|
ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class));
|
|
} catch (NoSuchMethodException | SecurityException ex) {
|
|
throw new ExceptionInInitializerError(ex);
|
|
}
|
|
julLoggerIgnores = Collections.unmodifiableList(ignores);
|
|
}
|
|
|
|
|
|
|
|
// Don't require LoggerBridge to have a body for those methods
|
|
interface LoggerBridgeMethodsWithNoBody extends
|
|
PlatformLogger.Bridge, java.lang.System.Logger {
|
|
|
|
@Override
|
|
public default String getName() {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
}
|
|
|
|
@Override
|
|
public default boolean isLoggable(PlatformLogger.Level level) {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
}
|
|
|
|
@Override
|
|
public default void log(sun.util.logging.PlatformLogger.Level level,
|
|
String msg, Throwable thrown) {
|
|
}
|
|
@Override
|
|
public default void log(sun.util.logging.PlatformLogger.Level level,
|
|
Throwable thrown, Supplier<String> msgSupplier) {
|
|
}
|
|
@Override
|
|
public default void log(sun.util.logging.PlatformLogger.Level level,
|
|
Supplier<String> msgSupplier) {
|
|
}
|
|
@Override
|
|
public default void log(sun.util.logging.PlatformLogger.Level level, String msg) {
|
|
}
|
|
@Override
|
|
public default void log(sun.util.logging.PlatformLogger.Level level,
|
|
String format, Object... params) {
|
|
}
|
|
@Override
|
|
public default void logrb(sun.util.logging.PlatformLogger.Level level,
|
|
ResourceBundle bundle, String key, Throwable thrown) {
|
|
}
|
|
@Override
|
|
public default void logrb(sun.util.logging.PlatformLogger.Level level,
|
|
ResourceBundle bundle, String format, Object... params) {
|
|
}
|
|
|
|
@Override
|
|
public default void logrb(PlatformLogger.Level level,
|
|
String sourceClass, String sourceMethod,
|
|
ResourceBundle bundle, String msg, Throwable thrown) {
|
|
}
|
|
|
|
@Override
|
|
public default void logrb(PlatformLogger.Level level, String sourceClass,
|
|
String sourceMethod, ResourceBundle bundle, String msg,
|
|
Object... params) {
|
|
}
|
|
|
|
@Override
|
|
public default void logp(PlatformLogger.Level level, String sourceClass,
|
|
String sourceMethod, Supplier<String> msgSupplier) {
|
|
}
|
|
|
|
@Override
|
|
public default void logp(PlatformLogger.Level level, String sourceClass,
|
|
String sourceMethod, String msg, Object... params) {
|
|
}
|
|
|
|
@Override
|
|
public default void logp(PlatformLogger.Level level, String sourceClass,
|
|
String sourceMethod, String msg, Throwable thrown) {
|
|
}
|
|
|
|
@Override
|
|
public default void logp(PlatformLogger.Level level, String sourceClass,
|
|
String sourceMethod, String msg) {
|
|
}
|
|
|
|
@Override
|
|
public default void logp(PlatformLogger.Level level, String sourceClass,
|
|
String sourceMethod, Throwable thrown,
|
|
Supplier<String> msgSupplier) {
|
|
}
|
|
|
|
static boolean requiresDefaultBodyFor(Method m) {
|
|
try {
|
|
Method m2 = LoggerBridgeMethodsWithNoBody.class
|
|
.getDeclaredMethod(m.getName(),
|
|
m.getParameterTypes());
|
|
return !m2.isDefault();
|
|
} catch (NoSuchMethodException x) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
final boolean warnDuplicateMappings;
|
|
public LoggerFinderAPITest(boolean verbose) {
|
|
this.warnDuplicateMappings = verbose;
|
|
for (Handler h : Logger.getLogger("").getHandlers()) {
|
|
if (h instanceof ConsoleHandler) {
|
|
Logger.getLogger("").removeHandler(h);
|
|
}
|
|
}
|
|
Logger.getLogger("").addHandler( new Handler() {
|
|
@Override
|
|
public void publish(LogRecord record) {
|
|
StringBuilder builder = new StringBuilder();
|
|
builder.append("GOT LogRecord: ")
|
|
.append(record.getLevel().getLocalizedName())
|
|
.append(": [").append(record.getLoggerName())
|
|
.append("] ").append(record.getSourceClassName())
|
|
.append('.')
|
|
.append(record.getSourceMethodName()).append(" -> ")
|
|
.append(record.getMessage())
|
|
.append(' ')
|
|
.append(record.getParameters() == null ? ""
|
|
: Arrays.toString(record.getParameters()))
|
|
;
|
|
System.out.println(builder);
|
|
if (record.getThrown() != null) {
|
|
record.getThrown().printStackTrace(System.out);
|
|
}
|
|
}
|
|
@Override public void flush() {}
|
|
@Override public void close() {}
|
|
});
|
|
}
|
|
|
|
public Stream<Method> getJulLogMethodStream(Class<?> loggerClass) {
|
|
|
|
return Stream.of(loggerClass.getMethods()).filter((x) -> {
|
|
final Matcher m = julLogNames.matcher(x.getName());
|
|
return m.matches() ? x.getAnnotation(Deprecated.class) == null : false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Tells whether a method invocation of 'origin' can be transformed in a
|
|
* method invocation of 'target'.
|
|
* This method only look at the parameter signatures, it doesn't look at
|
|
* the name, nor does it look at the return types.
|
|
* <p>
|
|
* Example:
|
|
* <ul>
|
|
* <li>java.util.logging.Logger.log(Level, String, Object) can be invoked as<br>
|
|
java.util.logging.spi.Logger.log(Level, String, Object...) because the
|
|
last parameter in 'target' is a varargs.</li>
|
|
* <li>java.util.logging.Logger.log(Level, String) can also be invoked as<br>
|
|
java.util.logging.spi.Logger.log(Level, String, Object...) for the
|
|
same reason.</li>
|
|
* </ul>
|
|
* <p>
|
|
* The algorithm is tailored for our needs: when the last parameter in the
|
|
* target is a vararg, and when origin & target have the same number of
|
|
* parameters, then we consider that the types of the last parameter *must*
|
|
* match.
|
|
* <p>
|
|
* Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y)
|
|
* although strictly speaking, it should...
|
|
*
|
|
* @param origin The method in the original class
|
|
* @param target The correspondent candidate in the target class
|
|
* @return true if a method invocation of 'origin' can be transformed in a
|
|
* method invocation of 'target'.
|
|
*/
|
|
public boolean canBeInvokedAs(Method origin, Method target,
|
|
Map<Class<?>,Class<?>> substitutes) {
|
|
final Class<?>[] xParams = target.getParameterTypes();
|
|
final Class<?>[] mParams = Stream.of(origin.getParameterTypes())
|
|
.map((x) -> substitutes.getOrDefault(x, x))
|
|
.collect(Collectors.toList()).toArray(new Class<?>[0]);
|
|
if (Arrays.deepEquals(xParams, mParams)) return true;
|
|
if (target.isVarArgs()) {
|
|
if (xParams.length == mParams.length) {
|
|
if (xParams[xParams.length-1].isArray()) {
|
|
return mParams[mParams.length -1].equals(
|
|
xParams[xParams.length -1].getComponentType());
|
|
}
|
|
} else if (xParams.length == mParams.length + 1) {
|
|
return Arrays.deepEquals(
|
|
Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Look whether {@code otherClass} has a public method similar to m
|
|
* @param m
|
|
* @param otherClass
|
|
* @return
|
|
*/
|
|
public Stream<Method> findInvokable(Method m, Class<?> otherClass) {
|
|
final Map<Class<?>,Class<?>> substitues =
|
|
Collections.singletonMap(java.util.logging.Level.class,
|
|
sun.util.logging.PlatformLogger.Level.class);
|
|
return Stream.of(otherClass.getMethods())
|
|
.filter((x) -> m.getName().equals(x.getName()))
|
|
.filter((x) -> canBeInvokedAs(m, x, substitues));
|
|
}
|
|
|
|
/**
|
|
* Test that the concrete Logger implementation passed as parameter
|
|
* overrides all the methods defined by its interface.
|
|
* @param julLogger A concrete implementation of System.Logger
|
|
* whose backend is a JUL Logger.
|
|
*/
|
|
StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) {
|
|
final StringBuilder errors = new StringBuilder();
|
|
if (!bridgeLoggerClass.isInstance(julLogger)) {
|
|
final String errorMsg =
|
|
"Logger returned by LoggerFactory.getLogger(\"foo\") is not a "
|
|
+ bridgeLoggerClass + "\n\t" + julLogger;
|
|
System.err.println(errorMsg);
|
|
errors.append(errorMsg).append('\n');
|
|
}
|
|
final Class<? extends java.lang.System.Logger> xClass = julLogger.getClass();
|
|
List<Method> notOverridden =
|
|
Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> {
|
|
try {
|
|
Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes());
|
|
return x == null;
|
|
} catch (NoSuchMethodException ex) {
|
|
return !Modifier.isStatic(m.getModifiers());
|
|
}
|
|
}).collect(Collectors.toList());
|
|
notOverridden.stream().filter((x) -> {
|
|
boolean shouldOverride = true;
|
|
try {
|
|
final Method m = xClass.getMethod(x.getName(), x.getParameterTypes());
|
|
Method m2 = null;
|
|
try {
|
|
m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes());
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
shouldOverride = m.isDefault() || m2 == null;
|
|
} catch (Exception e) {
|
|
// should override.
|
|
}
|
|
return shouldOverride;
|
|
}).forEach(x -> {
|
|
final String errorMsg = xClass.getName() + " should override\n\t" + x.toString();
|
|
System.err.println(errorMsg);
|
|
errors.append(errorMsg).append('\n');
|
|
});
|
|
if (notOverridden.isEmpty()) {
|
|
System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass);
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
public static class ResourceBundeParam extends ResourceBundle {
|
|
Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());
|
|
@Override
|
|
protected Object handleGetObject(String key) {
|
|
map.putIfAbsent(key, "${"+key+"}");
|
|
return map.get(key);
|
|
}
|
|
|
|
@Override
|
|
public Enumeration<String> getKeys() {
|
|
return Collections.enumeration(new LinkedHashSet<>(map.keySet()));
|
|
}
|
|
|
|
}
|
|
|
|
final ResourceBundle bundleParam =
|
|
ResourceBundle.getBundle(ResourceBundeParam.class.getName());
|
|
|
|
public static class ResourceBundeLocalized extends ResourceBundle {
|
|
Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());
|
|
@Override
|
|
protected Object handleGetObject(String key) {
|
|
map.putIfAbsent(key, "Localized:${"+key+"}");
|
|
return map.get(key);
|
|
}
|
|
|
|
@Override
|
|
public Enumeration<String> getKeys() {
|
|
return Collections.enumeration(new LinkedHashSet<>(map.keySet()));
|
|
}
|
|
|
|
}
|
|
|
|
final static ResourceBundle bundleLocalized =
|
|
ResourceBundle.getBundle(ResourceBundeLocalized.class.getName());
|
|
|
|
final Map<Class<?>, Object> params = new HashMap<>();
|
|
{
|
|
params.put(String.class, "TestString");
|
|
params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING);
|
|
params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING);
|
|
params.put(ResourceBundle.class, bundleParam);
|
|
params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)"));
|
|
params.put(Object[].class, new Object[] {"One", "Two"});
|
|
params.put(Object.class, new Object() {
|
|
@Override public String toString() { return "I am an object!"; }
|
|
});
|
|
}
|
|
|
|
public Object[] getParamsFor(Method m) {
|
|
final Object[] res = new Object[m.getParameterCount()];
|
|
final Class<?>[] sig = m.getParameterTypes();
|
|
if (res.length == 0) {
|
|
return res;
|
|
}
|
|
for (int i=0; i<res.length; i++) {
|
|
Object p = params.get(sig[i]);
|
|
if (p == null && sig[i].equals(Supplier.class)) {
|
|
final String msg = "SuppliedMsg["+i+"]";
|
|
p = (Supplier<String>) () -> msg;
|
|
}
|
|
if (p instanceof String) {
|
|
res[i] = String.valueOf(p)+"["+i+"]";
|
|
} else {
|
|
res[i] = p;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
public void invokeOn(java.lang.System.Logger logger, Method m) {
|
|
Object[] p = getParamsFor(m);
|
|
try {
|
|
m.invoke(logger, p);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Failed to invoke "+m.toString(), e);
|
|
}
|
|
}
|
|
|
|
public void testAllJdkExtensionMethods(java.lang.System.Logger logger) {
|
|
Stream.of(jdkLoggerClass.getDeclaredMethods())
|
|
.filter(m -> !Modifier.isStatic(m.getModifiers()))
|
|
.forEach((m) -> invokeOn(logger, m));
|
|
}
|
|
|
|
public void testAllAPIMethods(java.lang.System.Logger logger) {
|
|
Stream.of(spiLoggerClass.getDeclaredMethods())
|
|
.filter(m -> !Modifier.isStatic(m.getModifiers()))
|
|
.forEach((m) -> invokeOn(logger, m));
|
|
}
|
|
|
|
public void testAllBridgeMethods(java.lang.System.Logger logger) {
|
|
Stream.of(bridgeLoggerClass.getDeclaredMethods())
|
|
.filter(m -> !Modifier.isStatic(m.getModifiers()))
|
|
.forEach((m) -> invokeOn(logger, m));
|
|
}
|
|
|
|
public void testAllLogProducerMethods(java.lang.System.Logger logger) {
|
|
Stream.of(julLogProducerClass.getDeclaredMethods())
|
|
.filter(m -> !Modifier.isStatic(m.getModifiers()))
|
|
.forEach((m) -> invokeOn(logger, m));
|
|
}
|
|
|
|
public StringBuilder testGetLoggerOverriddenOnSpi() {
|
|
final StringBuilder errors = new StringBuilder();
|
|
Stream.of(jdkLoggerClass.getDeclaredMethods())
|
|
.filter(m -> Modifier.isStatic(m.getModifiers()))
|
|
.filter(m -> Modifier.isPublic(m.getModifiers()))
|
|
.filter(m -> !m.getName().equals("getLoggerFinder"))
|
|
.filter(m -> {
|
|
try {
|
|
final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes());
|
|
return x == null;
|
|
} catch (NoSuchMethodException ex) {
|
|
return true;
|
|
}
|
|
}).forEach(m -> {
|
|
final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString();
|
|
System.err.println(errorMsg);
|
|
errors.append(errorMsg).append('\n');
|
|
});
|
|
if (errors.length() == 0) {
|
|
System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass);
|
|
} else {
|
|
if (errors.length() > 0) throw new RuntimeException(errors.toString());
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
public static void main(String argv[]) throws Exception {
|
|
final LoggerFinderAPITest test = new LoggerFinderAPITest(false);
|
|
final StringBuilder errors = new StringBuilder();
|
|
errors.append(test.testGetLoggerOverriddenOnSpi());
|
|
java.lang.System.Logger julLogger =
|
|
java.lang.System.LoggerFinder.getLoggerFinder()
|
|
.getLogger("foo", LoggerFinderAPITest.class);
|
|
errors.append(test.testDefaultJULLogger(julLogger));
|
|
if (errors.length() > 0) throw new RuntimeException(errors.toString());
|
|
java.lang.System.Logger julSystemLogger =
|
|
java.lang.System.LoggerFinder.getLoggerFinder()
|
|
.getLogger("bar", Thread.class);
|
|
errors.append(test.testDefaultJULLogger(julSystemLogger));
|
|
if (errors.length() > 0) throw new RuntimeException(errors.toString());
|
|
java.lang.System.Logger julLocalizedLogger =
|
|
(java.lang.System.Logger)
|
|
System.getLogger("baz", bundleLocalized);
|
|
java.lang.System.Logger julLocalizedSystemLogger =
|
|
java.lang.System.LoggerFinder.getLoggerFinder()
|
|
.getLocalizedLogger("oof", bundleLocalized, Thread.class);
|
|
final String error = errors.toString();
|
|
if (!error.isEmpty()) throw new RuntimeException(error);
|
|
for (java.lang.System.Logger logger : new java.lang.System.Logger[] {
|
|
julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger
|
|
}) {
|
|
test.testAllJdkExtensionMethods(logger);
|
|
test.testAllAPIMethods(logger);
|
|
test.testAllBridgeMethods(logger);
|
|
test.testAllLogProducerMethods(logger);
|
|
}
|
|
}
|
|
|
|
}
|