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