Level.java revision 1cfadde2152806a52c5c7e68d0d028160d89edc7
1/*
2 * Copyright (c) 2000, 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.  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.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
66public 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     * <p>
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        // Android-change: Use Thread.currentThread().getContextClassLoader(),
266        // otherwise we might get a BootClassLoader.
267        ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName, newLocale,
268                                                     Thread.currentThread().getContextClassLoader());
269        final String localizedName = rb.getString(name);
270
271        final boolean isDefaultBundle = defaultBundle.equals(resourceBundleName);
272        if (!isDefaultBundle) return localizedName;
273
274        // This is a trick to determine whether the name has been translated
275        // or not. If it has not been translated, we need to use Locale.ROOT
276        // when calling toUpperCase().
277        final Locale rbLocale = rb.getLocale();
278        final Locale locale =
279                Locale.ROOT.equals(rbLocale)
280                || name.equals(localizedName.toUpperCase(Locale.ROOT))
281                ? Locale.ROOT : rbLocale;
282
283        // ALL CAPS in a resource bundle's message indicates no translation
284        // needed per Oracle translation guideline.  To workaround this
285        // in Oracle JDK implementation, convert the localized level name
286        // to uppercase for compatibility reason.
287        return Locale.ROOT.equals(locale) ? name : localizedName.toUpperCase(locale);
288    }
289
290    // Avoid looking up the localizedLevelName twice if we already
291    // have it.
292    final String getCachedLocalizedLevelName() {
293
294        if (localizedLevelName != null) {
295            if (cachedLocale != null) {
296                if (cachedLocale.equals(Locale.getDefault())) {
297                    // OK: our cached value was looked up with the same
298                    //     locale. We can use it.
299                    return localizedLevelName;
300                }
301            }
302        }
303
304        if (resourceBundleName == null) {
305            // No resource bundle: just use the name.
306            return name;
307        }
308
309        // We need to compute the localized name.
310        // Either because it's the first time, or because our cached
311        // value is for a different locale. Just return null.
312        return null;
313    }
314
315    final synchronized String getLocalizedLevelName() {
316
317        // See if we have a cached localized name
318        final String cachedLocalizedName = getCachedLocalizedLevelName();
319        if (cachedLocalizedName != null) {
320            return cachedLocalizedName;
321        }
322
323        // No cached localized name or cache invalid.
324        // Need to compute the localized name.
325        final Locale newLocale = Locale.getDefault();
326        try {
327            localizedLevelName = computeLocalizedLevelName(newLocale);
328        } catch (Exception ex) {
329            localizedLevelName = name;
330        }
331        cachedLocale = newLocale;
332        return localizedLevelName;
333    }
334
335    // Returns a mirrored Level object that matches the given name as
336    // specified in the Level.parse method.  Returns null if not found.
337    //
338    // It returns the same Level object as the one returned by Level.parse
339    // method if the given name is a non-localized name or integer.
340    //
341    // If the name is a localized name, findLevel and parse method may
342    // return a different level value if there is a custom Level subclass
343    // that overrides Level.getLocalizedName() to return a different string
344    // than what's returned by the default implementation.
345    //
346    static Level findLevel(String name) {
347        if (name == null) {
348            throw new NullPointerException();
349        }
350
351        KnownLevel level;
352
353        // Look for a known Level with the given non-localized name.
354        level = KnownLevel.findByName(name);
355        if (level != null) {
356            return level.mirroredLevel;
357        }
358
359        // Now, check if the given name is an integer.  If so,
360        // first look for a Level with the given value and then
361        // if necessary create one.
362        try {
363            int x = Integer.parseInt(name);
364            level = KnownLevel.findByValue(x);
365            if (level == null) {
366                // add new Level
367                Level levelObject = new Level(name, x);
368                level = KnownLevel.findByValue(x);
369            }
370            return level.mirroredLevel;
371        } catch (NumberFormatException ex) {
372            // Not an integer.
373            // Drop through.
374        }
375
376        level = KnownLevel.findByLocalizedLevelName(name);
377        if (level != null) {
378            return level.mirroredLevel;
379        }
380
381        return null;
382    }
383
384    /**
385     * Returns a string representation of this Level.
386     *
387     * @return the non-localized name of the Level, for example "INFO".
388     */
389    @Override
390    public final String toString() {
391        return name;
392    }
393
394    /**
395     * Get the integer value for this level.  This integer value
396     * can be used for efficient ordering comparisons between
397     * Level objects.
398     * @return the integer value for this level.
399     */
400    public final int intValue() {
401        return value;
402    }
403
404    private static final long serialVersionUID = -8176160795706313070L;
405
406    // Serialization magic to prevent "doppelgangers".
407    // This is a performance optimization.
408    private Object readResolve() {
409        KnownLevel o = KnownLevel.matches(this);
410        if (o != null) {
411            return o.levelObject;
412        }
413
414        // Woops.  Whoever sent us this object knows
415        // about a new log level.  Add it to our list.
416        Level level = new Level(this.name, this.value, this.resourceBundleName);
417        return level;
418    }
419
420    /**
421     * Parse a level name string into a Level.
422     * <p>
423     * The argument string may consist of either a level name
424     * or an integer value.
425     * <p>
426     * For example:
427     * <ul>
428     * <li>     "SEVERE"
429     * <li>     "1000"
430     * </ul>
431     *
432     * @param  name   string to be parsed
433     * @throws NullPointerException if the name is null
434     * @throws IllegalArgumentException if the value is not valid.
435     * Valid values are integers between <CODE>Integer.MIN_VALUE</CODE>
436     * and <CODE>Integer.MAX_VALUE</CODE>, and all known level names.
437     * Known names are the levels defined by this class (e.g., <CODE>FINE</CODE>,
438     * <CODE>FINER</CODE>, <CODE>FINEST</CODE>), or created by this class with
439     * appropriate package access, or new levels defined or created
440     * by subclasses.
441     *
442     * @return The parsed value. Passing an integer that corresponds to a known name
443     * (e.g., 700) will return the associated name (e.g., <CODE>CONFIG</CODE>).
444     * Passing an integer that does not (e.g., 1) will return a new level name
445     * initialized to that value.
446     */
447    public static synchronized Level parse(String name) throws IllegalArgumentException {
448        // Check that name is not null.
449        name.length();
450
451        KnownLevel level;
452
453        // Look for a known Level with the given non-localized name.
454        level = KnownLevel.findByName(name);
455        if (level != null) {
456            return level.levelObject;
457        }
458
459        // Now, check if the given name is an integer.  If so,
460        // first look for a Level with the given value and then
461        // if necessary create one.
462        try {
463            int x = Integer.parseInt(name);
464            level = KnownLevel.findByValue(x);
465            if (level == null) {
466                // add new Level
467                Level levelObject = new Level(name, x);
468                level = KnownLevel.findByValue(x);
469            }
470            return level.levelObject;
471        } catch (NumberFormatException ex) {
472            // Not an integer.
473            // Drop through.
474        }
475
476        // Finally, look for a known level with the given localized name,
477        // in the current default locale.
478        // This is relatively expensive, but not excessively so.
479        level = KnownLevel.findByLocalizedLevelName(name);
480        if (level != null) {
481            return level.levelObject;
482        }
483
484        // OK, we've tried everything and failed
485        throw new IllegalArgumentException("Bad level \"" + name + "\"");
486    }
487
488    /**
489     * Compare two objects for value equality.
490     * @return true if and only if the two objects have the same level value.
491     */
492    @Override
493    public boolean equals(Object ox) {
494        try {
495            Level lx = (Level)ox;
496            return (lx.value == this.value);
497        } catch (Exception ex) {
498            return false;
499        }
500    }
501
502    /**
503     * Generate a hashcode.
504     * @return a hashcode based on the level value
505     */
506    @Override
507    public int hashCode() {
508        return this.value;
509    }
510
511    // KnownLevel class maintains the global list of all known levels.
512    // The API allows multiple custom Level instances of the same name/value
513    // be created. This class provides convenient methods to find a level
514    // by a given name, by a given value, or by a given localized name.
515    //
516    // KnownLevel wraps the following Level objects:
517    // 1. levelObject:   standard Level object or custom Level object
518    // 2. mirroredLevel: Level object representing the level specified in the
519    //                   logging configuration.
520    //
521    // Level.getName, Level.getLocalizedName, Level.getResourceBundleName methods
522    // are non-final but the name and resource bundle name are parameters to
523    // the Level constructor.  Use the mirroredLevel object instead of the
524    // levelObject to prevent the logging framework to execute foreign code
525    // implemented by untrusted Level subclass.
526    //
527    // Implementation Notes:
528    // If Level.getName, Level.getLocalizedName, Level.getResourceBundleName methods
529    // were final, the following KnownLevel implementation can be removed.
530    // Future API change should take this into consideration.
531    static final class KnownLevel {
532        private static Map<String, List<KnownLevel>> nameToLevels = new HashMap<>();
533        private static Map<Integer, List<KnownLevel>> intToLevels = new HashMap<>();
534        final Level levelObject;     // instance of Level class or Level subclass
535        final Level mirroredLevel;   // mirror of the custom Level
536        KnownLevel(Level l) {
537            this.levelObject = l;
538            if (l.getClass() == Level.class) {
539                this.mirroredLevel = l;
540            } else {
541                // this mirrored level object is hidden
542                this.mirroredLevel = new Level(l.name, l.value, l.resourceBundleName, false);
543            }
544        }
545
546        static synchronized void add(Level l) {
547            // the mirroredLevel object is always added to the list
548            // before the custom Level instance
549            KnownLevel o = new KnownLevel(l);
550            List<KnownLevel> list = nameToLevels.get(l.name);
551            if (list == null) {
552                list = new ArrayList<>();
553                nameToLevels.put(l.name, list);
554            }
555            list.add(o);
556
557            list = intToLevels.get(l.value);
558            if (list == null) {
559                list = new ArrayList<>();
560                intToLevels.put(l.value, list);
561            }
562            list.add(o);
563        }
564
565        // Returns a KnownLevel with the given non-localized name.
566        static synchronized KnownLevel findByName(String name) {
567            List<KnownLevel> list = nameToLevels.get(name);
568            if (list != null) {
569                return list.get(0);
570            }
571            return null;
572        }
573
574        // Returns a KnownLevel with the given value.
575        static synchronized KnownLevel findByValue(int value) {
576            List<KnownLevel> list = intToLevels.get(value);
577            if (list != null) {
578                return list.get(0);
579            }
580            return null;
581        }
582
583        // Returns a KnownLevel with the given localized name matching
584        // by calling the Level.getLocalizedLevelName() method (i.e. found
585        // from the resourceBundle associated with the Level object).
586        // This method does not call Level.getLocalizedName() that may
587        // be overridden in a subclass implementation
588        static synchronized KnownLevel findByLocalizedLevelName(String name) {
589            for (List<KnownLevel> levels : nameToLevels.values()) {
590                for (KnownLevel l : levels) {
591                    String lname = l.levelObject.getLocalizedLevelName();
592                    if (name.equals(lname)) {
593                        return l;
594                    }
595                }
596            }
597            return null;
598        }
599
600        static synchronized KnownLevel matches(Level l) {
601            List<KnownLevel> list = nameToLevels.get(l.name);
602            if (list != null) {
603                for (KnownLevel level : list) {
604                    Level other = level.mirroredLevel;
605                    Class<? extends Level> type = level.levelObject.getClass();
606                    if (l.value == other.value &&
607                           (l.resourceBundleName == other.resourceBundleName ||
608                               (l.resourceBundleName != null &&
609                                l.resourceBundleName.equals(other.resourceBundleName)))) {
610                        if (type == l.getClass()) {
611                            return level;
612                        }
613                    }
614                }
615            }
616            return null;
617        }
618    }
619
620}
621