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