Level.java revision ed0a7b5290c302f8c58fdb3fb4ed8df5e0d32bd4
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
26package java.util.logging;
27import java.util.ArrayList;
28import java.util.HashMap;
29import java.util.List;
30import java.util.Locale;
31import java.util.Map;
32import java.util.MissingResourceException;
33import java.util.ResourceBundle;
34
35import 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
69public 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