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 }