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