1 /*
   2  * Copyright (c) 2000, 2012, 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 
  27 package java.util.logging;
  28 
  29 import java.io.*;
  30 import java.util.*;
  31 import java.security.*;
  32 import java.lang.ref.ReferenceQueue;
  33 import java.lang.ref.WeakReference;
  34 import java.lang.reflect.Constructor;
  35 import java.lang.reflect.InvocationTargetException;
  36 import java.lang.reflect.Method;
  37 import java.beans.PropertyChangeListener;
  38 
  39 /**
  40  * There is a single global LogManager object that is used to
  41  * maintain a set of shared state about Loggers and log services.
  42  * <p>
  43  * This LogManager object:
  44  * <ul>
  45  * <li> Manages a hierarchical namespace of Logger objects.  All
  46  *      named Loggers are stored in this namespace.
  47  * <li> Manages a set of logging control properties.  These are
  48  *      simple key-value pairs that can be used by Handlers and
  49  *      other logging objects to configure themselves.
  50  * </ul>
  51  * <p>
  52  * The global LogManager object can be retrieved using LogManager.getLogManager().
  53  * The LogManager object is created during class initialization and
  54  * cannot subsequently be changed.
  55  * <p>
  56  * At startup the LogManager class is located using the
  57  * java.util.logging.manager system property.
  58  * <p>
  59  * The LogManager defines two optional system properties that allow control over
  60  * the initial configuration:
  61  * <ul>
  62  * <li>"java.util.logging.config.class"
  63  * <li>"java.util.logging.config.file"
  64  * </ul>
  65  * These two properties may be specified on the command line to the "java"
  66  * command, or as system property definitions passed to JNI_CreateJavaVM.
  67  * <p>
  68  * If the "java.util.logging.config.class" property is set, then the
  69  * property value is treated as a class name.  The given class will be
  70  * loaded, an object will be instantiated, and that object's constructor
  71  * is responsible for reading in the initial configuration.  (That object
  72  * may use other system properties to control its configuration.)  The
  73  * alternate configuration class can use <tt>readConfiguration(InputStream)</tt>
  74  * to define properties in the LogManager.
  75  * <p>
  76  * If "java.util.logging.config.class" property is <b>not</b> set,
  77  * then the "java.util.logging.config.file" system property can be used
  78  * to specify a properties file (in java.util.Properties format). The
  79  * initial logging configuration will be read from this file.
  80  * <p>
  81  * If neither of these properties is defined then the LogManager uses its
  82  * default configuration. The default configuration is typically loaded from the
  83  * properties file "{@code lib/logging.properties}" in the Java installation
  84  * directory.
  85  * <p>
  86  * The properties for loggers and Handlers will have names starting
  87  * with the dot-separated name for the handler or logger.
  88  * <p>
  89  * The global logging properties may include:
  90  * <ul>
  91  * <li>A property "handlers".  This defines a whitespace or comma separated
  92  * list of class names for handler classes to load and register as
  93  * handlers on the root Logger (the Logger named "").  Each class
  94  * name must be for a Handler class which has a default constructor.
  95  * Note that these Handlers may be created lazily, when they are
  96  * first used.
  97  *
  98  * <li>A property "&lt;logger&gt;.handlers". This defines a whitespace or
  99  * comma separated list of class names for handlers classes to
 100  * load and register as handlers to the specified logger. Each class
 101  * name must be for a Handler class which has a default constructor.
 102  * Note that these Handlers may be created lazily, when they are
 103  * first used.
 104  *
 105  * <li>A property "&lt;logger&gt;.useParentHandlers". This defines a boolean
 106  * value. By default every logger calls its parent in addition to
 107  * handling the logging message itself, this often result in messages
 108  * being handled by the root logger as well. When setting this property
 109  * to false a Handler needs to be configured for this logger otherwise
 110  * no logging messages are delivered.
 111  *
 112  * <li>A property "config".  This property is intended to allow
 113  * arbitrary configuration code to be run.  The property defines a
 114  * whitespace or comma separated list of class names.  A new instance will be
 115  * created for each named class.  The default constructor of each class
 116  * may execute arbitrary code to update the logging configuration, such as
 117  * setting logger levels, adding handlers, adding filters, etc.
 118  * </ul>
 119  * <p>
 120  * Note that all classes loaded during LogManager configuration are
 121  * first searched on the system class path before any user class path.
 122  * That includes the LogManager class, any config classes, and any
 123  * handler classes.
 124  * <p>
 125  * Loggers are organized into a naming hierarchy based on their
 126  * dot separated names.  Thus "a.b.c" is a child of "a.b", but
 127  * "a.b1" and a.b2" are peers.
 128  * <p>
 129  * All properties whose names end with ".level" are assumed to define
 130  * log levels for Loggers.  Thus "foo.level" defines a log level for
 131  * the logger called "foo" and (recursively) for any of its children
 132  * in the naming hierarchy.  Log Levels are applied in the order they
 133  * are defined in the properties file.  Thus level settings for child
 134  * nodes in the tree should come after settings for their parents.
 135  * The property name ".level" can be used to set the level for the
 136  * root of the tree.
 137  * <p>
 138  * All methods on the LogManager object are multi-thread safe.
 139  *
 140  * @since 1.4
 141 */
 142 
 143 public class LogManager {
 144     // The global LogManager object
 145     private static LogManager manager;
 146 
 147     private final static Handler[] emptyHandlers = { };
 148     private Properties props = new Properties();
 149     private final static Level defaultLevel = Level.INFO;
 150 
 151     // The map of the registered listeners. The map value is the registration
 152     // count to allow for cases where the same listener is registered many times.
 153     private final Map<Object,Integer> listenerMap = new HashMap<>();
 154 
 155     // Table of named Loggers that maps names to Loggers.
 156     private Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
 157     // Tree of named Loggers
 158     private LogNode root = new LogNode(null);
 159     private Logger rootLogger;
 160 
 161     // Have we done the primordial reading of the configuration file?
 162     // (Must be done after a suitable amount of java.lang.System
 163     // initialization has been done)
 164     private volatile boolean readPrimordialConfiguration;
 165     // Have we initialized global (root) handlers yet?
 166     // This gets set to false in readConfiguration
 167     private boolean initializedGlobalHandlers = true;
 168     // True if JVM death is imminent and the exit hook has been called.
 169     private boolean deathImminent;
 170 
 171     static {
 172         AccessController.doPrivileged(new PrivilegedAction<Object>() {
 173                 public Object run() {
 174                     String cname = null;
 175                     try {
 176                         cname = System.getProperty("java.util.logging.manager");
 177                         if (cname != null) {
 178                             try {
 179                                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
 180                                 manager = (LogManager) clz.newInstance();
 181                             } catch (ClassNotFoundException ex) {
 182                                 Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
 183                                 manager = (LogManager) clz.newInstance();
 184                             }
 185                         }
 186                     } catch (Exception ex) {
 187                         System.err.println("Could not load Logmanager \"" + cname + "\"");
 188                         ex.printStackTrace();
 189                     }
 190                     if (manager == null) {
 191                         manager = new LogManager();
 192                     }
 193 
 194                     // Create and retain Logger for the root of the namespace.
 195                     manager.rootLogger = manager.new RootLogger();
 196                     manager.addLogger(manager.rootLogger);
 197 
 198                     // Adding the global Logger. Doing so in the Logger.<clinit>
 199                     // would deadlock with the LogManager.<clinit>.
 200                     Logger.getGlobal().setLogManager(manager);
 201                     manager.addLogger(Logger.getGlobal());
 202 
 203                     // We don't call readConfiguration() here, as we may be running
 204                     // very early in the JVM startup sequence.  Instead readConfiguration
 205                     // will be called lazily in getLogManager().
 206                     return null;
 207                 }
 208             });
 209     }
 210 
 211 
 212     // This private class is used as a shutdown hook.
 213     // It does a "reset" to close all open handlers.
 214     private class Cleaner extends Thread {
 215 
 216         private Cleaner() {
 217             /* Set context class loader to null in order to avoid
 218              * keeping a strong reference to an application classloader.
 219              */
 220             this.setContextClassLoader(null);
 221         }
 222 
 223         public void run() {
 224             // This is to ensure the LogManager.<clinit> is completed
 225             // before synchronized block. Otherwise deadlocks are possible.
 226             LogManager mgr = manager;
 227 
 228             // If the global handlers haven't been initialized yet, we
 229             // don't want to initialize them just so we can close them!
 230             synchronized (LogManager.this) {
 231                 // Note that death is imminent.
 232                 deathImminent = true;
 233                 initializedGlobalHandlers = true;
 234             }
 235 
 236             // Do a reset to close all active handlers.
 237             reset();
 238         }
 239     }
 240 
 241 
 242     /**
 243      * Protected constructor.  This is protected so that container applications
 244      * (such as J2EE containers) can subclass the object.  It is non-public as
 245      * it is intended that there only be one LogManager object, whose value is
 246      * retrieved by calling LogManager.getLogManager.
 247      */
 248     protected LogManager() {
 249         // Add a shutdown hook to close the global handlers.
 250         try {
 251             Runtime.getRuntime().addShutdownHook(new Cleaner());
 252         } catch (IllegalStateException e) {
 253             // If the VM is already shutting down,
 254             // We do not need to register shutdownHook.
 255         }
 256     }
 257 
 258     /**
 259      * Return the global LogManager object.
 260      */
 261     public static LogManager getLogManager() {
 262         if (manager != null) {
 263             manager.readPrimordialConfiguration();
 264         }
 265         return manager;
 266     }
 267 
 268     private void readPrimordialConfiguration() {
 269         if (!readPrimordialConfiguration) {
 270             synchronized (this) {
 271                 if (!readPrimordialConfiguration) {
 272                     // If System.in/out/err are null, it's a good
 273                     // indication that we're still in the
 274                     // bootstrapping phase
 275                     if (System.out == null) {
 276                         return;
 277                     }
 278                     readPrimordialConfiguration = true;
 279                     try {
 280                         AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
 281                                 public Object run() throws Exception {
 282                                     readConfiguration();
 283 
 284                                     // Platform loggers begin to delegate to java.util.logging.Logger
 285                                     sun.util.logging.PlatformLogger.redirectPlatformLoggers();
 286 
 287                                     return null;
 288                                 }
 289                             });
 290                     } catch (Exception ex) {
 291                         // System.err.println("Can't read logging configuration:");
 292                         // ex.printStackTrace();
 293                     }
 294                 }
 295             }
 296         }
 297     }
 298 
 299     /**
 300      * Adds an event listener to be invoked when the logging
 301      * properties are re-read. Adding multiple instances of
 302      * the same event Listener results in multiple entries
 303      * in the property event listener table.
 304      *
 305      * <p><b>WARNING:</b> This method is omitted from this class in all subset
 306      * Profiles of Java SE that do not include the {@code java.beans} package.
 307      * </p>
 308      *
 309      * @param l  event listener
 310      * @exception  SecurityException  if a security manager exists and if
 311      *             the caller does not have LoggingPermission("control").
 312      * @exception NullPointerException if the PropertyChangeListener is null.
 313      * @deprecated The dependency on {@code PropertyChangeListener} creates a
 314      *             significant impediment to future modularization of the Java
 315      *             platform. This method will be removed in a future release.
 316      *             The global {@code LogManager} can detect changes to the
 317      *             logging configuration by overridding the {@link
 318      *             #readConfiguration readConfiguration} method.
 319      */
 320     @Deprecated
 321     public void addPropertyChangeListener(PropertyChangeListener l) throws SecurityException {
 322         PropertyChangeListener listener = Objects.requireNonNull(l);
 323         checkPermission();
 324         synchronized (listenerMap) {
 325             // increment the registration count if already registered
 326             Integer value = listenerMap.get(listener);
 327             value = (value == null) ? 1 : (value + 1);
 328             listenerMap.put(listener, value);
 329         }
 330     }
 331 
 332     /**
 333      * Removes an event listener for property change events.
 334      * If the same listener instance has been added to the listener table
 335      * through multiple invocations of <CODE>addPropertyChangeListener</CODE>,
 336      * then an equivalent number of
 337      * <CODE>removePropertyChangeListener</CODE> invocations are required to remove
 338      * all instances of that listener from the listener table.
 339      * <P>
 340      * Returns silently if the given listener is not found.
 341      *
 342      * <p><b>WARNING:</b> This method is omitted from this class in all subset
 343      * Profiles of Java SE that do not include the {@code java.beans} package.
 344      * </p>
 345      *
 346      * @param l  event listener (can be null)
 347      * @exception  SecurityException  if a security manager exists and if
 348      *             the caller does not have LoggingPermission("control").
 349      * @deprecated The dependency on {@code PropertyChangeListener} creates a
 350      *             significant impediment to future modularization of the Java
 351      *             platform. This method will be removed in a future release.
 352      *             The global {@code LogManager} can detect changes to the
 353      *             logging configuration by overridding the {@link
 354      *             #readConfiguration readConfiguration} method.
 355      */
 356     @Deprecated
 357     public void removePropertyChangeListener(PropertyChangeListener l) throws SecurityException {
 358         checkPermission();
 359         if (l != null) {
 360             PropertyChangeListener listener = l;
 361             synchronized (listenerMap) {
 362                 Integer value = listenerMap.get(listener);
 363                 if (value != null) {
 364                     // remove from map if registration count is 1, otherwise
 365                     // just decrement its count
 366                     int i = value.intValue();
 367                     if (i == 1) {
 368                         listenerMap.remove(listener);
 369                     } else {
 370                         assert i > 1;
 371                         listenerMap.put(listener, i - 1);
 372                     }
 373                 }
 374             }
 375         }
 376     }
 377 
 378     // Package-level method.
 379     // Find or create a specified logger instance. If a logger has
 380     // already been created with the given name it is returned.
 381     // Otherwise a new logger instance is created and registered
 382     // in the LogManager global namespace.
 383 
 384     // This method will always return a non-null Logger object.
 385     // Synchronization is not required here. All synchronization for
 386     // adding a new Logger object is handled by addLogger().
 387     Logger demandLogger(String name) {
 388         Logger result = getLogger(name);
 389         if (result == null) {
 390             // only allocate the new logger once
 391             Logger newLogger = new Logger(name, null);
 392             do {
 393                 if (addLogger(newLogger)) {
 394                     // We successfully added the new Logger that we
 395                     // created above so return it without refetching.
 396                     return newLogger;
 397                 }
 398 
 399                 // We didn't add the new Logger that we created above
 400                 // because another thread added a Logger with the same
 401                 // name after our null check above and before our call
 402                 // to addLogger(). We have to refetch the Logger because
 403                 // addLogger() returns a boolean instead of the Logger
 404                 // reference itself. However, if the thread that created
 405                 // the other Logger is not holding a strong reference to
 406                 // the other Logger, then it is possible for the other
 407                 // Logger to be GC'ed after we saw it in addLogger() and
 408                 // before we can refetch it. If it has been GC'ed then
 409                 // we'll just loop around and try again.
 410                 result = getLogger(name);
 411             } while (result == null);
 412         }
 413         return result;
 414     }
 415 
 416     // If logger.getUseParentHandlers() returns 'true' and any of the logger's
 417     // parents have levels or handlers defined, make sure they are instantiated.
 418     private void processParentHandlers(Logger logger, String name) {
 419         int ix = 1;
 420         for (;;) {
 421             int ix2 = name.indexOf(".", ix);
 422             if (ix2 < 0) {
 423                 break;
 424             }
 425             String pname = name.substring(0,ix2);
 426 
 427             if (getProperty(pname+".level")    != null ||
 428                 getProperty(pname+".handlers") != null) {
 429                 // This pname has a level/handlers definition.
 430                 // Make sure it exists.
 431                 demandLogger(pname);
 432             }
 433             ix = ix2+1;
 434         }
 435     }
 436 
 437     // Add new per logger handlers.
 438     // We need to raise privilege here. All our decisions will
 439     // be made based on the logging configuration, which can
 440     // only be modified by trusted code.
 441     private void loadLoggerHandlers(final Logger logger, final String name,
 442                                     final String handlersPropertyName) {
 443         AccessController.doPrivileged(new PrivilegedAction<Object>() {
 444             public Object run() {
 445                 if (logger != rootLogger) {
 446                     boolean useParent = getBooleanProperty(name + ".useParentHandlers", true);
 447                     if (!useParent) {
 448                         logger.setUseParentHandlers(false);
 449                     }
 450                 }
 451 
 452                 String names[] = parseClassNames(handlersPropertyName);
 453                 for (int i = 0; i < names.length; i++) {
 454                     String word = names[i];
 455                     try {
 456                         Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word);
 457                         Handler  hdl = (Handler) clz.newInstance();
 458                         try {
 459                             // Check if there is a property defining the
 460                             // this handler's level.
 461                             String levs = getProperty(word + ".level");
 462                             if (levs != null) {
 463                                 hdl.setLevel(Level.parse(levs));
 464                             }
 465                         } catch (Exception ex) {
 466                             System.err.println("Can't set level for " + word);
 467                             // Probably a bad level. Drop through.
 468                         }
 469                         // Add this Handler to the logger
 470                         logger.addHandler(hdl);
 471                     } catch (Exception ex) {
 472                         System.err.println("Can't load log handler \"" + word + "\"");
 473                         System.err.println("" + ex);
 474                         ex.printStackTrace();
 475                     }
 476                 }
 477                 return null;
 478             }});
 479     }
 480 
 481 
 482     // loggerRefQueue holds LoggerWeakRef objects for Logger objects
 483     // that have been GC'ed.
 484     private final ReferenceQueue<Logger> loggerRefQueue
 485         = new ReferenceQueue<>();
 486 
 487     // Package-level inner class.
 488     // Helper class for managing WeakReferences to Logger objects.
 489     //
 490     // LogManager.namedLoggers
 491     //     - has weak references to all named Loggers
 492     //     - namedLoggers keeps the LoggerWeakRef objects for the named
 493     //       Loggers around until we can deal with the book keeping for
 494     //       the named Logger that is being GC'ed.
 495     // LogManager.LogNode.loggerRef
 496     //     - has a weak reference to a named Logger
 497     //     - the LogNode will also keep the LoggerWeakRef objects for
 498     //       the named Loggers around; currently LogNodes never go away.
 499     // Logger.kids
 500     //     - has a weak reference to each direct child Logger; this
 501     //       includes anonymous and named Loggers
 502     //     - anonymous Loggers are always children of the rootLogger
 503     //       which is a strong reference; rootLogger.kids keeps the
 504     //       LoggerWeakRef objects for the anonymous Loggers around
 505     //       until we can deal with the book keeping.
 506     //
 507     final class LoggerWeakRef extends WeakReference<Logger> {
 508         private String                name;       // for namedLoggers cleanup
 509         private LogNode               node;       // for loggerRef cleanup
 510         private WeakReference<Logger> parentRef;  // for kids cleanup
 511 
 512         LoggerWeakRef(Logger logger) {
 513             super(logger, loggerRefQueue);
 514 
 515             name = logger.getName();  // save for namedLoggers cleanup
 516         }
 517 
 518         // dispose of this LoggerWeakRef object
 519         void dispose() {
 520             if (node != null) {
 521                 // if we have a LogNode, then we were a named Logger
 522                 // so clear namedLoggers weak ref to us
 523                 manager.namedLoggers.remove(name);
 524                 name = null;  // clear our ref to the Logger's name
 525 
 526                 node.loggerRef = null;  // clear LogNode's weak ref to us
 527                 node = null;            // clear our ref to LogNode
 528             }
 529 
 530             if (parentRef != null) {
 531                 // this LoggerWeakRef has or had a parent Logger
 532                 Logger parent = parentRef.get();
 533                 if (parent != null) {
 534                     // the parent Logger is still there so clear the
 535                     // parent Logger's weak ref to us
 536                     parent.removeChildLogger(this);
 537                 }
 538                 parentRef = null;  // clear our weak ref to the parent Logger
 539             }
 540         }
 541 
 542         // set the node field to the specified value
 543         void setNode(LogNode node) {
 544             this.node = node;
 545         }
 546 
 547         // set the parentRef field to the specified value
 548         void setParentRef(WeakReference<Logger> parentRef) {
 549             this.parentRef = parentRef;
 550         }
 551     }
 552 
 553     // Package-level method.
 554     // Drain some Logger objects that have been GC'ed.
 555     //
 556     // drainLoggerRefQueueBounded() is called by addLogger() below
 557     // and by Logger.getAnonymousLogger(String) so we'll drain up to
 558     // MAX_ITERATIONS GC'ed Loggers for every Logger we add.
 559     //
 560     // On a WinXP VMware client, a MAX_ITERATIONS value of 400 gives
 561     // us about a 50/50 mix in increased weak ref counts versus
 562     // decreased weak ref counts in the AnonLoggerWeakRefLeak test.
 563     // Here are stats for cleaning up sets of 400 anonymous Loggers:
 564     //   - test duration 1 minute
 565     //   - sample size of 125 sets of 400
 566     //   - average: 1.99 ms
 567     //   - minimum: 0.57 ms
 568     //   - maximum: 25.3 ms
 569     //
 570     // The same config gives us a better decreased weak ref count
 571     // than increased weak ref count in the LoggerWeakRefLeak test.
 572     // Here are stats for cleaning up sets of 400 named Loggers:
 573     //   - test duration 2 minutes
 574     //   - sample size of 506 sets of 400
 575     //   - average: 0.57 ms
 576     //   - minimum: 0.02 ms
 577     //   - maximum: 10.9 ms
 578     //
 579     private final static int MAX_ITERATIONS = 400;
 580     final synchronized void drainLoggerRefQueueBounded() {
 581         for (int i = 0; i < MAX_ITERATIONS; i++) {
 582             if (loggerRefQueue == null) {
 583                 // haven't finished loading LogManager yet
 584                 break;
 585             }
 586 
 587             LoggerWeakRef ref = (LoggerWeakRef) loggerRefQueue.poll();
 588             if (ref == null) {
 589                 break;
 590             }
 591             // a Logger object has been GC'ed so clean it up
 592             ref.dispose();
 593         }
 594     }
 595 
 596     /**
 597      * Add a named logger.  This does nothing and returns false if a logger
 598      * with the same name is already registered.
 599      * <p>
 600      * The Logger factory methods call this method to register each
 601      * newly created Logger.
 602      * <p>
 603      * The application should retain its own reference to the Logger
 604      * object to avoid it being garbage collected.  The LogManager
 605      * may only retain a weak reference.
 606      *
 607      * @param   logger the new logger.
 608      * @return  true if the argument logger was registered successfully,
 609      *          false if a logger of that name already exists.
 610      * @exception NullPointerException if the logger name is null.
 611      */
 612     public synchronized boolean addLogger(Logger logger) {
 613         final String name = logger.getName();
 614         if (name == null) {
 615             throw new NullPointerException();
 616         }
 617 
 618         // cleanup some Loggers that have been GC'ed
 619         drainLoggerRefQueueBounded();
 620 
 621         LoggerWeakRef ref = namedLoggers.get(name);
 622         if (ref != null) {
 623             if (ref.get() == null) {
 624                 // It's possible that the Logger was GC'ed after the
 625                 // drainLoggerRefQueueBounded() call above so allow
 626                 // a new one to be registered.
 627                 namedLoggers.remove(name);
 628             } else {
 629                 // We already have a registered logger with the given name.
 630                 return false;
 631             }
 632         }
 633 
 634         // We're adding a new logger.
 635         // Note that we are creating a weak reference here.
 636         ref = new LoggerWeakRef(logger);
 637         namedLoggers.put(name, ref);
 638 
 639         // Apply any initial level defined for the new logger.
 640         Level level = getLevelProperty(name+".level", null);
 641         if (level != null) {
 642             doSetLevel(logger, level);
 643         }
 644 
 645         // Do we have a per logger handler too?
 646         // Note: this will add a 200ms penalty
 647         loadLoggerHandlers(logger, name, name+".handlers");
 648         processParentHandlers(logger, name);
 649 
 650         // Find the new node and its parent.
 651         LogNode node = findNode(name);
 652         node.loggerRef = ref;
 653         Logger parent = null;
 654         LogNode nodep = node.parent;
 655         while (nodep != null) {
 656             LoggerWeakRef nodeRef = nodep.loggerRef;
 657             if (nodeRef != null) {
 658                 parent = nodeRef.get();
 659                 if (parent != null) {
 660                     break;
 661                 }
 662             }
 663             nodep = nodep.parent;
 664         }
 665 
 666         if (parent != null) {
 667             doSetParent(logger, parent);
 668         }
 669         // Walk over the children and tell them we are their new parent.
 670         node.walkAndSetParent(logger);
 671 
 672         // new LogNode is ready so tell the LoggerWeakRef about it
 673         ref.setNode(node);
 674 
 675         return true;
 676     }
 677 
 678 
 679     // Private method to set a level on a logger.
 680     // If necessary, we raise privilege before doing the call.
 681     private static void doSetLevel(final Logger logger, final Level level) {
 682         SecurityManager sm = System.getSecurityManager();
 683         if (sm == null) {
 684             // There is no security manager, so things are easy.
 685             logger.setLevel(level);
 686             return;
 687         }
 688         // There is a security manager.  Raise privilege before
 689         // calling setLevel.
 690         AccessController.doPrivileged(new PrivilegedAction<Object>() {
 691             public Object run() {
 692                 logger.setLevel(level);
 693                 return null;
 694             }});
 695     }
 696 
 697 
 698 
 699     // Private method to set a parent on a logger.
 700     // If necessary, we raise privilege before doing the setParent call.
 701     private static void doSetParent(final Logger logger, final Logger parent) {
 702         SecurityManager sm = System.getSecurityManager();
 703         if (sm == null) {
 704             // There is no security manager, so things are easy.
 705             logger.setParent(parent);
 706             return;
 707         }
 708         // There is a security manager.  Raise privilege before
 709         // calling setParent.
 710         AccessController.doPrivileged(new PrivilegedAction<Object>() {
 711             public Object run() {
 712                 logger.setParent(parent);
 713                 return null;
 714             }});
 715     }
 716 
 717     // Find a node in our tree of logger nodes.
 718     // If necessary, create it.
 719     private LogNode findNode(String name) {
 720         if (name == null || name.equals("")) {
 721             return root;
 722         }
 723         LogNode node = root;
 724         while (name.length() > 0) {
 725             int ix = name.indexOf(".");
 726             String head;
 727             if (ix > 0) {
 728                 head = name.substring(0,ix);
 729                 name = name.substring(ix+1);
 730             } else {
 731                 head = name;
 732                 name = "";
 733             }
 734             if (node.children == null) {
 735                 node.children = new HashMap<>();
 736             }
 737             LogNode child = node.children.get(head);
 738             if (child == null) {
 739                 child = new LogNode(node);
 740                 node.children.put(head, child);
 741             }
 742             node = child;
 743         }
 744         return node;
 745     }
 746 
 747     /**
 748      * Method to find a named logger.
 749      * <p>
 750      * Note that since untrusted code may create loggers with
 751      * arbitrary names this method should not be relied on to
 752      * find Loggers for security sensitive logging.
 753      * It is also important to note that the Logger associated with the
 754      * String {@code name} may be garbage collected at any time if there
 755      * is no strong reference to the Logger. The caller of this method
 756      * must check the return value for null in order to properly handle
 757      * the case where the Logger has been garbage collected.
 758      * <p>
 759      * @param name name of the logger
 760      * @return  matching logger or null if none is found
 761      */
 762     public synchronized Logger getLogger(String name) {
 763         LoggerWeakRef ref = namedLoggers.get(name);
 764         if (ref == null) {
 765             return null;
 766         }
 767         Logger logger = ref.get();
 768         if (logger == null) {
 769             // Hashtable holds stale weak reference
 770             // to a logger which has been GC-ed.
 771             namedLoggers.remove(name);
 772         }
 773         return logger;
 774     }
 775 
 776     /**
 777      * Get an enumeration of known logger names.
 778      * <p>
 779      * Note:  Loggers may be added dynamically as new classes are loaded.
 780      * This method only reports on the loggers that are currently registered.
 781      * It is also important to note that this method only returns the name
 782      * of a Logger, not a strong reference to the Logger itself.
 783      * The returned String does nothing to prevent the Logger from being
 784      * garbage collected. In particular, if the returned name is passed
 785      * to {@code LogManager.getLogger()}, then the caller must check the
 786      * return value from {@code LogManager.getLogger()} for null to properly
 787      * handle the case where the Logger has been garbage collected in the
 788      * time since its name was returned by this method.
 789      * <p>
 790      * @return  enumeration of logger name strings
 791      */
 792     public synchronized Enumeration<String> getLoggerNames() {
 793         return namedLoggers.keys();
 794     }
 795 
 796     /**
 797      * Reinitialize the logging properties and reread the logging configuration.
 798      * <p>
 799      * The same rules are used for locating the configuration properties
 800      * as are used at startup.  So normally the logging properties will
 801      * be re-read from the same file that was used at startup.
 802      * <P>
 803      * Any log level definitions in the new configuration file will be
 804      * applied using Logger.setLevel(), if the target Logger exists.
 805      * <p>
 806      * A PropertyChangeEvent will be fired after the properties are read.
 807      *
 808      * @exception  SecurityException  if a security manager exists and if
 809      *             the caller does not have LoggingPermission("control").
 810      * @exception  IOException if there are IO problems reading the configuration.
 811      */
 812     public void readConfiguration() throws IOException, SecurityException {
 813         checkPermission();
 814 
 815         // if a configuration class is specified, load it and use it.
 816         String cname = System.getProperty("java.util.logging.config.class");
 817         if (cname != null) {
 818             try {
 819                 // Instantiate the named class.  It is its constructor's
 820                 // responsibility to initialize the logging configuration, by
 821                 // calling readConfiguration(InputStream) with a suitable stream.
 822                 try {
 823                     Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
 824                     clz.newInstance();
 825                     return;
 826                 } catch (ClassNotFoundException ex) {
 827                     Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
 828                     clz.newInstance();
 829                     return;
 830                 }
 831             } catch (Exception ex) {
 832                 System.err.println("Logging configuration class \"" + cname + "\" failed");
 833                 System.err.println("" + ex);
 834                 // keep going and useful config file.
 835             }
 836         }
 837 
 838         String fname = System.getProperty("java.util.logging.config.file");
 839         if (fname == null) {
 840             fname = System.getProperty("java.home");
 841             if (fname == null) {
 842                 throw new Error("Can't find java.home ??");
 843             }
 844             File f = new File(fname, "lib");
 845             f = new File(f, "logging.properties");
 846             fname = f.getCanonicalPath();
 847         }
 848         InputStream in = new FileInputStream(fname);
 849         BufferedInputStream bin = new BufferedInputStream(in);
 850         try {
 851             readConfiguration(bin);
 852         } finally {
 853             if (in != null) {
 854                 in.close();
 855             }
 856         }
 857     }
 858 
 859     /**
 860      * Reset the logging configuration.
 861      * <p>
 862      * For all named loggers, the reset operation removes and closes
 863      * all Handlers and (except for the root logger) sets the level
 864      * to null.  The root logger's level is set to Level.INFO.
 865      *
 866      * @exception  SecurityException  if a security manager exists and if
 867      *             the caller does not have LoggingPermission("control").
 868      */
 869 
 870     public void reset() throws SecurityException {
 871         checkPermission();
 872         synchronized (this) {
 873             props = new Properties();
 874             // Since we are doing a reset we no longer want to initialize
 875             // the global handlers, if they haven't been initialized yet.
 876             initializedGlobalHandlers = true;
 877         }
 878         Enumeration<String> enum_ = getLoggerNames();
 879         while (enum_.hasMoreElements()) {
 880             String name = enum_.nextElement();
 881             resetLogger(name);
 882         }
 883     }
 884 
 885 
 886     // Private method to reset an individual target logger.
 887     private void resetLogger(String name) {
 888         Logger logger = getLogger(name);
 889         if (logger == null) {
 890             return;
 891         }
 892         // Close all the Logger's handlers.
 893         Handler[] targets = logger.getHandlers();
 894         for (int i = 0; i < targets.length; i++) {
 895             Handler h = targets[i];
 896             logger.removeHandler(h);
 897             try {
 898                 h.close();
 899             } catch (Exception ex) {
 900                 // Problems closing a handler?  Keep going...
 901             }
 902         }
 903         if (name != null && name.equals("")) {
 904             // This is the root logger.
 905             logger.setLevel(defaultLevel);
 906         } else {
 907             logger.setLevel(null);
 908         }
 909     }
 910 
 911     // get a list of whitespace separated classnames from a property.
 912     private String[] parseClassNames(String propertyName) {
 913         String hands = getProperty(propertyName);
 914         if (hands == null) {
 915             return new String[0];
 916         }
 917         hands = hands.trim();
 918         int ix = 0;
 919         Vector<String> result = new Vector<>();
 920         while (ix < hands.length()) {
 921             int end = ix;
 922             while (end < hands.length()) {
 923                 if (Character.isWhitespace(hands.charAt(end))) {
 924                     break;
 925                 }
 926                 if (hands.charAt(end) == ',') {
 927                     break;
 928                 }
 929                 end++;
 930             }
 931             String word = hands.substring(ix, end);
 932             ix = end+1;
 933             word = word.trim();
 934             if (word.length() == 0) {
 935                 continue;
 936             }
 937             result.add(word);
 938         }
 939         return result.toArray(new String[result.size()]);
 940     }
 941 
 942     /**
 943      * Reinitialize the logging properties and reread the logging configuration
 944      * from the given stream, which should be in java.util.Properties format.
 945      * A PropertyChangeEvent will be fired after the properties are read.
 946      * <p>
 947      * Any log level definitions in the new configuration file will be
 948      * applied using Logger.setLevel(), if the target Logger exists.
 949      *
 950      * @param ins       stream to read properties from
 951      * @exception  SecurityException  if a security manager exists and if
 952      *             the caller does not have LoggingPermission("control").
 953      * @exception  IOException if there are problems reading from the stream.
 954      */
 955     public void readConfiguration(InputStream ins) throws IOException, SecurityException {
 956         checkPermission();
 957         reset();
 958 
 959         // Load the properties
 960         props.load(ins);
 961         // Instantiate new configuration objects.
 962         String names[] = parseClassNames("config");
 963 
 964         for (int i = 0; i < names.length; i++) {
 965             String word = names[i];
 966             try {
 967                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word);
 968                 clz.newInstance();
 969             } catch (Exception ex) {
 970                 System.err.println("Can't load config class \"" + word + "\"");
 971                 System.err.println("" + ex);
 972                 // ex.printStackTrace();
 973             }
 974         }
 975 
 976         // Set levels on any pre-existing loggers, based on the new properties.
 977         setLevelsOnExistingLoggers();
 978 
 979         // Notify any interested parties that our properties have changed.
 980         // We first take a copy of the listener map so that we aren't holding any
 981         // locks when calling the listeners.
 982         Map<Object,Integer> listeners = null;
 983         synchronized (listenerMap) {
 984             if (!listenerMap.isEmpty())
 985                 listeners = new HashMap<>(listenerMap);
 986         }
 987         if (listeners != null) {
 988             assert Beans.isBeansPresent();
 989             Object ev = Beans.newPropertyChangeEvent(LogManager.class, null, null, null);
 990             for (Map.Entry<Object,Integer> entry : listeners.entrySet()) {
 991                 Object listener = entry.getKey();
 992                 int count = entry.getValue().intValue();
 993                 for (int i = 0; i < count; i++) {
 994                     Beans.invokePropertyChange(listener, ev);
 995                 }
 996             }
 997         }
 998 
 999 
