1 /*
   2  * Copyright (c) 2013, 2016, 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 import java.io.*;
  25 import java.lang.management.ManagementFactory;
  26 import java.lang.management.PlatformLoggingMXBean;
  27 import java.lang.ref.Reference;
  28 import java.lang.ref.ReferenceQueue;
  29 import java.lang.ref.WeakReference;
  30 import java.lang.reflect.InvocationTargetException;
  31 import java.lang.reflect.Method;
  32 import java.net.MalformedURLException;
  33 import java.net.URL;
  34 import java.net.URLClassLoader;
  35 import java.util.*;
  36 import java.util.logging.*;
  37 
  38 /*
  39  * @test
  40  * @bug 8026027 6543126 8187073
  41  * @modules java.management
  42  * @summary Test Level.parse to look up custom levels by name and its
  43  *          localized name, as well as severity.
  44  *
  45  * @run main/othervm CustomLevel
  46  */
  47 
  48 public class CustomLevel extends Level {
  49     public CustomLevel(String name, int value, String resourceBundleName) {
  50         super(name, value, resourceBundleName);
  51     }
  52 
  53     private static final List<Level> levels = new ArrayList<>();
  54     private static final String RB_NAME = "myresource";
  55     private static final String OTHERRB_NAME = "myresource2";
  56 
  57     private static class CustomLevelReference extends WeakReference<Level> {
  58         final String name;
  59         final int value;
  60         final String resourceBundleName;
  61         public CustomLevelReference(Level level, ReferenceQueue<Level> queue) {
  62             super(level, queue);
  63             name = level.getName();
  64             value = level.intValue();
  65             resourceBundleName = level.getResourceBundleName();
  66         }
  67 
  68         @Override
  69         public String toString() {
  70             return "CustomLevelReference(\"" + name + "\", " + value + ", \""
  71                     + resourceBundleName + "\")";
  72         }
  73 
  74     }
  75 
  76     public static void main(String[] args) throws Exception {
  77         setupCustomLevels();
  78         setUpCustomLevelsOtherLoader();
  79         PlatformLoggingMXBean mxbean = ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class);
  80         Logger logger = Logger.getLogger("foo.bar");
  81 
  82         // Level.parse will return the custom Level instance
  83         for (Level level : levels) {
  84             final ResourceBundle rb = getResourceBundle(level);
  85             String name = level.getName();
  86             Level l = Level.parse(name);
  87             if (!name.equals("WARNING") && !name.equals("INFO")
  88                  && !name.equals("SEVERE")) {
  89                 // custom level whose name doesn't conflict with any standard one
  90                 checkCustomLevel(l, level);
  91             } else if (l != Level.WARNING && l != Level.INFO && l != Level.SEVERE
  92                     || !name.equals(l.getName())) {
  93                 throw new RuntimeException("Unexpected level " + formatLevel(l));
  94             }
  95             System.out.println("Level.parse found expected level: "
  96                             + formatLevel(l));
  97             String localizedName = rb.getString(level.getName());
  98             l = Level.parse(localizedName);
  99             if (l != level) {
 100                 throw new RuntimeException("Unexpected level " + l + " "
 101                     + l.getClass() + " for " + localizedName
 102                     + " in " + rb.getBaseBundleName());
 103             }
 104             l = Level.parse(String.valueOf(level.intValue()));
 105             System.out.println("Level.parse(" + level.intValue() + ") returns " + l);
 106             if (l != level) {
 107                 if (l == null || l.intValue() != level.intValue()) {
 108                     throw new RuntimeException("Unexpected level " + l
 109                             + (l == null ? "" : (" " + l.getClass()))
 110                             + " for " + level.intValue());
 111                 }
 112             }
 113             mxbean.setLoggerLevel(logger.getName(), String.valueOf(level.intValue()));
 114             Level l2 = logger.getLevel();
 115             if (l2 != level) {
 116                 if (l2 == null || l2.intValue() != level.intValue()) {
 117                     throw new RuntimeException("Unexpected level " + l2
 118                             + (l2 == null ? "" : (" " + l2.getClass()))
 119                             + " for " + level.intValue());
 120                 }
 121             }
 122         }
 123 
 124 
 125         final long otherLevelCount = levels.stream()
 126             .filter(CustomLevel::isCustomLoader)
 127             .count();
 128 
 129         // Now verify that custom level instances are correctly
 130         // garbage collected when no longer referenced
 131         ReferenceQueue<Level> queue = new ReferenceQueue<>();
 132         List<CustomLevelReference> refs = new ArrayList<>();
 133         List<CustomLevelReference> customRefs = new ArrayList<>();
 134         int otherLevels = 0;
 135         while (!levels.isEmpty()) {
 136             Level l = levels.stream().findAny().get();
 137             boolean isCustomLoader = isCustomLoader(l);
 138             if (isCustomLoader) otherLevels++;
 139 
 140             CustomLevelReference ref = new CustomLevelReference(l, queue);
 141             if (isCustomLoader) {
 142                 customRefs.add(ref);
 143             } else {
 144                 refs.add(ref);
 145             }
 146 
 147             // remove strong references to l
 148             levels.remove(l);
 149             l = null;
 150 
 151             // Run gc and wait for garbage collection
 152             if (otherLevels == otherLevelCount) {
 153                 if (customRefs.size() != otherLevelCount) {
 154                     throw new RuntimeException("Test bug: customRefs.size() != "
 155                              + otherLevelCount);
 156                 }
 157                 waitForGC(customRefs, queue);
 158             }
 159         }
 160         if (otherLevelCount != otherLevels || otherLevelCount == 0) {
 161             throw new RuntimeException("Test bug: "
 162                 + "no or wrong count of levels loaded from custom loader");
 163         }
 164         if (!customRefs.isEmpty()) {
 165             throw new RuntimeException(
 166                 "Test bug: customRefs.size() should be empty!");
 167         }
 168         while (!refs.isEmpty()) {
 169             final Reference<?> ref = refs.remove(0);
 170             if (ref.get() == null) {
 171                 throw new RuntimeException("Unexpected garbage collection for "
 172                            + ref);
 173             }
 174         }
 175     }
 176 
 177     private static void waitForGC(List<CustomLevelReference> customRefs,
 178                                   ReferenceQueue<Level> queue)
 179          throws InterruptedException
 180     {
 181         while (!customRefs.isEmpty()) {
 182             Reference<? extends Level> ref2;
 183             do {
 184                 System.gc();
 185                 Thread.sleep(100);
 186             } while ((ref2 = queue.poll()) == null);
 187 
 188             // Check garbage collected reference
 189             if (!customRefs.contains(ref2)) {
 190                throw new RuntimeException("Unexpected reference: " + ref2);
 191             }
 192             CustomLevelReference ref = customRefs.remove(customRefs.indexOf(ref2));
 193             System.out.println(ref2 + " garbage collected");
 194             final String name = ref.name;
 195             Level l;
 196             try {
 197                 l = Level.parse(name);
 198                 if (!name.equals("SEVERE")
 199                     && !name.equals("INFO")
 200                     || !name.equals(l.getName())) {
 201                     throw new RuntimeException("Unexpected level "
 202                             + formatLevel(l));
 203                 } else {
 204                     if (l == Level.WARNING || l == Level.INFO
 205                             || l == Level.SEVERE) {
 206                         System.out.println("Level.parse found expected level: "
 207                                 + formatLevel(l));
 208                     } else {
 209                         throw new RuntimeException("Unexpected level "
 210                             + formatLevel(l));
 211                     }
 212                 }
 213             } catch (IllegalArgumentException iae) {
 214                 if (!name.equals("WARNING")
 215                     && !name.equals("INFO")
 216                     && !name.equals("SEVERE")) {
 217                     System.out.println("Level.parse fired expected exception: "
 218                         + iae);
 219                 } else {
 220                     throw iae;
 221                 }
 222             }
 223         }
 224     }
 225 
 226     private static boolean isCustomLoader(Level level) {
 227         final ClassLoader cl = level.getClass().getClassLoader();
 228         return cl != null
 229              && cl != ClassLoader.getPlatformClassLoader()
 230              && cl != ClassLoader.getSystemClassLoader();
 231     }
 232 
 233     static ResourceBundle getResourceBundle(Level level) {
 234         return isCustomLoader(level)
 235             ? ResourceBundle.getBundle(OTHERRB_NAME, Locale.getDefault(),
 236                                        level.getClass().getClassLoader())
 237             : ResourceBundle.getBundle(RB_NAME);
 238     }
 239 
 240     private static void setupCustomLevels() throws IOException {
 241         levels.add(new CustomLevel("EMERGENCY", 1090, RB_NAME));
 242         levels.add(new CustomLevel("ALERT", 1060, RB_NAME));
 243         levels.add(new CustomLevel("CRITICAL", 1030, RB_NAME));
 244         levels.add(new CustomLevel("WARNING", 1010, RB_NAME));
 245         levels.add(new CustomLevel("INFO", 1000, RB_NAME));
 246     }
 247 
 248     static void setUpCustomLevelsOtherLoader()
 249          throws MalformedURLException,
 250                ClassNotFoundException, NoSuchMethodException,
 251                IllegalAccessException, InvocationTargetException
 252     {
 253         final String classes = System.getProperty("test.classes",
 254                                                   "build/classes");
 255         final String sources = System.getProperty("test.src",
 256                                                   "src");
 257         final URL curl = new File(classes).toURI().toURL();
 258         final URL surl = new File(sources).toURI().toURL();
 259         URLClassLoader loader = new URLClassLoader(new URL[] {curl, surl},
 260                                      ClassLoader.getPlatformClassLoader());
 261         Class<?> customLevelClass = Class.forName("CustomLevel", false, loader);
 262         Method m = customLevelClass.getMethod("setUpCustomLevelsOtherLoader",
 263                                               List.class);
 264         m.invoke(null, levels);
 265     }
 266 
 267     public static void setUpCustomLevelsOtherLoader(List<Level> levels) {
 268         levels.add(new CustomLevel("OTHEREMERGENCY", 1091, OTHERRB_NAME));
 269         levels.add(new CustomLevel("OTHERALERT", 1061, OTHERRB_NAME));
 270         levels.add(new CustomLevel("OTHERCRITICAL", 1031, OTHERRB_NAME));
 271         levels.add(new CustomLevel("SEVERE", 1011, OTHERRB_NAME));
 272         levels.add(new CustomLevel("INFO", 1000, OTHERRB_NAME));
 273     }
 274 
 275     static void checkCustomLevel(Level level, Level expected) {
 276         // Level value must be the same
 277         if (!level.equals(expected)) {
 278             throw new RuntimeException(formatLevel(level) + " != "
 279                  + formatLevel(expected));
 280         }
 281 
 282         if (!level.getName().equals(expected.getName())) {
 283             throw new RuntimeException(formatLevel(level) + " != "
 284                  + formatLevel(expected));
 285         }
 286 
 287         // Level.parse is expected to return the custom Level
 288         if (level != expected) {
 289             throw new RuntimeException(formatLevel(level) + " != "
 290                  + formatLevel(expected));
 291         }
 292 
 293         final ResourceBundle rb = getResourceBundle(level);
 294         String name = rb.getString(level.getName());
 295         if (!level.getLocalizedName().equals(name)) {
 296             // must have the same localized name
 297             throw new RuntimeException(level.getLocalizedName() + " != " + name);
 298         }
 299     }
 300 
 301     static String formatLevel(Level l) {
 302         return l + ":" + l.intValue() + ":" + l.getClass().getName();
 303     }
 304 }