1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8140364 27 * @author danielfuchs 28 * @summary JDK implementation specific unit test for JDK internal artifacts. 29 * Tests the consistency of the LoggerFinder and JDK extensions. 30 * @modules java.base/sun.util.logging 31 * java.base/jdk.internal.logger 32 * @run main LoggerFinderAPITest 33 */ 34 35 36 import java.lang.reflect.Method; 37 import java.lang.reflect.Modifier; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collection; 41 import java.util.Collections; 42 import java.util.Enumeration; 43 import java.util.HashMap; 44 import java.util.LinkedHashMap; 45 import java.util.LinkedHashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.ResourceBundle; 49 import java.util.function.Supplier; 50 import java.util.logging.ConsoleHandler; 51 import java.util.logging.Handler; 52 import java.util.logging.LogRecord; 53 import java.util.logging.Logger; 54 import java.util.regex.Matcher; 55 import java.util.regex.Pattern; 56 import java.util.stream.Collectors; 57 import java.util.stream.Stream; 58 import sun.util.logging.PlatformLogger; 59 60 public class LoggerFinderAPITest { 61 62 static final Class<java.lang.System.Logger> spiLoggerClass 63 = java.lang.System.Logger.class; 64 static final Class<java.lang.System.Logger> jdkLoggerClass 65 = java.lang.System.Logger.class; 66 static final Class<sun.util.logging.PlatformLogger.Bridge> bridgeLoggerClass 67 = sun.util.logging.PlatformLogger.Bridge.class; 68 static final Class<java.util.logging.Logger> julLoggerClass 69 = java.util.logging.Logger.class; 70 static final Class<sun.util.logging.PlatformLogger.Bridge> julLogProducerClass 71 = PlatformLogger.Bridge.class; 72 static final Pattern julLogNames = Pattern.compile( 73 "^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$"); 74 static final Collection<Method> julLoggerIgnores; 75 static { 76 List<Method> ignores = new ArrayList<>(); 77 try { 78 ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class)); 79 } catch (NoSuchMethodException | SecurityException ex) { 80 throw new ExceptionInInitializerError(ex); 81 } 82 julLoggerIgnores = Collections.unmodifiableList(ignores); 83 } 84 85 86 87 // Don't require LoggerBridge to have a body for those methods 88 interface LoggerBridgeMethodsWithNoBody extends 89 PlatformLogger.Bridge, java.lang.System.Logger { 90 91 @Override 92 public default String getName() { 93 throw new UnsupportedOperationException("Not supported yet."); 94 } 95 96 @Override 97 public default boolean isLoggable(PlatformLogger.Level level) { 98 throw new UnsupportedOperationException("Not supported yet."); 99 } 100 101 @Override 102 public default void log(sun.util.logging.PlatformLogger.Level level, 103 String msg, Throwable thrown) { 104 } 105 @Override 106 public default void log(sun.util.logging.PlatformLogger.Level level, 107 Throwable thrown, Supplier<String> msgSupplier) { 108 } 109 @Override 110 public default void log(sun.util.logging.PlatformLogger.Level level, 111 Supplier<String> msgSupplier) { 112 } 113 @Override 114 public default void log(sun.util.logging.PlatformLogger.Level level, String msg) { 115 } 116 @Override 117 public default void log(sun.util.logging.PlatformLogger.Level level, 118 String format, Object... params) { 119 } 120 @Override 121 public default void logrb(sun.util.logging.PlatformLogger.Level level, 122 ResourceBundle bundle, String key, Throwable thrown) { 123 } 124 @Override 125 public default void logrb(sun.util.logging.PlatformLogger.Level level, 126 ResourceBundle bundle, String format, Object... params) { 127 } 128 129 @Override 130 public default void logrb(PlatformLogger.Level level, 131 String sourceClass, String sourceMethod, 132 ResourceBundle bundle, String msg, Throwable thrown) { 133 } 134 135 @Override 136 public default void logrb(PlatformLogger.Level level, String sourceClass, 137 String sourceMethod, ResourceBundle bundle, String msg, 138 Object... params) { 139 } 140 141 @Override 142 public default void logp(PlatformLogger.Level level, String sourceClass, 143 String sourceMethod, Supplier<String> msgSupplier) { 144 } 145 146 @Override 147 public default void logp(PlatformLogger.Level level, String sourceClass, 148 String sourceMethod, String msg, Object... params) { 149 } 150 151 @Override 152 public default void logp(PlatformLogger.Level level, String sourceClass, 153 String sourceMethod, String msg, Throwable thrown) { 154 } 155 156 @Override 157 public default void logp(PlatformLogger.Level level, String sourceClass, 158 String sourceMethod, String msg) { 159 } 160 161 @Override 162 public default void logp(PlatformLogger.Level level, String sourceClass, 163 String sourceMethod, Throwable thrown, 164 Supplier<String> msgSupplier) { 165 } 166 167 static boolean requiresDefaultBodyFor(Method m) { 168 try { 169 Method m2 = LoggerBridgeMethodsWithNoBody.class 170 .getDeclaredMethod(m.getName(), 171 m.getParameterTypes()); 172 return !m2.isDefault(); 173 } catch (NoSuchMethodException x) { 174 return true; 175 } 176 } 177 } 178 179 final boolean warnDuplicateMappings; 180 public LoggerFinderAPITest(boolean verbose) { 181 this.warnDuplicateMappings = verbose; 182 for (Handler h : Logger.getLogger("").getHandlers()) { 183 if (h instanceof ConsoleHandler) { 184 Logger.getLogger("").removeHandler(h); 185 } 186 } 187 Logger.getLogger("").addHandler( new Handler() { 188 @Override 189 public void publish(LogRecord record) { 190 StringBuilder builder = new StringBuilder(); 191 builder.append("GOT LogRecord: ") 192 .append(record.getLevel().getLocalizedName()) 193 .append(": [").append(record.getLoggerName()) 194 .append("] ").append(record.getSourceClassName()) 195 .append('.') 196 .append(record.getSourceMethodName()).append(" -> ") 197 .append(record.getMessage()) 198 .append(' ') 199 .append(record.getParameters() == null ? "" 200 : Arrays.toString(record.getParameters())) 201 ; 202 System.out.println(builder); 203 if (record.getThrown() != null) { 204 record.getThrown().printStackTrace(System.out); 205 } 206 } 207 @Override public void flush() {} 208 @Override public void close() {} 209 }); 210 } 211 212 public Stream<Method> getJulLogMethodStream(Class<?> loggerClass) { 213 214 return Stream.of(loggerClass.getMethods()).filter((x) -> { 215 final Matcher m = julLogNames.matcher(x.getName()); 216 return m.matches() ? x.getAnnotation(Deprecated.class) == null : false; 217 }); 218 } 219 220 /** 221 * Tells whether a method invocation of 'origin' can be transformed in a 222 * method invocation of 'target'. 223 * This method only look at the parameter signatures, it doesn't look at 224 * the name, nor does it look at the return types. 225 * <p> 226 * Example: 227 * <ul> 228 * <li>java.util.logging.Logger.log(Level, String, Object) can be invoked as<br> 229 java.util.logging.spi.Logger.log(Level, String, Object...) because the 230 last parameter in 'target' is a varargs.</li> 231 * <li>java.util.logging.Logger.log(Level, String) can also be invoked as<br> 232 java.util.logging.spi.Logger.log(Level, String, Object...) for the 233 same reason.</li> 234 * </ul> 235 * <p> 236 * The algorithm is tailored for our needs: when the last parameter in the 237 * target is a vararg, and when origin & target have the same number of 238 * parameters, then we consider that the types of the last parameter *must* 239 * match. 240 * <p> 241 * Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y) 242 * although strictly speaking, it should... 243 * 244 * @param origin The method in the original class 245 * @param target The correspondent candidate in the target class 246 * @return true if a method invocation of 'origin' can be transformed in a 247 * method invocation of 'target'. 248 */ 249 public boolean canBeInvokedAs(Method origin, Method target, 250 Map<Class<?>,Class<?>> substitutes) { 251 final Class<?>[] xParams = target.getParameterTypes(); 252 final Class<?>[] mParams = Stream.of(origin.getParameterTypes()) 253 .map((x) -> substitutes.getOrDefault(x, x)) 254 .collect(Collectors.toList()).toArray(new Class<?>[0]); 255 if (Arrays.deepEquals(xParams, mParams)) return true; 256 if (target.isVarArgs()) { 257 if (xParams.length == mParams.length) { 258 if (xParams[xParams.length-1].isArray()) { 259 return mParams[mParams.length -1].equals( 260 xParams[xParams.length -1].getComponentType()); 261 } 262 } else if (xParams.length == mParams.length + 1) { 263 return Arrays.deepEquals( 264 Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams); 265 } 266 } 267 return false; 268 } 269 270 /** 271 * Look whether {@code otherClass} has a public method similar to m 272 * @param m 273 * @param otherClass 274 * @return 275 */ 276 public Stream<Method> findInvokable(Method m, Class<?> otherClass) { 277 final Map<Class<?>,Class<?>> substitues = 278 Collections.singletonMap(java.util.logging.Level.class, 279 sun.util.logging.PlatformLogger.Level.class); 280 return Stream.of(otherClass.getMethods()) 281 .filter((x) -> m.getName().equals(x.getName())) 282 .filter((x) -> canBeInvokedAs(m, x, substitues)); 283 } 284 285 /** 286 * Test that the concrete Logger implementation passed as parameter 287 * overrides all the methods defined by its interface. 288 * @param julLogger A concrete implementation of System.Logger 289 * whose backend is a JUL Logger. 290 */ 291 StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) { 292 final StringBuilder errors = new StringBuilder(); 293 if (!bridgeLoggerClass.isInstance(julLogger)) { 294 final String errorMsg = 295 "Logger returned by LoggerFactory.getLogger(\"foo\") is not a " 296 + bridgeLoggerClass + "\n\t" + julLogger; 297 System.err.println(errorMsg); 298 errors.append(errorMsg).append('\n'); 299 } 300 final Class<? extends java.lang.System.Logger> xClass = julLogger.getClass(); 301 List<Method> notOverridden = 302 Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> { 303 try { 304 Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); 305 return x == null; 306 } catch (NoSuchMethodException ex) { 307 return !Modifier.isStatic(m.getModifiers()); 308 } 309 }).collect(Collectors.toList()); 310 notOverridden.stream().filter((x) -> { 311 boolean shouldOverride = true; 312 try { 313 final Method m = xClass.getMethod(x.getName(), x.getParameterTypes()); 314 Method m2 = null; 315 try { 316 m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes()); 317 } catch (Exception e) { 318 319 } 320 shouldOverride = m.isDefault() || m2 == null; 321 } catch (Exception e) { 322 // should override. 323 } 324 return shouldOverride; 325 }).forEach(x -> { 326 final String errorMsg = xClass.getName() + " should override\n\t" + x.toString(); 327 System.err.println(errorMsg); 328 errors.append(errorMsg).append('\n'); 329 }); 330 if (notOverridden.isEmpty()) { 331 System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass); 332 } 333 return errors; 334 } 335 336 public static class ResourceBundeParam extends ResourceBundle { 337 Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>()); 338 @Override 339 protected Object handleGetObject(String key) { 340 map.putIfAbsent(key, "${"+key+"}"); 341 return map.get(key); 342 } 343 344 @Override 345 public Enumeration<String> getKeys() { 346 return Collections.enumeration(new LinkedHashSet<>(map.keySet())); 347 } 348 349 } 350 351 final ResourceBundle bundleParam = 352 ResourceBundle.getBundle(ResourceBundeParam.class.getName()); 353 354 public static class ResourceBundeLocalized extends ResourceBundle { 355 Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>()); 356 @Override 357 protected Object handleGetObject(String key) { 358 map.putIfAbsent(key, "Localized:${"+key+"}"); 359 return map.get(key); 360 } 361 362 @Override 363 public Enumeration<String> getKeys() { 364 return Collections.enumeration(new LinkedHashSet<>(map.keySet())); 365 } 366 367 } 368 369 final static ResourceBundle bundleLocalized = 370 ResourceBundle.getBundle(ResourceBundeLocalized.class.getName()); 371 372 final Map<Class<?>, Object> params = new HashMap<>(); 373 { 374 params.put(String.class, "TestString"); 375 params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING); 376 params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING); 377 params.put(ResourceBundle.class, bundleParam); 378 params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)")); 379 params.put(Object[].class, new Object[] {"One", "Two"}); 380 params.put(Object.class, new Object() { 381 @Override public String toString() { return "I am an object!"; } 382 }); 383 } 384 385 public Object[] getParamsFor(Method m) { 386 final Object[] res = new Object[m.getParameterCount()]; 387 final Class<?>[] sig = m.getParameterTypes(); 388 if (res.length == 0) { 389 return res; 390 } 391 for (int i=0; i<res.length; i++) { 392 Object p = params.get(sig[i]); 393 if (p == null && sig[i].equals(Supplier.class)) { 394 final String msg = "SuppliedMsg["+i+"]"; 395 p = (Supplier<String>) () -> msg; 396 } 397 if (p instanceof String) { 398 res[i] = String.valueOf(p)+"["+i+"]"; 399 } else { 400 res[i] = p; 401 } 402 } 403 return res; 404 } 405 406 public void invokeOn(java.lang.System.Logger logger, Method m) { 407 Object[] p = getParamsFor(m); 408 try { 409 m.invoke(logger, p); 410 } catch (Exception e) { 411 throw new RuntimeException("Failed to invoke "+m.toString(), e); 412 } 413 } 414 415 public void testAllJdkExtensionMethods(java.lang.System.Logger logger) { 416 Stream.of(jdkLoggerClass.getDeclaredMethods()) 417 .filter(m -> !Modifier.isStatic(m.getModifiers())) 418 .forEach((m) -> invokeOn(logger, m)); 419 } 420 421 public void testAllAPIMethods(java.lang.System.Logger logger) { 422 Stream.of(spiLoggerClass.getDeclaredMethods()) 423 .filter(m -> !Modifier.isStatic(m.getModifiers())) 424 .forEach((m) -> invokeOn(logger, m)); 425 } 426 427 public void testAllBridgeMethods(java.lang.System.Logger logger) { 428 Stream.of(bridgeLoggerClass.getDeclaredMethods()) 429 .filter(m -> !Modifier.isStatic(m.getModifiers())) 430 .forEach((m) -> invokeOn(logger, m)); 431 } 432 433 public void testAllLogProducerMethods(java.lang.System.Logger logger) { 434 Stream.of(julLogProducerClass.getDeclaredMethods()) 435 .filter(m -> !Modifier.isStatic(m.getModifiers())) 436 .forEach((m) -> invokeOn(logger, m)); 437 } 438 439 public StringBuilder testGetLoggerOverriddenOnSpi() { 440 final StringBuilder errors = new StringBuilder(); 441 Stream.of(jdkLoggerClass.getDeclaredMethods()) 442 .filter(m -> Modifier.isStatic(m.getModifiers())) 443 .filter(m -> Modifier.isPublic(m.getModifiers())) 444 .filter(m -> !m.getName().equals("getLoggerFinder")) 445 .filter(m -> { 446 try { 447 final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); 448 return x == null; 449 } catch (NoSuchMethodException ex) { 450 return true; 451 } 452 }).forEach(m -> { 453 final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString(); 454 System.err.println(errorMsg); 455 errors.append(errorMsg).append('\n'); 456 }); 457 if (errors.length() == 0) { 458 System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass); 459 } else { 460 if (errors.length() > 0) throw new RuntimeException(errors.toString()); 461 } 462 return errors; 463 } 464 465 public static void main(String argv[]) throws Exception { 466 final LoggerFinderAPITest test = new LoggerFinderAPITest(false); 467 final StringBuilder errors = new StringBuilder(); 468 errors.append(test.testGetLoggerOverriddenOnSpi()); 469 java.lang.System.Logger julLogger = 470 java.lang.System.LoggerFinder.getLoggerFinder() 471 .getLogger("foo", LoggerFinderAPITest.class); 472 errors.append(test.testDefaultJULLogger(julLogger)); 473 if (errors.length() > 0) throw new RuntimeException(errors.toString()); 474 java.lang.System.Logger julSystemLogger = 475 java.lang.System.LoggerFinder.getLoggerFinder() 476 .getLogger("bar", Thread.class); 477 errors.append(test.testDefaultJULLogger(julSystemLogger)); 478 if (errors.length() > 0) throw new RuntimeException(errors.toString()); 479 java.lang.System.Logger julLocalizedLogger = 480 (java.lang.System.Logger) 481 System.getLogger("baz", bundleLocalized); 482 java.lang.System.Logger julLocalizedSystemLogger = 483 java.lang.System.LoggerFinder.getLoggerFinder() 484 .getLocalizedLogger("oof", bundleLocalized, Thread.class); 485 final String error = errors.toString(); 486 if (!error.isEmpty()) throw new RuntimeException(error); 487 for (java.lang.System.Logger logger : new java.lang.System.Logger[] { 488 julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger 489 }) { 490 test.testAllJdkExtensionMethods(logger); 491 test.testAllAPIMethods(logger); 492 test.testAllBridgeMethods(logger); 493 test.testAllLogProducerMethods(logger); 494 } 495 } 496 497 }