1000         // Note that we need to reinitialize global handles when
1001         // they are first referenced.
1002         synchronized (this) {
1003             initializedGlobalHandlers = false;
1004         }
1005     }
1006 
1007     /**
1008      * Get the value of a logging property.
1009      * The method returns null if the property is not found.
1010      * @param name      property name
1011      * @return          property value
1012      */
1013     public String getProperty(String name) {
1014         return props.getProperty(name);
1015     }
1016 
1017     // Package private method to get a String property.
1018     // If the property is not defined we return the given
1019     // default value.
1020     String getStringProperty(String name, String defaultValue) {
1021         String val = getProperty(name);
1022         if (val == null) {
1023             return defaultValue;
1024         }
1025         return val.trim();
1026     }
1027 
1028     // Package private method to get an integer property.
1029     // If the property is not defined or cannot be parsed
1030     // we return the given default value.
1031     int getIntProperty(String name, int defaultValue) {
1032         String val = getProperty(name);
1033         if (val == null) {
1034             return defaultValue;
1035         }
1036         try {
1037             return Integer.parseInt(val.trim());
1038         } catch (Exception ex) {
1039             return defaultValue;
1040         }
1041     }
1042 
1043     // Package private method to get a boolean property.
1044     // If the property is not defined or cannot be parsed
1045     // we return the given default value.
1046     boolean getBooleanProperty(String name, boolean defaultValue) {
1047         String val = getProperty(name);
1048         if (val == null) {
1049             return defaultValue;
1050         }
1051         val = val.toLowerCase();
1052         if (val.equals("true") || val.equals("1")) {
1053             return true;
1054         } else if (val.equals("false") || val.equals("0")) {
1055             return false;
1056         }
1057         return defaultValue;
1058     }
1059 
1060     // Package private method to get a Level property.
1061     // If the property is not defined or cannot be parsed
1062     // we return the given default value.
1063     Level getLevelProperty(String name, Level defaultValue) {
1064         String val = getProperty(name);
1065         if (val == null) {
1066             return defaultValue;
1067         }
1068         try {
1069             return Level.parse(val.trim());
1070         } catch (Exception ex) {
1071             return defaultValue;
1072         }
1073     }
1074 
1075     // Package private method to get a filter property.
1076     // We return an instance of the class named by the "name"
1077     // property. If the property is not defined or has problems
1078     // we return the defaultValue.
1079     Filter getFilterProperty(String name, Filter defaultValue) {
1080         String val = getProperty(name);
1081         try {
1082             if (val != null) {
1083                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(val);
1084                 return (Filter) clz.newInstance();
1085             }
1086         } catch (Exception ex) {
1087             // We got one of a variety of exceptions in creating the
1088             // class or creating an instance.
1089             // Drop through.
1090         }
1091         // We got an exception.  Return the defaultValue.
1092         return defaultValue;
1093     }
1094 
1095 
1096     // Package private method to get a formatter property.
1097     // We return an instance of the class named by the "name"
1098     // property. If the property is not defined or has problems
1099     // we return the defaultValue.
1100     Formatter getFormatterProperty(String name, Formatter defaultValue) {
1101         String val = getProperty(name);
1102         try {
1103             if (val != null) {
1104                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(val);
1105                 return (Formatter) clz.newInstance();
1106             }
1107         } catch (Exception ex) {
1108             // We got one of a variety of exceptions in creating the
1109             // class or creating an instance.
1110             // Drop through.
1111         }
1112         // We got an exception.  Return the defaultValue.
1113         return defaultValue;
1114     }
1115 
1116     // Private method to load the global handlers.
1117     // We do the real work lazily, when the global handlers
1118     // are first used.
1119     private synchronized void initializeGlobalHandlers() {
1120         if (initializedGlobalHandlers) {
1121             return;
1122         }
1123 
1124         initializedGlobalHandlers = true;
1125 
1126         if (deathImminent) {
1127             // Aaargh...
1128             // The VM is shutting down and our exit hook has been called.
1129             // Avoid allocating global handlers.
1130             return;
1131         }
1132         loadLoggerHandlers(rootLogger, null, "handlers");
1133     }
1134 
1135     private final Permission controlPermission = new LoggingPermission("control", null);
1136 
1137     void checkPermission() {
1138         SecurityManager sm = System.getSecurityManager();
1139         if (sm != null)
1140             sm.checkPermission(controlPermission);
1141     }
1142 
1143     /**
1144      * Check that the current context is trusted to modify the logging
1145      * configuration.  This requires LoggingPermission("control").
1146      * <p>
1147      * If the check fails we throw a SecurityException, otherwise
1148      * we return normally.
1149      *
1150      * @exception  SecurityException  if a security manager exists and if
1151      *             the caller does not have LoggingPermission("control").
1152      */
1153     public void checkAccess() throws SecurityException {
1154         checkPermission();
1155     }
1156 
1157     // Nested class to represent a node in our tree of named loggers.
1158     private static class LogNode {
1159         HashMap<String,LogNode> children;
1160         LoggerWeakRef loggerRef;
1161         LogNode parent;
1162 
1163         LogNode(LogNode parent) {
1164             this.parent = parent;
1165         }
1166 
1167         // Recursive method to walk the tree below a node and set
1168         // a new parent logger.
1169         void walkAndSetParent(Logger parent) {
1170             if (children == null) {
1171                 return;
1172             }
1173             Iterator<LogNode> values = children.values().iterator();
1174             while (values.hasNext()) {
1175                 LogNode node = values.next();
1176                 LoggerWeakRef ref = node.loggerRef;
1177                 Logger logger = (ref == null) ? null : ref.get();
1178                 if (logger == null) {
1179                     node.walkAndSetParent(parent);
1180                 } else {
1181                     doSetParent(logger, parent);
1182                 }
1183             }
1184         }
1185     }
1186 
1187     // We use a subclass of Logger for the root logger, so
1188     // that we only instantiate the global handlers when they
1189     // are first needed.
1190     private class RootLogger extends Logger {
1191 
1192         private RootLogger() {
1193             super("", null);
1194             setLevel(defaultLevel);
1195         }
1196 
1197         public void log(LogRecord record) {
1198             // Make sure that the global handlers have been instantiated.
1199             initializeGlobalHandlers();
1200             super.log(record);
1201         }
1202 
1203         public void addHandler(Handler h) {
1204             initializeGlobalHandlers();
1205             super.addHandler(h);
1206         }
1207 
1208         public void removeHandler(Handler h) {
1209             initializeGlobalHandlers();
1210             super.removeHandler(h);
1211         }
1212 
1213         public Handler[] getHandlers() {
1214             initializeGlobalHandlers();
1215             return super.getHandlers();
1216         }
1217     }
1218 
1219 
1220     // Private method to be called when the configuration has
1221     // changed to apply any level settings to any pre-existing loggers.
1222     synchronized private void setLevelsOnExistingLoggers() {
1223         Enumeration<?> enum_ = props.propertyNames();
1224         while (enum_.hasMoreElements()) {
1225             String key = (String)enum_.nextElement();
1226             if (!key.endsWith(".level")) {
1227                 // Not a level definition.
1228                 continue;
1229             }
1230             int ix = key.length() - 6;
1231             String name = key.substring(0, ix);
1232             Level level = getLevelProperty(key, null);
1233             if (level == null) {
1234                 System.err.println("Bad level value for property: " + key);
1235                 continue;
1236             }
1237             Logger l = getLogger(name);
1238             if (l == null) {
1239                 continue;
1240             }
1241             l.setLevel(level);
1242         }
1243     }
1244 
1245     // Management Support
1246     private static LoggingMXBean loggingMXBean = null;
1247     /**
1248      * String representation of the
1249      * {@link javax.management.ObjectName} for the management interface
1250      * for the logging facility.
1251      *
1252      * @see java.lang.management.PlatformLoggingMXBean
1253      * @see java.util.logging.LoggingMXBean
1254      *
1255      * @since 1.5
1256      */
1257     public final static String LOGGING_MXBEAN_NAME
1258         = "java.util.logging:type=Logging";
1259 
1260     /**
1261      * Returns <tt>LoggingMXBean</tt> for managing loggers.
1262      * An alternative way to manage loggers is through the
1263      * {@link java.lang.management.PlatformLoggingMXBean} interface
1264      * that can be obtained by calling:
1265      * <pre>
1266      *     PlatformLoggingMXBean logging = {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class)
1267      *         ManagementFactory.getPlatformMXBean}(PlatformLoggingMXBean.class);
1268      * </pre>
1269      *
1270      * @return a {@link LoggingMXBean} object.
1271      *
1272      * @see java.lang.management.PlatformLoggingMXBean
1273      * @since 1.5
1274      */
1275     public static synchronized LoggingMXBean getLoggingMXBean() {
1276         if (loggingMXBean == null) {
1277             loggingMXBean =  new Logging();
1278         }
1279         return loggingMXBean;
1280     }
1281 
1282     /**
1283      * A class that provides access to the java.beans.PropertyChangeListener
1284      * and java.beans.PropertyChangeEvent without creating a static dependency
1285      * on java.beans. This class can be removed once the addPropertyChangeListener
1286      * and removePropertyChangeListener methods are removed.
1287      */
1288     private static class Beans {
1289         private static final Class<?> propertyChangeListenerClass =
1290             getClass("java.beans.PropertyChangeListener");
1291 
1292         private static final Class<?> propertyChangeEventClass =
1293             getClass("java.beans.PropertyChangeEvent");
1294 
1295         private static final Method propertyChangeMethod =
1296             getMethod(propertyChangeListenerClass,
1297                       "propertyChange",
1298                       propertyChangeEventClass);
1299 
1300         private static final Constructor<?> propertyEventCtor =
1301             getConstructor(propertyChangeEventClass,
1302                            Object.class,
1303                            String.class,
1304                            Object.class,
1305                            Object.class);
1306 
1307         private static Class<?> getClass(String name) {
1308             try {
1309                 return Class.forName(name, true, Beans.class.getClassLoader());
1310             } catch (ClassNotFoundException e) {
1311                 return null;
1312             }
1313         }
1314         private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) {
1315             try {
1316                 return (c == null) ? null : c.getDeclaredConstructor(types);
1317             } catch (NoSuchMethodException x) {
1318                 throw new AssertionError(x);
1319             }
1320         }
1321 
1322         private static Method getMethod(Class<?> c, String name, Class<?>... types) {
1323             try {
1324                 return (c == null) ? null : c.getMethod(name, types);
1325             } catch (NoSuchMethodException e) {
1326                 throw new AssertionError(e);
1327             }
1328         }
1329 
1330         /**
1331          * Returns {@code true} if java.beans is present.
1332          */
1333         static boolean isBeansPresent() {
1334             return propertyChangeListenerClass != null &&
1335                    propertyChangeEventClass != null;
1336         }
1337 
1338         /**
1339          * Returns a new PropertyChangeEvent with the given source, property
1340          * name, old and new values.
1341          */
1342         static Object newPropertyChangeEvent(Object source, String prop,
1343                                              Object oldValue, Object newValue)
1344         {
1345             try {
1346                 return propertyEventCtor.newInstance(source, prop, oldValue, newValue);
1347             } catch (InstantiationException | IllegalAccessException x) {
1348                 throw new AssertionError(x);
1349             } catch (InvocationTargetException x) {
1350                 Throwable cause = x.getCause();
1351                 if (cause instanceof Error)
1352                     throw (Error)cause;
1353                 if (cause instanceof RuntimeException)
1354                     throw (RuntimeException)cause;
1355                 throw new AssertionError(x);
1356             }
1357         }
1358 
1359         /**
1360          * Invokes the given PropertyChangeListener's propertyChange method
1361          * with the given event.
1362          */
1363         static void invokePropertyChange(Object listener, Object ev) {
1364             try {
1365                 propertyChangeMethod.invoke(listener, ev);
1366             } catch (IllegalAccessException x) {
1367                 throw new AssertionError(x);
1368             } catch (InvocationTargetException x) {
1369                 Throwable cause = x.getCause();
1370                 if (cause instanceof Error)
1371                     throw (Error)cause;
1372                 if (cause instanceof RuntimeException)
1373                     throw (RuntimeException)cause;
1374                 throw new AssertionError(x);
1375             }
1376         }
1377     }
1378 }