1 /*
   2  * Copyright (c) 2000, 2013, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util.logging;
  27 import java.util.ArrayList;
  28 import java.util.HashMap;
  29 import java.util.List;
  30 import java.util.Locale;
  31 import java.util.Map;
  32 import java.util.ResourceBundle;
  33 
  34 /**
  35  * The Level class defines a set of standard logging levels that
  36  * can be used to control logging output.  The logging Level objects
  37  * are ordered and are specified by ordered integers.  Enabling logging
  38  * at a given level also enables logging at all higher levels.
  39  * <p>
  40  * Clients should normally use the predefined Level constants such
  41  * as Level.SEVERE.
  42  * <p>
  43  * The levels in descending order are:
  44  * <ul>
  45  * <li>SEVERE (highest value)
  46  * <li>WARNING
  47  * <li>INFO
  48  * <li>CONFIG
  49  * <li>FINE
  50  * <li>FINER
  51  * <li>FINEST  (lowest value)
  52  * </ul>
  53  * In addition there is a level OFF that can be used to turn
  54  * off logging, and a level ALL that can be used to enable
  55  * logging of all messages.
  56  * <p>
  57  * It is possible for third parties to define additional logging
  58  * levels by subclassing Level.  In such cases subclasses should
  59  * take care to chose unique integer level values and to ensure that
  60  * they maintain the Object uniqueness property across serialization
  61  * by defining a suitable readResolve method.
  62  *
  63  * @since 1.4
  64  */
  65 
  66 public class Level implements java.io.Serializable {
  67     private static final String defaultBundle = "sun.util.logging.resources.logging";
  68 
  69     /**
  70      * @serial  The non-localized name of the level.
  71      */
  72     private final String name;
  73 
  74     /**
  75      * @serial  The integer value of the level.
  76      */
  77     private final int value;
  78 
  79     /**
  80      * @serial The resource bundle name to be used in localizing the level name.
  81      */
  82     private final String resourceBundleName;
  83 
  84     // localized level name
  85     private transient String localizedLevelName;
  86     private transient Locale cachedLocale;
  87 
  88     /**
  89      * OFF is a special level that can be used to turn off logging.
  90      * This level is initialized to <CODE>Integer.MAX_VALUE</CODE>.
  91      */
  92     public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);
  93 
  94     /**
  95      * SEVERE is a message level indicating a serious failure.
  96      * <p>
  97      * In general SEVERE messages should describe events that are
  98      * of considerable importance and which will prevent normal
  99      * program execution.   They should be reasonably intelligible
 100      * to end users and to system administrators.
 101      * This level is initialized to <CODE>1000</CODE>.
 102      */
 103     public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);
 104 
 105     /**
 106      * WARNING is a message level indicating a potential problem.
 107      * <p>
 108      * In general WARNING messages should describe events that will
 109      * be of interest to end users or system managers, or which
 110      * indicate potential problems.
 111      * This level is initialized to <CODE>900</CODE>.
 112      */
 113     public static final Level WARNING = new Level("WARNING", 900, defaultBundle);
 114 
 115     /**
 116      * INFO is a message level for informational messages.
 117      * <p>
 118      * Typically INFO messages will be written to the console
 119      * or its equivalent.  So the INFO level should only be
 120      * used for reasonably significant messages that will
 121      * make sense to end users and system administrators.
 122      * This level is initialized to <CODE>800</CODE>.
 123      */
 124     public static final Level INFO = new Level("INFO", 800, defaultBundle);
 125 
 126     /**
 127      * CONFIG is a message level for static configuration messages.
 128      * <p>
 129      * CONFIG messages are intended to provide a variety of static
 130      * configuration information, to assist in debugging problems
 131      * that may be associated with particular configurations.
 132      * For example, CONFIG message might include the CPU type,
 133      * the graphics depth, the GUI look-and-feel, etc.
 134      * This level is initialized to <CODE>700</CODE>.
 135      */
 136     public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);
 137 
 138     /**
 139      * FINE is a message level providing tracing information.
 140      * <p>
 141      * All of FINE, FINER, and FINEST are intended for relatively
 142      * detailed tracing.  The exact meaning of the three levels will
 143      * vary between subsystems, but in general, FINEST should be used
 144      * for the most voluminous detailed output, FINER for somewhat
 145      * less detailed output, and FINE for the  lowest volume (and
 146      * most important) messages.
 147      * <p>
 148      * In general the FINE level should be used for information
 149      * that will be broadly interesting to developers who do not have
 150      * a specialized interest in the specific subsystem.
 151      * <p>
 152      * FINE messages might include things like minor (recoverable)
 153      * failures.  Issues indicating potential performance problems
 154      * are also worth logging as FINE.
 155      * This level is initialized to <CODE>500</CODE>.
 156      */
 157     public static final Level FINE = new Level("FINE", 500, defaultBundle);
 158 
 159     /**
 160      * FINER indicates a fairly detailed tracing message.
 161      * By default logging calls for entering, returning, or throwing
 162      * an exception are traced at this level.
 163      * This level is initialized to <CODE>400</CODE>.
 164      */
 165     public static final Level FINER = new Level("FINER", 400, defaultBundle);
 166 
 167     /**
 168      * FINEST indicates a highly detailed tracing message.
 169      * This level is initialized to <CODE>300</CODE>.
 170      */
 171     public static final Level FINEST = new Level("FINEST", 300, defaultBundle);
 172 
 173     /**
 174      * ALL indicates that all messages should be logged.
 175      * This level is initialized to <CODE>Integer.MIN_VALUE</CODE>.
 176      */
 177     public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);
 178 
 179     /**
 180      * Create a named Level with a given integer value.
 181      * <p>
 182      * Note that this constructor is "protected" to allow subclassing.
 183      * In general clients of logging should use one of the constant Level
 184      * objects such as SEVERE or FINEST.  However, if clients need to
 185      * add new logging levels, they may subclass Level and define new
 186      * constants.
 187      * @param name  the name of the Level, for example "SEVERE".
 188      * @param value an integer value for the level.
 189      * @throws NullPointerException if the name is null
 190      */
 191     protected Level(String name, int value) {
 192         this(name, value, null);
 193     }
 194 
 195     /**
 196      * Create a named Level with a given integer value and a
 197      * given localization resource name.
 198      *
 199      * @param name  the name of the Level, for example "SEVERE".
 200      * @param value an integer value for the level.
 201      * @param resourceBundleName name of a resource bundle to use in
 202      *    localizing the given name. If the resourceBundleName is null
 203      *    or an empty string, it is ignored.
 204      * @throws NullPointerException if the name is null
 205      */
 206     protected Level(String name, int value, String resourceBundleName) {
 207         this(name, value, resourceBundleName, true);
 208     }
 209 
 210     // private constructor to specify whether this instance should be added
 211     // to the KnownLevel list from which Level.parse method does its look up
 212     private Level(String name, int value, String resourceBundleName, boolean visible) {
 213         if (name == null) {
 214             throw new NullPointerException();
 215         }
 216         this.name = name;
 217         this.value = value;
 218         this.resourceBundleName = resourceBundleName;
 219         this.localizedLevelName = resourceBundleName == null ? name : null;
 220         this.cachedLocale = null;
 221         if (visible) {
 222             KnownLevel.add(this);
 223         }
 224     }
 225 
 226     /**
 227      * Return the level's localization resource bundle name, or
 228      * null if no localization bundle is defined.
 229      *
 230      * @return localization resource bundle name
 231      */
 232     public String getResourceBundleName() {
 233         return resourceBundleName;
 234     }
 235 
 236     /**
 237      * Return the non-localized string name of the Level.
 238      *
 239      * @return non-localized name
 240      */
 241     public String getName() {
 242         return name;
 243     }
 244 
 245     /**
 246      * Return the localized string name of the Level, for
 247      * the current default locale.
 248      * <p>
 249      * If no localization information is available, the
 250      * non-localized name is returned.
 251      *
 252      * @return localized name
 253      */
 254     public String getLocalizedName() {
 255         return getLocalizedLevelName();
 256     }
 257 
 258     // package-private getLevelName() is used by the implementation
 259     // instead of getName() to avoid calling the subclass's version
 260     final String getLevelName() {
 261         return this.name;
 262     }
 263 
 264     private String computeLocalizedLevelName(Locale newLocale) {
 265         ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName, newLocale);
 266         final String localizedName = rb.getString(name);
 267 
 268         final boolean isDefaultBundle = defaultBundle.equals(resourceBundleName);
 269         if (!isDefaultBundle) return localizedName;
 270 
 271         // This is a trick to determine whether the name has been translated
 272         // or not. If it has not been translated, we need to use Locale.ROOT
 273         // when calling toUpperCase().
 274         final Locale rbLocale = rb.getLocale();
 275         final Locale locale =
 276                 Locale.ROOT.equals(rbLocale)
 277                 || name.equals(localizedName.toUpperCase(Locale.ROOT))
 278                 ? Locale.ROOT : rbLocale;
 279 
 280         // ALL CAPS in a resource bundle's message indicates no translation
 281         // needed per Oracle translation guideline.  To workaround this
 282         // in Oracle JDK implementation, convert the localized level name
 283         // to uppercase for compatibility reason.
 284         return Locale.ROOT.equals(locale) ? name : localizedName.toUpperCase(locale);
 285     }
 286 
 287     // Avoid looking up the localizedLevelName twice if we already
 288     // have it.
 289     final String getCachedLocalizedLevelName() {
 290 
 291         if (localizedLevelName != null) {
 292             if (cachedLocale != null) {
 293                 if (cachedLocale.equals(Locale.getDefault())) {
 294                     // OK: our cached value was looked up with the same
 295                     //     locale. We can use it.
 296                     return localizedLevelName;
 297                 }
 298             }
 299         }
 300 
 301         if (resourceBundleName == null) {
 302             // No resource bundle: just use the name.
 303             return name;
 304         }
 305 
 306         // We need to compute the localized name.
 307         // Either because it's the first time, or because our cached
 308         // value is for a different locale. Just return null.
 309         return null;
 310     }
 311 
 312     final synchronized String getLocalizedLevelName() {
 313 
 314         // See if we have a cached localized name
 315         final String cachedLocalizedName = getCachedLocalizedLevelName();
 316         if (cachedLocalizedName != null) {
 317             return cachedLocalizedName;
 318         }
 319 
 320         // No cached localized name or cache invalid.
 321         // Need to compute the localized name.
 322         final Locale newLocale = Locale.getDefault();
 323         try {
 324             localizedLevelName = computeLocalizedLevelName(newLocale);
 325         } catch (Exception ex) {
 326             localizedLevelName = name;
 327         }
 328         cachedLocale = newLocale;
 329         return localizedLevelName;
 330     }
 331 
 332     // Returns a mirrored Level object that matches the given name as
 333     // specified in the Level.parse method.  Returns null if not found.
 334     //
 335     // It returns the same Level object as the one returned by Level.parse
 336     // method if the given name is a non-localized name or integer.
 337     //
 338     // If the name is a localized name, findLevel and parse method may
 339     // return a different level value if there is a custom Level subclass
 340     // that overrides Level.getLocalizedName() to return a different string
 341     // than what's returned by the default implementation.
 342     //
 343     static Level findLevel(String name) {
 344         if (name == null) {
 345             throw new NullPointerException();
 346         }
 347 
 348         KnownLevel level;
 349 
 350         // Look for a known Level with the given non-localized name.
 351         level = KnownLevel.findByName(name);
 352         if (level != null) {
 353             return level.mirroredLevel;
 354         }
 355 
 356         // Now, check if the given name is an integer.  If so,
 357         // first look for a Level with the given value and then
 358         // if necessary create one.
 359         try {
 360             int x = Integer.parseInt(name);
 361             level = KnownLevel.findByValue(x);
 362             if (level == null) {
 363                 // add new Level
 364                 Level levelObject = new Level(name, x);
 365                 level = KnownLevel.findByValue(x);
 366             }
 367             return level.mirroredLevel;
 368         } catch (NumberFormatException ex) {
 369             // Not an integer.
 370             // Drop through.
 371         }
 372 
 373         level = KnownLevel.findByLocalizedLevelName(name);
 374         if (level != null) {
 375             return level.mirroredLevel;
 376         }
 377 
 378         return null;
 379     }
 380 
 381     /**
 382      * Returns a string representation of this Level.
 383      *
 384      * @return the non-localized name of the Level, for example "INFO".
 385      */
 386     @Override
 387     public final String toString() {
 388         return name;
 389     }
 390 
 391     /**
 392      * Get the integer value for this level.  This integer value
 393      * can be used for efficient ordering comparisons between
 394      * Level objects.
 395      * @return the integer value for this level.
 396      */
 397     public final int intValue() {
 398         return value;
 399     }
 400 
 401     private static final long serialVersionUID = -8176160795706313070L;
 402 
 403     // Serialization magic to prevent "doppelgangers".
 404     // This is a performance optimization.
 405     private Object readResolve() {
 406         KnownLevel o = KnownLevel.matches(this);
 407         if (o != null) {
 408             return o.levelObject;
 409         }
 410 
 411         // Woops.  Whoever sent us this object knows
 412         // about a new log level.  Add it to our list.
 413         Level level = new Level(this.name, this.value, this.resourceBundleName);
 414         return level;
 415     }
 416 
 417     /**
 418      * Parse a level name string into a Level.
 419      * <p>
 420      * The argument string may consist of either a level name
 421      * or an integer value.
 422      * <p>
 423      * For example:
 424      * <ul>
 425      * <li>     "SEVERE"
 426      * <li>     "1000"
 427      * </ul>
 428      *
 429      * @param  name   string to be parsed
 430      * @throws NullPointerException if the name is null
 431      * @throws IllegalArgumentException if the value is not valid.
 432      * Valid values are integers between <CODE>Integer.MIN_VALUE</CODE>
 433      * and <CODE>Integer.MAX_VALUE</CODE>, and all known level names.
 434      * Known names are the levels defined by this class (e.g., <CODE>FINE</CODE>,
 435      * <CODE>FINER</CODE>, <CODE>FINEST</CODE>), or created by this class with
 436      * appropriate package access, or new levels defined or created
 437      * by subclasses.
 438      *
 439      * @return The parsed value. Passing an integer that corresponds to a known name
 440      * (e.g., 700) will return the associated name (e.g., <CODE>CONFIG</CODE>).
 441      * Passing an integer that does not (e.g., 1) will return a new level name
 442      * initialized to that value.
 443      */
 444     public static synchronized Level parse(String name) throws IllegalArgumentException {
 445         // Check that name is not null.
 446         name.length();
 447 
 448         KnownLevel level;
 449 
 450         // Look for a known Level with the given non-localized name.
 451         level = KnownLevel.findByName(name);
 452         if (level != null) {
 453             return level.levelObject;
 454         }
 455 
 456         // Now, check if the given name is an integer.  If so,
 457         // first look for a Level with the given value and then
 458         // if necessary create one.
 459         try {
 460             int x = Integer.parseInt(name);
 461             level = KnownLevel.findByValue(x);
 462             if (level == null) {
 463                 // add new Level
 464                 Level levelObject = new Level(name, x);
 465                 level = KnownLevel.findByValue(x);
 466             }
 467             return level.levelObject;
 468         } catch (NumberFormatException ex) {
 469             // Not an integer.
 470             // Drop through.
 471         }
 472 
 473         // Finally, look for a known level with the given localized name,
 474         // in the current default locale.
 475         // This is relatively expensive, but not excessively so.
 476         level = KnownLevel.findByLocalizedLevelName(name);
 477         if (level != null) {
 478             return level.levelObject;
 479         }
 480 
 481         // OK, we've tried everything and failed
 482         throw new IllegalArgumentException("Bad level \"" + name + "\"");
 483     }
 484 
 485     /**
 486      * Compare two objects for value equality.
 487      * @return true if and only if the two objects have the same level value.
 488      */
 489     @Override
 490     public boolean equals(Object ox) {
 491         try {
 492             Level lx = (Level)ox;
 493             return (lx.value == this.value);
 494         } catch (Exception ex) {
 495             return false;
 496         }
 497     }
 498 
 499     /**
 500      * Generate a hashcode.
 501      * @return a hashcode based on the level value
 502      */
 503     @Override
 504     public int hashCode() {
 505         return this.value;
 506     }
 507 
 508     // KnownLevel class maintains the global list of all known levels.
 509     // The API allows multiple custom Level instances of the same name/value
 510     // be created. This class provides convenient methods to find a level
 511     // by a given name, by a given value, or by a given localized name.
 512     //
 513     // KnownLevel wraps the following Level objects:
 514     // 1. levelObject:   standard Level object or custom Level object
 515     // 2. mirroredLevel: Level object representing the level specified in the
 516     //                   logging configuration.
 517     //
 518     // Level.getName, Level.getLocalizedName, Level.getResourceBundleName methods
 519     // are non-final but the name and resource bundle name are parameters to
 520     // the Level constructor.  Use the mirroredLevel object instead of the
 521     // levelObject to prevent the logging framework to execute foreign code
 522     // implemented by untrusted Level subclass.
 523     //
 524     // Implementation Notes:
 525     // If Level.getName, Level.getLocalizedName, Level.getResourceBundleName methods
 526     // were final, the following KnownLevel implementation can be removed.
 527     // Future API change should take this into consideration.
 528     static final class KnownLevel {
 529         private static Map<String, List<KnownLevel>> nameToLevels = new HashMap<>();
 530         private static Map<Integer, List<KnownLevel>> intToLevels = new HashMap<>();
 531         final Level levelObject;     // instance of Level class or Level subclass
 532         final Level mirroredLevel;   // mirror of the custom Level
 533         KnownLevel(Level l) {
 534             this.levelObject = l;
 535             if (l.getClass() == Level.class) {
 536                 this.mirroredLevel = l;
 537             } else {
 538                 // this mirrored level object is hidden
 539                 this.mirroredLevel = new Level(l.name, l.value, l.resourceBundleName, false);
 540             }
 541         }
 542 
 543         static synchronized void add(Level l) {
 544             // the mirroredLevel object is always added to the list
 545             // before the custom Level instance
 546             KnownLevel o = new KnownLevel(l);
 547             List<KnownLevel> list = nameToLevels.get(l.name);
 548             if (list == null) {
 549                 list = new ArrayList<>();
 550                 nameToLevels.put(l.name, list);
 551             }
 552             list.add(o);
 553 
 554             list = intToLevels.get(l.value);
 555             if (list == null) {
 556                 list = new ArrayList<>();
 557                 intToLevels.put(l.value, list);
 558             }
 559             list.add(o);
 560         }
 561 
 562         // Returns a KnownLevel with the given non-localized name.
 563         static synchronized KnownLevel findByName(String name) {
 564             List<KnownLevel> list = nameToLevels.get(name);
 565             if (list != null) {
 566                 return list.get(0);
 567             }
 568             return null;
 569         }
 570 
 571         // Returns a KnownLevel with the given value.
 572         static synchronized KnownLevel findByValue(int value) {
 573             List<KnownLevel> list = intToLevels.get(value);
 574             if (list != null) {
 575                 return list.get(0);
 576             }
 577             return null;
 578         }
 579 
 580         // Returns a KnownLevel with the given localized name matching
 581         // by calling the Level.getLocalizedLevelName() method (i.e. found
 582         // from the resourceBundle associated with the Level object).
 583         // This method does not call Level.getLocalizedName() that may
 584         // be overridden in a subclass implementation
 585         static synchronized KnownLevel findByLocalizedLevelName(String name) {
 586             for (List<KnownLevel> levels : nameToLevels.values()) {
 587                 for (KnownLevel l : levels) {
 588                     String lname = l.levelObject.getLocalizedLevelName();
 589                     if (name.equals(lname)) {
 590                         return l;
 591                     }
 592                 }
 593             }
 594             return null;
 595         }
 596 
 597         static synchronized KnownLevel matches(Level l) {
 598             List<KnownLevel> list = nameToLevels.get(l.name);
 599             if (list != null) {
 600                 for (KnownLevel level : list) {
 601                     Level other = level.mirroredLevel;
 602                     if (l.value == other.value &&
 603                            (l.resourceBundleName == other.resourceBundleName ||
 604                                (l.resourceBundleName != null &&
 605                                 l.resourceBundleName.equals(other.resourceBundleName)))) {
 606                         return level;
 607                     }
 608                 }
 609             }
 610             return null;
 611         }
 612     }
 613 
 614 }