提交 e0a8e955 编写于 作者: M mchung

6664509: Add logging context

6664528: Find log level matching its name or value given at construction time
Reviewed-by: alanb, ahgross, jgish, hawtin
上级 7bd29c94
...@@ -24,6 +24,10 @@ ...@@ -24,6 +24,10 @@
*/ */
package java.util.logging; package java.util.logging;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
/** /**
...@@ -59,7 +63,6 @@ import java.util.ResourceBundle; ...@@ -59,7 +63,6 @@ import java.util.ResourceBundle;
*/ */
public class Level implements java.io.Serializable { 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"; private static String defaultBundle = "sun.util.logging.resources.logging";
/** /**
...@@ -77,6 +80,9 @@ public class Level implements java.io.Serializable { ...@@ -77,6 +80,9 @@ public class Level implements java.io.Serializable {
*/ */
private final String resourceBundleName; private final String resourceBundleName;
// localized level name
private String localizedLevelName;
/** /**
* OFF is a special level that can be used to turn off logging. * OFF is a special level that can be used to turn off logging.
* This level is initialized to <CODE>Integer.MAX_VALUE</CODE>. * This level is initialized to <CODE>Integer.MAX_VALUE</CODE>.
...@@ -202,9 +208,8 @@ public class Level implements java.io.Serializable { ...@@ -202,9 +208,8 @@ public class Level implements java.io.Serializable {
this.name = name; this.name = name;
this.value = value; this.value = value;
this.resourceBundleName = resourceBundleName; this.resourceBundleName = resourceBundleName;
synchronized (Level.class) { this.localizedLevelName = resourceBundleName == null ? name : null;
known.add(this); KnownLevel.add(this);
}
} }
/** /**
...@@ -236,12 +241,76 @@ public class Level implements java.io.Serializable { ...@@ -236,12 +241,76 @@ public class Level implements java.io.Serializable {
* @return localized name * @return localized name
*/ */
public String getLocalizedName() { 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 { try {
ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName); ResourceBundle rb = ResourceBundle.getBundle(resourceBundleName);
return rb.getString(name); localizedLevelName = rb.getString(name);
} catch (Exception ex) { } 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 @@ public class Level implements java.io.Serializable { ...@@ -268,21 +337,15 @@ public class Level implements java.io.Serializable {
// Serialization magic to prevent "doppelgangers". // Serialization magic to prevent "doppelgangers".
// This is a performance optimization. // This is a performance optimization.
private Object readResolve() { private Object readResolve() {
synchronized (Level.class) { KnownLevel o = KnownLevel.matches(this);
for (int i = 0; i < known.size(); i++) { if (o != null) {
Level other = known.get(i); return o.levelObject;
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;
} }
// 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 @@ public class Level implements java.io.Serializable { ...@@ -296,6 +359,7 @@ public class Level implements java.io.Serializable {
* <li> "SEVERE" * <li> "SEVERE"
* <li> "1000" * <li> "1000"
* </ul> * </ul>
*
* @param name string to be parsed * @param name string to be parsed
* @throws NullPointerException if the name is null * @throws NullPointerException if the name is null
* @throws IllegalArgumentException if the value is not valid. * @throws IllegalArgumentException if the value is not valid.
...@@ -315,12 +379,12 @@ public class Level implements java.io.Serializable { ...@@ -315,12 +379,12 @@ public class Level implements java.io.Serializable {
// Check that name is not null. // Check that name is not null.
name.length(); name.length();
KnownLevel level;
// Look for a known Level with the given non-localized name. // Look for a known Level with the given non-localized name.
for (int i = 0; i < known.size(); i++) { level = KnownLevel.findByName(name);
Level l = known.get(i); if (level != null) {
if (name.equals(l.name)) { return level.levelObject;
return l;
}
} }
// Now, check if the given name is an integer. If so, // Now, check if the given name is an integer. If so,
...@@ -328,14 +392,13 @@ public class Level implements java.io.Serializable { ...@@ -328,14 +392,13 @@ public class Level implements java.io.Serializable {
// if necessary create one. // if necessary create one.
try { try {
int x = Integer.parseInt(name); int x = Integer.parseInt(name);
for (int i = 0; i < known.size(); i++) { level = KnownLevel.findByValue(x);
Level l = known.get(i); if (level == null) {
if (l.value == x) { // add new Level
return l; Level levelObject = new Level(name, x);
} level = KnownLevel.findByValue(x);
} }
// Create a new Level. return level.levelObject;
return new Level(name, x);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
// Not an integer. // Not an integer.
// Drop through. // Drop through.
...@@ -344,11 +407,9 @@ public class Level implements java.io.Serializable { ...@@ -344,11 +407,9 @@ public class Level implements java.io.Serializable {
// Finally, look for a known level with the given localized name, // Finally, look for a known level with the given localized name,
// in the current default locale. // in the current default locale.
// This is relatively expensive, but not excessively so. // This is relatively expensive, but not excessively so.
for (int i = 0; i < known.size(); i++) { level = KnownLevel.findByLocalizedName(name);
Level l = known.get(i); if (level != null) {
if (name.equals(l.getLocalizedName())) { return level.levelObject;
return l;
}
} }
// OK, we've tried everything and failed // OK, we've tried everything and failed
...@@ -375,4 +436,124 @@ public class Level implements java.io.Serializable { ...@@ -375,4 +436,124 @@ public class Level implements java.io.Serializable {
public int hashCode() { public int hashCode() {
return this.value; 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;
}
}
} }
...@@ -30,6 +30,7 @@ import java.util.*; ...@@ -30,6 +30,7 @@ import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.security.*; import java.security.*;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.logging.LogManager.LoggerContext;
/** /**
* A Logger object is used to log messages for a specific * A Logger object is used to log messages for a specific
...@@ -286,6 +287,26 @@ public class Logger { ...@@ -286,6 +287,26 @@ public class Logger {
} }
} }
// Until all JDK code converted to call sun.util.logging.PlatformLogger
// (see 7054233), we need to determine if Logger.getLogger is to add
// a system logger or user logger.
//
// As an interim solution, if the immediate caller whose caller loader is
// null, we assume it's a system logger and add it to the system context.
private static LoggerContext getLoggerContext() {
LogManager manager = LogManager.getLogManager();
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 0: Reflection 1: Logger.getLoggerContext 2: Logger.getLogger 3: caller
final int SKIP_FRAMES = 3;
Class<?> caller = sun.reflect.Reflection.getCallerClass(SKIP_FRAMES);
if (caller.getClassLoader() == null) {
return manager.getSystemContext();
}
}
return manager.getUserContext();
}
/** /**
* Find or create a logger for a named subsystem. If a logger has * Find or create a logger for a named subsystem. If a logger has
* already been created with the given name it is returned. Otherwise * already been created with the given name it is returned. Otherwise
...@@ -327,8 +348,8 @@ public class Logger { ...@@ -327,8 +348,8 @@ public class Logger {
// would throw an IllegalArgumentException in the second call // would throw an IllegalArgumentException in the second call
// because the wrapper would result in an attempt to replace // because the wrapper would result in an attempt to replace
// the existing "resourceBundleForFoo" with null. // the existing "resourceBundleForFoo" with null.
LogManager manager = LogManager.getLogManager(); LoggerContext context = getLoggerContext();
return manager.demandLogger(name); return context.demandLogger(name);
} }
/** /**
...@@ -375,8 +396,8 @@ public class Logger { ...@@ -375,8 +396,8 @@ public class Logger {
// Synchronization is not required here. All synchronization for // Synchronization is not required here. All synchronization for
// adding a new Logger object is handled by LogManager.addLogger(). // adding a new Logger object is handled by LogManager.addLogger().
public static Logger getLogger(String name, String resourceBundleName) { public static Logger getLogger(String name, String resourceBundleName) {
LogManager manager = LogManager.getLogManager(); LoggerContext context = getLoggerContext();
Logger result = manager.demandLogger(name); Logger result = context.demandLogger(name, resourceBundleName);
// MissingResourceException or IllegalArgumentException can be // MissingResourceException or IllegalArgumentException can be
// thrown by setupResourceInfo(). // thrown by setupResourceInfo().
...@@ -384,6 +405,18 @@ public class Logger { ...@@ -384,6 +405,18 @@ public class Logger {
return result; return result;
} }
// package-private
// Add a platform logger to the system context.
// i.e. caller of sun.util.logging.PlatformLogger.getLogger
static Logger getPlatformLogger(String name) {
LogManager manager = LogManager.getLogManager();
LoggerContext context = manager.getSystemContext();
// all loggers in the system context will default to
// the system logger's resource bundle
Logger result = context.demandLogger(name);
return result;
}
/** /**
* Create an anonymous Logger. The newly created Logger is not * Create an anonymous Logger. The newly created Logger is not
...@@ -536,7 +569,7 @@ public class Logger { ...@@ -536,7 +569,7 @@ public class Logger {
private void doLog(LogRecord lr) { private void doLog(LogRecord lr) {
lr.setLoggerName(name); lr.setLoggerName(name);
String ebname = getEffectiveResourceBundleName(); String ebname = getEffectiveResourceBundleName();
if (ebname != null) { if (ebname != null && !ebname.equals(SYSTEM_LOGGER_RB_NAME)) {
lr.setResourceBundleName(ebname); lr.setResourceBundleName(ebname);
lr.setResourceBundle(findResourceBundle(ebname)); lr.setResourceBundle(findResourceBundle(ebname));
} }
...@@ -1285,6 +1318,22 @@ public class Logger { ...@@ -1285,6 +1318,22 @@ public class Logger {
// May also return null if we can't find the resource bundle and // May also return null if we can't find the resource bundle and
// there is no suitable previous cached value. // there is no suitable previous cached value.
static final String SYSTEM_LOGGER_RB_NAME = "sun.util.logging.resources.logging";
private static ResourceBundle findSystemResourceBundle(final Locale locale) {
// the resource bundle is in a restricted package
return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() {
public ResourceBundle run() {
try {
return ResourceBundle.getBundle(SYSTEM_LOGGER_RB_NAME,
locale);
} catch (MissingResourceException e) {
throw new InternalError(e.toString());
}
}
});
}
private synchronized ResourceBundle findResourceBundle(String name) { private synchronized ResourceBundle findResourceBundle(String name) {
// Return a null bundle for a null name. // Return a null bundle for a null name.
if (name == null) { if (name == null) {
...@@ -1299,6 +1348,13 @@ public class Logger { ...@@ -1299,6 +1348,13 @@ public class Logger {
return catalog; return catalog;
} }
if (name.equals(SYSTEM_LOGGER_RB_NAME)) {
catalog = findSystemResourceBundle(currentLocale);
catalogName = name;
catalogLocale = currentLocale;
return catalog;
}
// Use the thread's context ClassLoader. If there isn't one, // Use the thread's context ClassLoader. If there isn't one,
// use the SystemClassloader. // use the SystemClassloader.
ClassLoader cl = Thread.currentThread().getContextClassLoader(); ClassLoader cl = Thread.currentThread().getContextClassLoader();
...@@ -1315,7 +1371,6 @@ public class Logger { ...@@ -1315,7 +1371,6 @@ public class Logger {
// ClassLoader. Drop through. // ClassLoader. Drop through.
} }
// Fall back to searching up the call stack and trying each // Fall back to searching up the call stack and trying each
// calling ClassLoader. // calling ClassLoader.
for (int ix = 0; ; ix++) { for (int ix = 0; ; ix++) {
......
...@@ -34,7 +34,7 @@ import java.util.ArrayList; ...@@ -34,7 +34,7 @@ import java.util.ArrayList;
* *
* The <tt>LoggingMXBean</tt> interface provides a standard * The <tt>LoggingMXBean</tt> interface provides a standard
* method for management access to the individual * method for management access to the individual
* java.util.Logger objects available at runtime. * {@code Logger} objects available at runtime.
* *
* @author Ron Mann * @author Ron Mann
* @author Mandy Chung * @author Mandy Chung
...@@ -75,7 +75,7 @@ class Logging implements LoggingMXBean { ...@@ -75,7 +75,7 @@ class Logging implements LoggingMXBean {
if (level == null) { if (level == null) {
return EMPTY_STRING; return EMPTY_STRING;
} else { } else {
return level.getName(); return level.getLevelName();
} }
} }
...@@ -85,7 +85,6 @@ class Logging implements LoggingMXBean { ...@@ -85,7 +85,6 @@ class Logging implements LoggingMXBean {
} }
Logger logger = logManager.getLogger(loggerName); Logger logger = logManager.getLogger(loggerName);
if (logger == null) { if (logger == null) {
throw new IllegalArgumentException("Logger " + loggerName + throw new IllegalArgumentException("Logger " + loggerName +
"does not exist"); "does not exist");
...@@ -94,7 +93,10 @@ class Logging implements LoggingMXBean { ...@@ -94,7 +93,10 @@ class Logging implements LoggingMXBean {
Level level = null; Level level = null;
if (levelName != null) { if (levelName != null) {
// parse will throw IAE if logLevel is invalid // parse will throw IAE if logLevel is invalid
level = Level.parse(levelName); level = Level.findLevel(levelName);
if (level == null) {
throw new IllegalArgumentException("Unknown level \"" + levelName + "\"");
}
} }
logger.setLevel(level); logger.setLevel(level);
......
...@@ -37,7 +37,8 @@ class LoggingProxyImpl implements LoggingProxy { ...@@ -37,7 +37,8 @@ class LoggingProxyImpl implements LoggingProxy {
@Override @Override
public Object getLogger(String name) { public Object getLogger(String name) {
return Logger.getLogger(name); // always create a platform logger with the resource bundle name
return Logger.getPlatformLogger(name);
} }
@Override @Override
...@@ -92,12 +93,16 @@ class LoggingProxyImpl implements LoggingProxy { ...@@ -92,12 +93,16 @@ class LoggingProxyImpl implements LoggingProxy {
@Override @Override
public Object parseLevel(String levelName) { public Object parseLevel(String levelName) {
return Level.parse(levelName); Level level = Level.findLevel(levelName);
if (level == null) {
throw new IllegalArgumentException("Unknown level \"" + levelName + "\"");
}
return level;
} }
@Override @Override
public String getLevelName(Object level) { public String getLevelName(Object level) {
return ((Level) level).getName(); return ((Level) level).getLevelName();
} }
@Override @Override
......
...@@ -162,7 +162,7 @@ public class SimpleFormatter extends Formatter { ...@@ -162,7 +162,7 @@ public class SimpleFormatter extends Formatter {
dat, dat,
source, source,
record.getLoggerName(), record.getLoggerName(),
record.getLevel().getLocalizedName(), record.getLevel().getLocalizedLevelName(),
message, message,
throwable); throwable);
} }
......
...@@ -327,21 +327,27 @@ public final class AppContext { ...@@ -327,21 +327,27 @@ public final class AppContext {
// Before we return the main "system" AppContext, check to // Before we return the main "system" AppContext, check to
// see if there's an AWTSecurityManager installed. If so, // see if there's an AWTSecurityManager installed. If so,
// allow it to choose the AppContext to return. // allow it to choose the AppContext to return.
SecurityManager securityManager = System.getSecurityManager(); AppContext secAppContext = getExecutionAppContext();
if ((securityManager != null) && if (secAppContext != null) {
(securityManager instanceof AWTSecurityManager)) appContext = secAppContext; // Return what we're told
{
AWTSecurityManager awtSecMgr = (AWTSecurityManager)securityManager;
AppContext secAppContext = awtSecMgr.getAppContext();
if (secAppContext != null) {
appContext = secAppContext; // Return what we're told
}
} }
} }
return appContext; return appContext;
} }
private final static AppContext getExecutionAppContext() {
SecurityManager securityManager = System.getSecurityManager();
if ((securityManager != null) &&
(securityManager instanceof AWTSecurityManager))
{
AWTSecurityManager awtSecMgr = (AWTSecurityManager) securityManager;
AppContext secAppContext = awtSecMgr.getAppContext();
return secAppContext; // Return what we're told
}
return null;
}
/** /**
* Returns the main ("system") AppContext. * Returns the main ("system") AppContext.
* *
...@@ -806,6 +812,21 @@ public final class AppContext { ...@@ -806,6 +812,21 @@ public final class AppContext {
public boolean isMainAppContext() { public boolean isMainAppContext() {
return (numAppContexts.get() == 1); return (numAppContexts.get() == 1);
} }
public Object getContext() {
return getAppContext();
}
public Object getExecutionContext() {
return getExecutionAppContext();
}
public Object get(Object context, Object key) {
return ((AppContext)context).get(key);
}
public void put(Object context, Object key, Object value) {
((AppContext)context).put(key, value);
}
public void remove(Object context, Object key) {
((AppContext)context).remove(key);
}
}); });
} }
} }
......
...@@ -26,6 +26,14 @@ ...@@ -26,6 +26,14 @@
package sun.misc; package sun.misc;
public interface JavaAWTAccess { public interface JavaAWTAccess {
public Object getContext();
public Object getExecutionContext();
public Object get(Object context, Object key);
public void put(Object context, Object key, Object value);
public void remove(Object context, Object key);
// convenience methods whose context is the object returned by getContext()
public Object get(Object key); public Object get(Object key);
public void put(Object key, Object value); public void put(Object key, Object value);
public void remove(Object key); public void remove(Object key);
......
...@@ -148,6 +148,9 @@ keystore.type=jks ...@@ -148,6 +148,9 @@ keystore.type=jks
package.access=sun.,\ package.access=sun.,\
com.sun.xml.internal.,\ com.sun.xml.internal.,\
com.sun.imageio.,\ com.sun.imageio.,\
com.sun.istack.internal.,\
com.sun.jmx.default.,\
com.sun.jmx.remote.util.,\
com.sun.org.apache.xerces.internal.utils.,\ com.sun.org.apache.xerces.internal.utils.,\
com.sun.org.apache.xalan.internal.utils.,\ com.sun.org.apache.xalan.internal.utils.,\
com.sun.org.glassfish.external.,\ com.sun.org.glassfish.external.,\
...@@ -166,6 +169,9 @@ package.access=sun.,\ ...@@ -166,6 +169,9 @@ package.access=sun.,\
package.definition=sun.,\ package.definition=sun.,\
com.sun.xml.internal.,\ com.sun.xml.internal.,\
com.sun.imageio.,\ com.sun.imageio.,\
com.sun.istack.internal.,\
com.sun.jmx.default.,\
com.sun.jmx.remote.util.,\
com.sun.org.apache.xerces.internal.utils.,\ com.sun.org.apache.xerces.internal.utils.,\
com.sun.org.apache.xalan.internal.utils.,\ com.sun.org.apache.xalan.internal.utils.,\
com.sun.org.glassfish.external.,\ com.sun.org.glassfish.external.,\
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册