--- a/jdk/src/share/classes/java/util/logging/Level.java Mon Nov 26 20:49:54 2012 +0400
+++ b/jdk/src/share/classes/java/util/logging/Level.java Mon Nov 26 22:49:06 2012 -0800
@@ -24,6 +24,10 @@
*/
package java.util.logging;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.ResourceBundle;
/**
@@ -59,7 +63,6 @@
*/
public class Level implements java.io.Serializable {
- private static java.util.ArrayList<Level> known = new java.util.ArrayList<>();
private static String defaultBundle = "sun.util.logging.resources.logging";
/**
@@ -77,6 +80,9 @@
*/
private final String resourceBundleName;
+ // localized level name
+ private String localizedLevelName;
+
/**
* OFF is a special level that can be used to turn off logging.
* This level is initialized to <CODE>Integer.MAX_VALUE</CODE>.
@@ -202,9 +208,8 @@
this.name = name;
this.value = value;
this.resourceBundleName = resourceBundleName;
- synchronized (Level.class) {
- known.add(this);
- }
+ this.localizedLevelName = resourceBundleName == null ? name : null;
+ KnownLevel.add(this);
}
/**
@@ -236,12 +241,76 @@
* @return localized name
*/
public String getLocalizedName() {
+ return getLocalizedLevelName();
+ }
+
+ // package-private getLevelName() is used by the implementation
+ // instead of getName() to avoid calling the subclass's version
+ final String getLevelName() {
+ return this.name;
+ }
+
+ final synchronized String getLocalizedLevelName() {
+ if (localizedLevelName != null) {
+ return localizedLevelName;
+ }
+
try {
ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName);
- return rb.getString(name);
+ localizedLevelName = rb.getString(name);
} catch (Exception ex) {
- return name;
+ localizedLevelName = name;
+ }
+ return localizedLevelName;
+ }
+
+ // Returns a mirrored Level object that matches the given name as
+ // specified in the Level.parse method. Returns null if not found.
+ //
+ // It returns the same Level object as the one returned by Level.parse
+ // method if the given name is a non-localized name or integer.
+ //
+ // If the name is a localized name, findLevel and parse method may
+ // return a different level value if there is a custom Level subclass
+ // that overrides Level.getLocalizedName() to return a different string
+ // than what's returned by the default implementation.
+ //
+ static Level findLevel(String name) {
+ if (name == null) {
+ throw new NullPointerException();
}
+
+ KnownLevel level;
+
+ // Look for a known Level with the given non-localized name.
+ level = KnownLevel.findByName(name);
+ if (level != null) {
+ return level.mirroredLevel;
+ }
+
+ // Now, check if the given name is an integer. If so,
+ // first look for a Level with the given value and then
+ // if necessary create one.
+ try {
+ int x = Integer.parseInt(name);
+ level = KnownLevel.findByValue(x);
+ if (level == null) {
+ // add new Level
+ Level levelObject = new Level(name, x);
+ level = KnownLevel.findByValue(x);
+ }
+ return level.mirroredLevel;
+ } catch (NumberFormatException ex) {
+ // Not an integer.
+ // Drop through.
+ }
+
+ level = KnownLevel.findByLocalizedLevelName(name);
+ if (level != null) {
+ return level.mirroredLevel;
+ }
+
+ return null;
}
/**
@@ -268,21 +337,15 @@
// Serialization magic to prevent "doppelgangers".
// This is a performance optimization.
private Object readResolve() {
- synchronized (Level.class) {
- for (int i = 0; i < known.size(); i++) {
- Level other = known.get(i);
- if (this.name.equals(other.name) && this.value == other.value
- && (this.resourceBundleName == other.resourceBundleName ||
- (this.resourceBundleName != null &&
- this.resourceBundleName.equals(other.resourceBundleName)))) {
- return other;
- }
- }
- // Woops. Whoever sent us this object knows
- // about a new log level. Add it to our list.
- known.add(this);
- return this;
+ KnownLevel o = KnownLevel.matches(this);
+ if (o != null) {
+ return o.levelObject;
}
+
+ // Woops. Whoever sent us this object knows
+ // about a new log level. Add it to our list.
+ Level level = new Level(this.name, this.value, this.resourceBundleName);
+ return level;
}
/**
@@ -296,6 +359,7 @@
* <li> "SEVERE"
* <li> "1000"
* </ul>
+ *
* @param name string to be parsed
* @throws NullPointerException if the name is null
* @throws IllegalArgumentException if the value is not valid.
@@ -315,12 +379,12 @@
// Check that name is not null.
name.length();
+ KnownLevel level;
+
// Look for a known Level with the given non-localized name.
- for (int i = 0; i < known.size(); i++) {
- Level l = known.get(i);
- if (name.equals(l.name)) {
- return l;
- }
+ level = KnownLevel.findByName(name);
+ if (level != null) {
+ return level.levelObject;
}
// Now, check if the given name is an integer. If so,
@@ -328,14 +392,13 @@
// if necessary create one.
try {
int x = Integer.parseInt(name);
- for (int i = 0; i < known.size(); i++) {
- Level l = known.get(i);
- if (l.value == x) {
- return l;
- }
+ level = KnownLevel.findByValue(x);
+ if (level == null) {
+ // add new Level
+ Level levelObject = new Level(name, x);
+ level = KnownLevel.findByValue(x);
}
- // Create a new Level.
- return new Level(name, x);
+ return level.levelObject;
} catch (NumberFormatException ex) {
// Not an integer.
// Drop through.
@@ -344,11 +407,9 @@
// Finally, look for a known level with the given localized name,
// in the current default locale.
// This is relatively expensive, but not excessively so.
- for (int i = 0; i < known.size(); i++) {
- Level l = known.get(i);
- if (name.equals(l.getLocalizedName())) {
- return l;
- }
+ level = KnownLevel.findByLocalizedName(name);
+ if (level != null) {
+ return level.levelObject;
}
// OK, we've tried everything and failed
@@ -375,4 +436,124 @@
public int hashCode() {
return this.value;
}
+
+ // KnownLevel class maintains the global list of all known levels.
+ // The API allows multiple custom Level instances of the same name/value
+ // be created. This class provides convenient methods to find a level
+ // by a given name, by a given value, or by a given localized name.
+ //
+ // KnownLevel wraps the following Level objects:
+ // 1. levelObject: standard Level object or custom Level object
+ // 2. mirroredLevel: Level object representing the level specified in the
+ // logging configuration.
+ //
+ // Level.getName, Level.getLocalizedName, Level.getResourceBundleName methods
+ // are non-final but the name and resource bundle name are parameters to
+ // the Level constructor. Use the mirroredLevel object instead of the
+ // levelObject to prevent the logging framework to execute foreign code
+ // implemented by untrusted Level subclass.
+ //
+ // Implementation Notes:
+ // If Level.getName, Level.getLocalizedName, Level.getResourceBundleName methods
+ // were final, the following KnownLevel implementation can be removed.
+ // Future API change should take this into consideration.
+ static final class KnownLevel {
+ private static Map<String, List<KnownLevel>> nameToLevels = new HashMap<>();
+ private static Map<Integer, List<KnownLevel>> intToLevels = new HashMap<>();
+ final Level levelObject; // instance of Level class or Level subclass
+ final Level mirroredLevel; // instance of Level class
+ KnownLevel(Level l) {
+ this.levelObject = l;
+ if (l.getClass() == Level.class) {
+ this.mirroredLevel = l;
+ } else {
+ this.mirroredLevel = new Level(l.name, l.value, l.resourceBundleName);
+ }
+ }
+
+ static synchronized void add(Level l) {
+ // the mirroredLevel object is always added to the list
+ // before the custom Level instance
+ KnownLevel o = new KnownLevel(l);
+ List<KnownLevel> list = nameToLevels.get(l.name);
+ if (list == null) {
+ list = new ArrayList<>();
+ nameToLevels.put(l.name, list);
+ }
+ list.add(o);
+
+ list = intToLevels.get(l.value);
+ if (list == null) {
+ list = new ArrayList<>();
+ intToLevels.put(l.value, list);
+ }
+ list.add(o);
+ }
+
+ // Returns a KnownLevel with the given non-localized name.
+ static synchronized KnownLevel findByName(String name) {
+ List<KnownLevel> list = nameToLevels.get(name);
+ if (list != null) {
+ return list.get(0);
+ }
+ return null;
+ }
+
+ // Returns a KnownLevel with the given value.
+ static synchronized KnownLevel findByValue(int value) {
+ List<KnownLevel> list = intToLevels.get(value);
+ if (list != null) {
+ return list.get(0);
+ }
+ return null;
+ }
+
+ // Returns a KnownLevel with the given localized name matching
+ // by calling the Level.getLocalizedLevelName() method (i.e. found
+ // from the resourceBundle associated with the Level object).
+ // This method does not call Level.getLocalizedName() that may
+ // be overridden in a subclass implementation
+ static synchronized KnownLevel findByLocalizedLevelName(String name) {
+ for (List<KnownLevel> levels : nameToLevels.values()) {
+ for (KnownLevel l : levels) {
+ String lname = l.levelObject.getLocalizedLevelName();
+ if (name.equals(lname)) {
+ return l;
+ }
+ }
+ }
+ return null;
+ }
+
+ // Returns a KnownLevel with the given localized name matching
+ // by calling the Level.getLocalizedName() method
+ static synchronized KnownLevel findByLocalizedName(String name) {
+ for (List<KnownLevel> levels : nameToLevels.values()) {
+ for (KnownLevel l : levels) {
+ String lname = l.levelObject.getLocalizedName();
+ if (name.equals(lname)) {
+ return l;
+ }
+ }
+ }
+ return null;
+ }
+
+ static synchronized KnownLevel matches(Level l) {
+ List<KnownLevel> list = nameToLevels.get(l.name);
+ if (list != null) {
+ for (KnownLevel level : list) {
+ Level other = level.mirroredLevel;
+ if (l.value == other.value &&
+ (l.resourceBundleName == other.resourceBundleName ||
+ (l.resourceBundleName != null &&
+ l.resourceBundleName.equals(other.resourceBundleName)))) {
+ return level;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
}