GNU Classpath (0.20) | |
Frames | No Frames |
1: /* LogManager.java -- a class for maintaining Loggers and managing 2: configuration properties 3: Copyright (C) 2002,2005 Free Software Foundation, Inc. 4: 5: This file is part of GNU Classpath. 6: 7: GNU Classpath is free software; you can redistribute it and/or modify 8: it under the terms of the GNU General Public License as published by 9: the Free Software Foundation; either version 2, or (at your option) 10: any later version. 11: 12: GNU Classpath is distributed in the hope that it will be useful, but 13: WITHOUT ANY WARRANTY; without even the implied warranty of 14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15: General Public License for more details. 16: 17: You should have received a copy of the GNU General Public License 18: along with GNU Classpath; see the file COPYING. If not, write to the 19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20: 02110-1301 USA. 21: 22: Linking this library statically or dynamically with other modules is 23: making a combined work based on this library. Thus, the terms and 24: conditions of the GNU General Public License cover the whole 25: combination. 26: 27: As a special exception, the copyright holders of this library give you 28: permission to link this library with independent modules to produce an 29: executable, regardless of the license terms of these independent 30: modules, and to copy and distribute the resulting executable under 31: terms of your choice, provided that you also meet, for each linked 32: independent module, the terms and conditions of the license of that 33: module. An independent module is a module which is not derived from 34: or based on this library. If you modify this library, you may extend 35: this exception to your version of the library, but you are not 36: obligated to do so. If you do not wish to do so, delete this 37: exception statement from your version. */ 38: 39: 40: package java.util.logging; 41: 42: import java.beans.PropertyChangeListener; 43: import java.beans.PropertyChangeSupport; 44: import java.io.IOException; 45: import java.io.InputStream; 46: import java.lang.ref.WeakReference; 47: import java.net.URL; 48: import java.util.Collections; 49: import java.util.Enumeration; 50: import java.util.Iterator; 51: import java.util.Map; 52: import java.util.Properties; 53: import java.util.StringTokenizer; 54: 55: /** 56: * The <code>LogManager</code> maintains a hierarchical namespace 57: * of Logger objects and manages properties for configuring the logging 58: * framework. There exists only one single <code>LogManager</code> 59: * per virtual machine. This instance can be retrieved using the 60: * static method {@link #getLogManager()}. 61: * 62: * <p><strong>Configuration Process:</strong> The global LogManager 63: * object is created and configured when the class 64: * <code>java.util.logging.LogManager</code> is initialized. 65: * The configuration process includes the subsequent steps: 66: * 67: * <ul> 68: * <li>If the system property <code>java.util.logging.manager</code> 69: * is set to the name of a subclass of 70: * <code>java.util.logging.LogManager</code>, an instance of 71: * that subclass is created and becomes the global LogManager. 72: * Otherwise, a new instance of LogManager is created.</li> 73: * <li>The <code>LogManager</code> constructor tries to create 74: * a new instance of the class specified by the system 75: * property <code>java.util.logging.config.class</code>. 76: * Typically, the constructor of this class will call 77: * <code>LogManager.getLogManager().readConfiguration(java.io.InputStream)</code> 78: * for configuring the logging framework. 79: * The configuration process stops at this point if 80: * the system property <code>java.util.logging.config.class</code> 81: * is set (irrespective of whether the class constructor 82: * could be called or an exception was thrown).</li> 83: * 84: * <li>If the system property <code>java.util.logging.config.class</code> 85: * is <em>not</em> set, the configuration parameters are read in from 86: * a file and passed to 87: * {@link #readConfiguration(java.io.InputStream)}. 88: * The name and location of this file are specified by the system 89: * property <code>java.util.logging.config.file</code>.</li> 90: * <li>If the system property <code>java.util.logging.config.file</code> 91: * is not set, however, the contents of the URL 92: * "{gnu.classpath.home.url}/logging.properties" are passed to 93: * {@link #readConfiguration(java.io.InputStream)}. 94: * Here, "{gnu.classpath.home.url}" stands for the value of 95: * the system property <code>gnu.classpath.home.url</code>.</li> 96: * </ul> 97: * 98: * <p>The <code>LogManager</code> has a level of <code>INFO</code> by 99: * default, and this will be inherited by <code>Logger</code>s unless they 100: * override it either by properties or programmatically. 101: * 102: * @author Sascha Brawer (brawer@acm.org) 103: */ 104: public class LogManager 105: { 106: /** 107: * The singleton LogManager instance. 108: */ 109: private static LogManager logManager; 110: 111: /** 112: * The registered named loggers; maps the name of a Logger to 113: * a WeakReference to it. 114: */ 115: private Map loggers; 116: final Logger rootLogger; 117: 118: /** 119: * The properties for the logging framework which have been 120: * read in last. 121: */ 122: private Properties properties; 123: 124: /** 125: * A delegate object that provides support for handling 126: * PropertyChangeEvents. The API specification does not 127: * mention which bean should be the source in the distributed 128: * PropertyChangeEvents, but Mauve test code has determined that 129: * the Sun J2SE 1.4 reference implementation uses the LogManager 130: * class object. This is somewhat strange, as the class object 131: * is not the bean with which listeners have to register, but 132: * there is no reason for the GNU Classpath implementation to 133: * behave differently from the reference implementation in 134: * this case. 135: */ 136: private final PropertyChangeSupport pcs = new PropertyChangeSupport( /* source bean */ 137: LogManager.class); 138: 139: protected LogManager() 140: { 141: if (logManager != null) 142: throw new IllegalStateException("there can be only one LogManager; use LogManager.getLogManager()"); 143: 144: logManager = this; 145: loggers = new java.util.HashMap(); 146: rootLogger = new Logger("", null); 147: rootLogger.setLevel(Level.INFO); 148: addLogger(rootLogger); 149: 150: /* Make sure that Logger.global has the rootLogger as its parent. 151: * 152: * Logger.global is set during class initialization of Logger, 153: * which may or may not be before this code is being executed. 154: * For example, on the Sun 1.3.1 and 1.4.0 JVMs, Logger.global 155: * has been set before this code is being executed. In contrast, 156: * Logger.global still is null on GCJ 3.2. Since the LogManager 157: * and Logger classes are mutually dependent, both behaviors are 158: * correct. 159: * 160: * This means that we cannot depend on Logger.global to have its 161: * value when this code executes, although that variable is final. 162: * Since Logger.getLogger will always return the same logger for 163: * the same name, the subsequent line works fine irrespective of 164: * the order in which classes are initialized. 165: */ 166: Logger.getLogger("global").setParent(rootLogger); 167: Logger.getLogger("global").setUseParentHandlers(true); 168: } 169: 170: /** 171: * Returns the globally shared LogManager instance. 172: */ 173: public static LogManager getLogManager() 174: { 175: return logManager; 176: } 177: 178: static 179: { 180: makeLogManager(); 181: 182: /* The Javadoc description of the class explains 183: * what is going on here. 184: */ 185: Object configurator = createInstance(System.getProperty("java.util.logging.config.class"), 186: /* must be instance of */ Object.class); 187: 188: try 189: { 190: if (configurator == null) 191: getLogManager().readConfiguration(); 192: } 193: catch (IOException ex) 194: { 195: /* FIXME: Is it ok to ignore exceptions here? */ 196: } 197: } 198: 199: private static LogManager makeLogManager() 200: { 201: String managerClassName; 202: LogManager manager; 203: 204: managerClassName = System.getProperty("java.util.logging.manager"); 205: manager = (LogManager) createInstance(managerClassName, LogManager.class); 206: if (manager != null) 207: return manager; 208: 209: if (managerClassName != null) 210: System.err.println("WARNING: System property \"java.util.logging.manager\"" 211: + " should be the name of a subclass of java.util.logging.LogManager"); 212: 213: return new LogManager(); 214: } 215: 216: /** 217: * Registers a listener which will be notified when the 218: * logging properties are re-read. 219: */ 220: public synchronized void addPropertyChangeListener(PropertyChangeListener listener) 221: { 222: /* do not register null. */ 223: listener.getClass(); 224: 225: pcs.addPropertyChangeListener(listener); 226: } 227: 228: /** 229: * Unregisters a listener. 230: * 231: * If <code>listener</code> has not been registered previously, 232: * nothing happens. Also, no exception is thrown if 233: * <code>listener</code> is <code>null</code>. 234: */ 235: public synchronized void removePropertyChangeListener(PropertyChangeListener listener) 236: { 237: if (listener != null) 238: pcs.removePropertyChangeListener(listener); 239: } 240: 241: /** 242: * Adds a named logger. If a logger with the same name has 243: * already been registered, the method returns <code>false</code> 244: * without adding the logger. 245: * 246: * <p>The <code>LogManager</code> only keeps weak references 247: * to registered loggers. Therefore, names can become available 248: * after automatic garbage collection. 249: * 250: * @param logger the logger to be added. 251: * 252: * @return <code>true</code>if <code>logger</code> was added, 253: * <code>false</code> otherwise. 254: * 255: * @throws NullPointerException if <code>name</code> is 256: * <code>null</code>. 257: */ 258: public synchronized boolean addLogger(Logger logger) 259: { 260: /* To developers thinking about to remove the 'synchronized' 261: * declaration from this method: Please read the comment 262: * in java.util.logging.Logger.getLogger(String, String) 263: * and make sure that whatever you change wrt. synchronization 264: * does not endanger thread-safety of Logger.getLogger. 265: * The current implementation of Logger.getLogger assumes 266: * that LogManager does its synchronization on the globally 267: * shared instance of LogManager. 268: */ 269: String name; 270: WeakReference ref; 271: 272: /* This will throw a NullPointerException if logger is null, 273: * as required by the API specification. 274: */ 275: name = logger.getName(); 276: 277: ref = (WeakReference) loggers.get(name); 278: if (ref != null) 279: { 280: if (ref.get() != null) 281: return false; 282: 283: /* There has been a logger under this name in the past, 284: * but it has been garbage collected. 285: */ 286: loggers.remove(ref); 287: } 288: 289: /* Adding a named logger requires a security permission. */ 290: if ((name != null) && ! name.equals("")) 291: checkAccess(); 292: 293: Logger parent = findAncestor(logger); 294: loggers.put(name, new WeakReference(logger)); 295: if (parent != logger.getParent()) 296: logger.setParent(parent); 297: 298: /* It can happen that existing loggers should be children of 299: * the newly added logger. For example, assume that there 300: * already exist loggers under the names "", "foo", and "foo.bar.baz". 301: * When adding "foo.bar", the logger "foo.bar.baz" should change 302: * its parent to "foo.bar". 303: */ 304: if (parent != rootLogger) 305: { 306: for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();) 307: { 308: Logger possChild = (Logger) ((WeakReference) loggers.get(iter.next())) 309: .get(); 310: if ((possChild == null) || (possChild == logger) 311: || (possChild.getParent() != parent)) 312: continue; 313: 314: if (! possChild.getName().startsWith(name)) 315: continue; 316: 317: if (possChild.getName().charAt(name.length()) != '.') 318: continue; 319: 320: possChild.setParent(logger); 321: } 322: } 323: 324: return true; 325: } 326: 327: /** 328: * Finds the closest ancestor for a logger among the currently 329: * registered ones. For example, if the currently registered 330: * loggers have the names "", "foo", and "foo.bar", the result for 331: * "foo.bar.baz" will be the logger whose name is "foo.bar". 332: * 333: * @param child a logger for whose name no logger has been 334: * registered. 335: * 336: * @return the closest ancestor for <code>child</code>, 337: * or <code>null</code> if <code>child</code> 338: * is the root logger. 339: * 340: * @throws NullPointerException if <code>child</code> 341: * is <code>null</code>. 342: */ 343: private synchronized Logger findAncestor(Logger child) 344: { 345: String childName = child.getName(); 346: int childNameLength = childName.length(); 347: Logger best = rootLogger; 348: int bestNameLength = 0; 349: 350: Logger cand; 351: String candName; 352: int candNameLength; 353: 354: if (child == rootLogger) 355: return null; 356: 357: for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();) 358: { 359: candName = (String) iter.next(); 360: candNameLength = candName.length(); 361: 362: if (candNameLength > bestNameLength 363: && childNameLength > candNameLength 364: && childName.startsWith(candName) 365: && childName.charAt(candNameLength) == '.') 366: { 367: cand = (Logger) ((WeakReference) loggers.get(candName)).get(); 368: if ((cand == null) || (cand == child)) 369: continue; 370: 371: bestNameLength = candName.length(); 372: best = cand; 373: } 374: } 375: 376: return best; 377: } 378: 379: /** 380: * Returns a Logger given its name. 381: * 382: * @param name the name of the logger. 383: * 384: * @return a named Logger, or <code>null</code> if there is no 385: * logger with that name. 386: * 387: * @throw java.lang.NullPointerException if <code>name</code> 388: * is <code>null</code>. 389: */ 390: public synchronized Logger getLogger(String name) 391: { 392: WeakReference ref; 393: 394: /* Throw a NullPointerException if name is null. */ 395: name.getClass(); 396: 397: ref = (WeakReference) loggers.get(name); 398: if (ref != null) 399: return (Logger) ref.get(); 400: else 401: return null; 402: } 403: 404: /** 405: * Returns an Enumeration of currently registered Logger names. 406: * Since other threads can register loggers at any time, the 407: * result could be different any time this method is called. 408: * 409: * @return an Enumeration with the names of the currently 410: * registered Loggers. 411: */ 412: public synchronized Enumeration getLoggerNames() 413: { 414: return Collections.enumeration(loggers.keySet()); 415: } 416: 417: /** 418: * Resets the logging configuration by removing all handlers for 419: * registered named loggers and setting their level to <code>null</code>. 420: * The level of the root logger will be set to <code>Level.INFO</code>. 421: * 422: * @throws SecurityException if a security manager exists and 423: * the caller is not granted the permission to control 424: * the logging infrastructure. 425: */ 426: public synchronized void reset() throws SecurityException 427: { 428: /* Throw a SecurityException if the caller does not have the 429: * permission to control the logging infrastructure. 430: */ 431: checkAccess(); 432: 433: properties = new Properties(); 434: 435: Iterator iter = loggers.values().iterator(); 436: while (iter.hasNext()) 437: { 438: WeakReference ref; 439: Logger logger; 440: 441: ref = (WeakReference) iter.next(); 442: if (ref != null) 443: { 444: logger = (Logger) ref.get(); 445: 446: if (logger == null) 447: iter.remove(); 448: else if (logger != rootLogger) 449: { 450: logger.resetLogger(); 451: logger.setLevel(null); 452: } 453: } 454: } 455: 456: rootLogger.setLevel(Level.INFO); 457: rootLogger.resetLogger(); 458: } 459: 460: /** 461: * Configures the logging framework by reading a configuration file. 462: * The name and location of this file are specified by the system 463: * property <code>java.util.logging.config.file</code>. If this 464: * property is not set, the URL 465: * "{gnu.classpath.home.url}/logging.properties" is taken, where 466: * "{gnu.classpath.home.url}" stands for the value of the system 467: * property <code>gnu.classpath.home.url</code>. 468: * 469: * <p>The task of configuring the framework is then delegated to 470: * {@link #readConfiguration(java.io.InputStream)}, which will 471: * notify registered listeners after having read the properties. 472: * 473: * @throws SecurityException if a security manager exists and 474: * the caller is not granted the permission to control 475: * the logging infrastructure, or if the caller is 476: * not granted the permission to read the configuration 477: * file. 478: * 479: * @throws IOException if there is a problem reading in the 480: * configuration file. 481: */ 482: public synchronized void readConfiguration() 483: throws IOException, SecurityException 484: { 485: String path; 486: InputStream inputStream; 487: 488: path = System.getProperty("java.util.logging.config.file"); 489: if ((path == null) || (path.length() == 0)) 490: { 491: String url = (System.getProperty("gnu.classpath.home.url") 492: + "/logging.properties"); 493: inputStream = new URL(url).openStream(); 494: } 495: else 496: inputStream = new java.io.FileInputStream(path); 497: 498: try 499: { 500: readConfiguration(inputStream); 501: } 502: finally 503: { 504: /* Close the stream in order to save 505: * resources such as file descriptors. 506: */ 507: inputStream.close(); 508: } 509: } 510: 511: public synchronized void readConfiguration(InputStream inputStream) 512: throws IOException, SecurityException 513: { 514: Properties newProperties; 515: Enumeration keys; 516: 517: checkAccess(); 518: newProperties = new Properties(); 519: newProperties.load(inputStream); 520: reset(); 521: this.properties = newProperties; 522: keys = newProperties.propertyNames(); 523: 524: while (keys.hasMoreElements()) 525: { 526: String key = ((String) keys.nextElement()).trim(); 527: String value = newProperties.getProperty(key); 528: 529: if (value == null) 530: continue; 531: 532: value = value.trim(); 533: 534: if ("handlers".equals(key)) 535: { 536: StringTokenizer tokenizer = new StringTokenizer(value); 537: while (tokenizer.hasMoreTokens()) 538: { 539: String handlerName = tokenizer.nextToken(); 540: try 541: { 542: Class handlerClass = ClassLoader.getSystemClassLoader().loadClass(handlerName); 543: getLogger("").addHandler((Handler) handlerClass 544: .newInstance()); 545: } 546: catch (ClassCastException ex) 547: { 548: System.err.println("[LogManager] class " + handlerName 549: + " is not subclass of java.util.logging.Handler"); 550: } 551: catch (Exception ex) 552: { 553: //System.out.println("[LogManager.readConfiguration]"+ex); 554: } 555: } 556: } 557: 558: if (key.endsWith(".level")) 559: { 560: String loggerName = key.substring(0, key.length() - 6); 561: Logger logger = getLogger(loggerName); 562: 563: if (logger == null) 564: { 565: logger = Logger.getLogger(loggerName); 566: addLogger(logger); 567: } 568: try 569: { 570: logger.setLevel(Level.parse(value)); 571: } 572: catch (Exception _) 573: { 574: //System.out.println("[LogManager.readConfiguration] "+_); 575: } 576: continue; 577: } 578: } 579: 580: /* The API specification does not talk about the 581: * property name that is distributed with the 582: * PropertyChangeEvent. With test code, it could 583: * be determined that the Sun J2SE 1.4 reference 584: * implementation uses null for the property name. 585: */ 586: pcs.firePropertyChange(null, null, null); 587: } 588: 589: /** 590: * Returns the value of a configuration property as a String. 591: */ 592: public synchronized String getProperty(String name) 593: { 594: if (properties != null) 595: return properties.getProperty(name); 596: else 597: return null; 598: } 599: 600: /** 601: * Returns the value of a configuration property as an integer. 602: * This function is a helper used by the Classpath implementation 603: * of java.util.logging, it is <em>not</em> specified in the 604: * logging API. 605: * 606: * @param name the name of the configuration property. 607: * 608: * @param defaultValue the value that will be returned if the 609: * property is not defined, or if its value is not an integer 610: * number. 611: */ 612: static int getIntProperty(String name, int defaultValue) 613: { 614: try 615: { 616: return Integer.parseInt(getLogManager().getProperty(name)); 617: } 618: catch (Exception ex) 619: { 620: return defaultValue; 621: } 622: } 623: 624: /** 625: * Returns the value of a configuration property as an integer, 626: * provided it is inside the acceptable range. 627: * This function is a helper used by the Classpath implementation 628: * of java.util.logging, it is <em>not</em> specified in the 629: * logging API. 630: * 631: * @param name the name of the configuration property. 632: * 633: * @param minValue the lowest acceptable value. 634: * 635: * @param maxValue the highest acceptable value. 636: * 637: * @param defaultValue the value that will be returned if the 638: * property is not defined, or if its value is not an integer 639: * number, or if it is less than the minimum value, 640: * or if it is greater than the maximum value. 641: */ 642: static int getIntPropertyClamped(String name, int defaultValue, 643: int minValue, int maxValue) 644: { 645: int val = getIntProperty(name, defaultValue); 646: if ((val < minValue) || (val > maxValue)) 647: val = defaultValue; 648: return val; 649: } 650: 651: /** 652: * Returns the value of a configuration property as a boolean. 653: * This function is a helper used by the Classpath implementation 654: * of java.util.logging, it is <em>not</em> specified in the 655: * logging API. 656: * 657: * @param name the name of the configuration property. 658: * 659: * @param defaultValue the value that will be returned if the 660: * property is not defined, or if its value is neither 661: * <code>"true"</code> nor <code>"false"</code>. 662: */ 663: static boolean getBooleanProperty(String name, boolean defaultValue) 664: { 665: try 666: { 667: return (Boolean.valueOf(getLogManager().getProperty(name))).booleanValue(); 668: } 669: catch (Exception ex) 670: { 671: return defaultValue; 672: } 673: } 674: 675: /** 676: * Returns the value of a configuration property as a Level. 677: * This function is a helper used by the Classpath implementation 678: * of java.util.logging, it is <em>not</em> specified in the 679: * logging API. 680: * 681: * @param propertyName the name of the configuration property. 682: * 683: * @param defaultValue the value that will be returned if the 684: * property is not defined, or if 685: * {@link Level#parse(java.lang.String)} does not like 686: * the property value. 687: */ 688: static Level getLevelProperty(String propertyName, Level defaultValue) 689: { 690: try 691: { 692: return Level.parse(getLogManager().getProperty(propertyName)); 693: } 694: catch (Exception ex) 695: { 696: return defaultValue; 697: } 698: } 699: 700: /** 701: * Returns the value of a configuration property as a Class. 702: * This function is a helper used by the Classpath implementation 703: * of java.util.logging, it is <em>not</em> specified in the 704: * logging API. 705: * 706: * @param propertyName the name of the configuration property. 707: * 708: * @param defaultValue the value that will be returned if the 709: * property is not defined, or if it does not specify 710: * the name of a loadable class. 711: */ 712: static final Class getClassProperty(String propertyName, Class defaultValue) 713: { 714: Class usingClass = null; 715: 716: try 717: { 718: String propertyValue = logManager.getProperty(propertyName); 719: if (propertyValue != null) 720: usingClass = Class.forName(propertyValue); 721: if (usingClass != null) 722: return usingClass; 723: } 724: catch (Exception _) 725: { 726: } 727: 728: return defaultValue; 729: } 730: 731: static final Object getInstanceProperty(String propertyName, Class ofClass, 732: Class defaultClass) 733: { 734: Class klass = getClassProperty(propertyName, defaultClass); 735: if (klass == null) 736: return null; 737: 738: try 739: { 740: Object obj = klass.newInstance(); 741: if (ofClass.isInstance(obj)) 742: return obj; 743: } 744: catch (Exception _) 745: { 746: } 747: 748: if (defaultClass == null) 749: return null; 750: 751: try 752: { 753: return defaultClass.newInstance(); 754: } 755: catch (java.lang.InstantiationException ex) 756: { 757: throw new RuntimeException(ex.getMessage()); 758: } 759: catch (java.lang.IllegalAccessException ex) 760: { 761: throw new RuntimeException(ex.getMessage()); 762: } 763: } 764: 765: /** 766: * An instance of <code>LoggingPermission("control")</code> 767: * that is shared between calls to <code>checkAccess()</code>. 768: */ 769: private static final LoggingPermission controlPermission = new LoggingPermission("control", 770: null); 771: 772: /** 773: * Checks whether the current security context allows changing 774: * the configuration of the logging framework. For the security 775: * context to be trusted, it has to be granted 776: * a LoggingPermission("control"). 777: * 778: * @throws SecurityException if a security manager exists and 779: * the caller is not granted the permission to control 780: * the logging infrastructure. 781: */ 782: public void checkAccess() throws SecurityException 783: { 784: SecurityManager sm = System.getSecurityManager(); 785: if (sm != null) 786: sm.checkPermission(controlPermission); 787: } 788: 789: /** 790: * Creates a new instance of a class specified by name. 791: * 792: * @param className the name of the class of which a new instance 793: * should be created. 794: * 795: * @param ofClass the class to which the new instance should 796: * be either an instance or an instance of a subclass. 797: * FIXME: This description is just terrible. 798: * 799: * @return the new instance, or <code>null</code> if 800: * <code>className</code> is <code>null</code>, if no class 801: * with that name could be found, if there was an error 802: * loading that class, or if the constructor of the class 803: * has thrown an exception. 804: */ 805: static final Object createInstance(String className, Class ofClass) 806: { 807: Class klass; 808: 809: if ((className == null) || (className.length() == 0)) 810: return null; 811: 812: try 813: { 814: klass = Class.forName(className); 815: if (! ofClass.isAssignableFrom(klass)) 816: return null; 817: 818: return klass.newInstance(); 819: } 820: catch (Exception _) 821: { 822: return null; 823: } 824: catch (java.lang.LinkageError _) 825: { 826: return null; 827: } 828: } 829: }
GNU Classpath (0.20) |