|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package java.util.logging; |
|
|
|
import java.lang.ref.Reference; |
|
import java.lang.ref.ReferenceQueue; |
|
import java.lang.ref.WeakReference; |
|
import java.security.AccessController; |
|
import java.security.PrivilegedAction; |
|
import java.util.ArrayList; |
|
import java.util.Collections; |
|
import java.util.HashMap; |
|
import java.util.List; |
|
import java.util.Locale; |
|
import java.util.Map; |
|
import java.util.Optional; |
|
import java.util.ResourceBundle; |
|
import java.util.function.Function; |
|
import jdk.internal.loader.ClassLoaderValue; |
|
import jdk.internal.misc.JavaUtilResourceBundleAccess; |
|
import jdk.internal.misc.SharedSecrets; |
|
|
|
/** |
|
* The Level class defines a set of standard logging levels that |
|
* can be used to control logging output. The logging Level objects |
|
* are ordered and are specified by ordered integers. Enabling logging |
|
* at a given level also enables logging at all higher levels. |
|
* <p> |
|
* Clients should normally use the predefined Level constants such |
|
* as Level.SEVERE. |
|
* <p> |
|
* The levels in descending order are: |
|
* <ul> |
|
* <li>SEVERE (highest value) |
|
* <li>WARNING |
|
* <li>INFO |
|
* <li>CONFIG |
|
* <li>FINE |
|
* <li>FINER |
|
* <li>FINEST (lowest value) |
|
* </ul> |
|
* In addition there is a level OFF that can be used to turn |
|
* off logging, and a level ALL that can be used to enable |
|
* logging of all messages. |
|
* <p> |
|
* It is possible for third parties to define additional logging |
|
* levels by subclassing Level. In such cases subclasses should |
|
* take care to chose unique integer level values and to ensure that |
|
* they maintain the Object uniqueness property across serialization |
|
* by defining a suitable readResolve method. |
|
* |
|
* @since 1.4 |
|
*/ |
|
|
|
public class Level implements java.io.Serializable { |
|
private static final String defaultBundle = |
|
"sun.util.logging.resources.logging"; |
|
|
|
// Calling SharedSecrets.getJavaUtilResourceBundleAccess() |
|
// forces the initialization of ResourceBundle.class, which |
|
|
|
private static final class RbAccess { |
|
static final JavaUtilResourceBundleAccess RB_ACCESS = |
|
SharedSecrets.getJavaUtilResourceBundleAccess(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private final String name; |
|
|
|
|
|
|
|
*/ |
|
private final int value; |
|
|
|
|
|
|
|
*/ |
|
private final String resourceBundleName; |
|
|
|
|
|
private transient String localizedLevelName; |
|
private transient Locale cachedLocale; |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level WARNING = new Level("WARNING", 900, defaultBundle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level INFO = new Level("INFO", 800, defaultBundle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level FINE = new Level("FINE", 500, defaultBundle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level FINER = new Level("FINER", 400, defaultBundle); |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level FINEST = new Level("FINEST", 300, defaultBundle); |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle); |
|
|
|
private static final Level[] standardLevels = { |
|
OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected Level(String name, int value) { |
|
this(name, value, null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected Level(String name, int value, String resourceBundleName) { |
|
this(name, value, resourceBundleName, true); |
|
} |
|
|
|
// private constructor to specify whether this instance should be added |
|
|
|
private Level(String name, int value, String resourceBundleName, boolean visible) { |
|
if (name == null) { |
|
throw new NullPointerException(); |
|
} |
|
this.name = name; |
|
this.value = value; |
|
this.resourceBundleName = resourceBundleName; |
|
this.localizedLevelName = resourceBundleName == null ? name : null; |
|
this.cachedLocale = null; |
|
if (visible) { |
|
KnownLevel.add(this); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getResourceBundleName() { |
|
return resourceBundleName; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getName() { |
|
return name; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getLocalizedName() { |
|
return getLocalizedLevelName(); |
|
} |
|
|
|
// package-private getLevelName() is used by the implementation |
|
|
|
final String getLevelName() { |
|
return this.name; |
|
} |
|
|
|
private String computeLocalizedLevelName(Locale newLocale) { |
|
// Resource bundle should be loaded from the defining module |
|
// or its defining class loader, if it's unnamed module, |
|
|
|
Module module = this.getClass().getModule(); |
|
ResourceBundle rb = RbAccess.RB_ACCESS.getBundle(resourceBundleName, |
|
newLocale, module); |
|
|
|
final String localizedName = rb.getString(name); |
|
final boolean isDefaultBundle = defaultBundle.equals(resourceBundleName); |
|
if (!isDefaultBundle) return localizedName; |
|
|
|
// This is a trick to determine whether the name has been translated |
|
// or not. If it has not been translated, we need to use Locale.ROOT |
|
|
|
final Locale rbLocale = rb.getLocale(); |
|
final Locale locale = |
|
Locale.ROOT.equals(rbLocale) |
|
|| name.equals(localizedName.toUpperCase(Locale.ROOT)) |
|
? Locale.ROOT : rbLocale; |
|
|
|
// ALL CAPS in a resource bundle's message indicates no translation |
|
// needed per Oracle translation guideline. To workaround this |
|
// in Oracle JDK implementation, convert the localized level name |
|
|
|
return Locale.ROOT.equals(locale) ? name : localizedName.toUpperCase(locale); |
|
} |
|
|
|
// Avoid looking up the localizedLevelName twice if we already |
|
|
|
final String getCachedLocalizedLevelName() { |
|
|
|
if (localizedLevelName != null) { |
|
if (cachedLocale != null) { |
|
if (cachedLocale.equals(Locale.getDefault())) { |
|
// OK: our cached value was looked up with the same |
|
|
|
return localizedLevelName; |
|
} |
|
} |
|
} |
|
|
|
if (resourceBundleName == null) { |
|
|
|
return name; |
|
} |
|
|
|
// We need to compute the localized name. |
|
// Either because it's the first time, or because our cached |
|
|
|
return null; |
|
} |
|
|
|
final synchronized String getLocalizedLevelName() { |
|
|
|
|
|
final String cachedLocalizedName = getCachedLocalizedLevelName(); |
|
if (cachedLocalizedName != null) { |
|
return cachedLocalizedName; |
|
} |
|
|
|
// No cached localized name or cache invalid. |
|
|
|
final Locale newLocale = Locale.getDefault(); |
|
try { |
|
localizedLevelName = computeLocalizedLevelName(newLocale); |
|
} catch (Exception ex) { |
|
localizedLevelName = name; |
|
} |
|
cachedLocale = newLocale; |
|
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(); |
|
} |
|
|
|
Optional<Level> level; |
|
|
|
|
|
level = KnownLevel.findByName(name, KnownLevel::mirrored); |
|
if (level.isPresent()) { |
|
return level.get(); |
|
} |
|
|
|
// Now, check if the given name is an integer. If so, |
|
// first look for a Level with the given value and then |
|
|
|
try { |
|
int x = Integer.parseInt(name); |
|
level = KnownLevel.findByValue(x, KnownLevel::mirrored); |
|
if (level.isPresent()) { |
|
return level.get(); |
|
} |
|
|
|
Level levelObject = new Level(name, x); |
|
// There's no need to use a reachability fence here because |
|
// KnownLevel keeps a strong reference on the level when |
|
|
|
return KnownLevel.findByValue(x, KnownLevel::mirrored).get(); |
|
} catch (NumberFormatException ex) { |
|
// Not an integer. |
|
// Drop through. |
|
} |
|
|
|
level = KnownLevel.findByLocalizedLevelName(name, |
|
KnownLevel::mirrored); |
|
if (level.isPresent()) { |
|
return level.get(); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public final String toString() { |
|
return name; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final int intValue() { |
|
return value; |
|
} |
|
|
|
private static final long serialVersionUID = -8176160795706313070L; |
|
|
|
// Serialization magic to prevent "doppelgangers". |
|
|
|
private Object readResolve() { |
|
Optional<Level> level = KnownLevel.matches(this); |
|
if (level.isPresent()) { |
|
return level.get(); |
|
} |
|
// Woops. Whoever sent us this object knows |
|
|
|
return new Level(this.name, this.value, this.resourceBundleName); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static synchronized Level parse(String name) throws IllegalArgumentException { |
|
|
|
name.length(); |
|
|
|
Optional<Level> level; |
|
|
|
|
|
level = KnownLevel.findByName(name, KnownLevel::referent); |
|
if (level.isPresent()) { |
|
return level.get(); |
|
} |
|
|
|
// Now, check if the given name is an integer. If so, |
|
// first look for a Level with the given value and then |
|
|
|
try { |
|
int x = Integer.parseInt(name); |
|
level = KnownLevel.findByValue(x, KnownLevel::referent); |
|
if (level.isPresent()) { |
|
return level.get(); |
|
} |
|
|
|
Level levelObject = new Level(name, x); |
|
// There's no need to use a reachability fence here because |
|
// KnownLevel keeps a strong reference on the level when |
|
|
|
return KnownLevel.findByValue(x, KnownLevel::referent).get(); |
|
} catch (NumberFormatException ex) { |
|
// Not an integer. |
|
// Drop through. |
|
} |
|
|
|
// Finally, look for a known level with the given localized name, |
|
// in the current default locale. |
|
|
|
level = KnownLevel.findByLocalizedLevelName(name, KnownLevel::referent); |
|
if (level .isPresent()) { |
|
return level.get(); |
|
} |
|
|
|
|
|
throw new IllegalArgumentException("Bad level \"" + name + "\""); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean equals(Object ox) { |
|
try { |
|
Level lx = (Level)ox; |
|
return (lx.value == this.value); |
|
} catch (Exception ex) { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
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. |
|
|
|
static final class KnownLevel extends WeakReference<Level> { |
|
private static Map<String, List<KnownLevel>> nameToLevels = new HashMap<>(); |
|
private static Map<Integer, List<KnownLevel>> intToLevels = new HashMap<>(); |
|
private static final ReferenceQueue<Level> QUEUE = new ReferenceQueue<>(); |
|
|
|
// CUSTOM_LEVEL_CLV is used to register custom level instances with |
|
// their defining class loader, so that they are garbage collected |
|
// if and only if their class loader is no longer strongly |
|
|
|
private static final ClassLoaderValue<List<Level>> CUSTOM_LEVEL_CLV = |
|
new ClassLoaderValue<>(); |
|
|
|
final Level mirroredLevel; |
|
KnownLevel(Level l) { |
|
super(l, QUEUE); |
|
if (l.getClass() == Level.class) { |
|
this.mirroredLevel = l; |
|
} else { |
|
|
|
this.mirroredLevel = new Level(l.name, l.value, |
|
l.resourceBundleName, false); |
|
} |
|
} |
|
|
|
Optional<Level> mirrored() { |
|
return Optional.of(mirroredLevel); |
|
} |
|
|
|
Optional<Level> referent() { |
|
return Optional.ofNullable(get()); |
|
} |
|
|
|
private void remove() { |
|
Optional.ofNullable(nameToLevels.get(mirroredLevel.name)) |
|
.ifPresent((x) -> x.remove(this)); |
|
Optional.ofNullable(intToLevels.get(mirroredLevel.value)) |
|
.ifPresent((x) -> x.remove(this)); |
|
} |
|
|
|
|
|
static synchronized void purge() { |
|
Reference<? extends Level> ref; |
|
while ((ref = QUEUE.poll()) != null) { |
|
if (ref instanceof KnownLevel) { |
|
((KnownLevel)ref).remove(); |
|
} |
|
} |
|
} |
|
|
|
private static void registerWithClassLoader(Level customLevel) { |
|
PrivilegedAction<ClassLoader> pa = |
|
() -> customLevel.getClass().getClassLoader(); |
|
PrivilegedAction<String> pn = customLevel.getClass()::getName; |
|
final String name = AccessController.doPrivileged(pn); |
|
final ClassLoader cl = AccessController.doPrivileged(pa); |
|
CUSTOM_LEVEL_CLV.computeIfAbsent(cl, (c, v) -> new ArrayList<>()) |
|
.add(customLevel); |
|
} |
|
|
|
static synchronized void add(Level l) { |
|
purge(); |
|
// the mirroredLevel object is always added to the list |
|
|
|
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); |
|
|
|
// keep the custom level reachable from its class loader |
|
// This will ensure that custom level values are not GC'ed |
|
|
|
if (o.mirroredLevel != l) { |
|
registerWithClassLoader(l); |
|
} |
|
|
|
} |
|
|
|
|
|
static synchronized Optional<Level> findByName(String name, |
|
Function<KnownLevel, Optional<Level>> selector) { |
|
purge(); |
|
return nameToLevels.getOrDefault(name, Collections.emptyList()) |
|
.stream() |
|
.map(selector) |
|
.flatMap(Optional::stream) |
|
.findFirst(); |
|
} |
|
|
|
|
|
static synchronized Optional<Level> findByValue(int value, |
|
Function<KnownLevel, Optional<Level>> selector) { |
|
purge(); |
|
return intToLevels.getOrDefault(value, Collections.emptyList()) |
|
.stream() |
|
.map(selector) |
|
.flatMap(Optional::stream) |
|
.findFirst(); |
|
} |
|
|
|
// 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 |
|
|
|
static synchronized Optional<Level> findByLocalizedLevelName(String name, |
|
Function<KnownLevel, Optional<Level>> selector) { |
|
purge(); |
|
return nameToLevels.values().stream() |
|
.flatMap(List::stream) |
|
.map(selector) |
|
.flatMap(Optional::stream) |
|
.filter(l -> name.equals(l.getLocalizedLevelName())) |
|
.findFirst(); |
|
} |
|
|
|
static synchronized Optional<Level> matches(Level l) { |
|
purge(); |
|
List<KnownLevel> list = nameToLevels.get(l.name); |
|
if (list != null) { |
|
for (KnownLevel ref : list) { |
|
Level levelObject = ref.get(); |
|
if (levelObject == null) continue; |
|
Level other = ref.mirroredLevel; |
|
Class<? extends Level> type = levelObject.getClass(); |
|
if (l.value == other.value && |
|
(l.resourceBundleName == other.resourceBundleName || |
|
(l.resourceBundleName != null && |
|
l.resourceBundleName.equals(other.resourceBundleName)))) { |
|
if (type == l.getClass()) { |
|
return Optional.of(levelObject); |
|
} |
|
} |
|
} |
|
} |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
} |