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