LogRecord.java, [...]: New files from classpath.
authorMichael Koch <konqueror@gmx.de>
Sat, 21 Jun 2003 10:31:55 +0000 (10:31 +0000)
committerMichael Koch <mkoch@gcc.gnu.org>
Sat, 21 Jun 2003 10:31:55 +0000 (10:31 +0000)
2003-06-21  Michael Koch  <konqueror@gmx.de>

* java/util/logging/LogRecord.java,
java/util/logging/Logger.java,
java/util/logging/SocketHandler.java,
java/util/logging/SimpleFormatter.java,
java/util/logging/Formatter.java,
java/util/logging/ErrorManager.java,
java/util/logging/Handler.java,
java/util/logging/FileHandler.java,
java/util/logging/LogManager.java,
java/util/logging/Level.java,
java/util/logging/ConsoleHandler.java,
java/util/logging/StreamHandler.java,
java/util/logging/LoggingPermission.java,
java/util/logging/Filter.java,
java/util/logging/MemoryHandler.java,
java/util/logging/XMLFormatter.java:
New files from classpath.

From-SVN: r68295

17 files changed:
libjava/ChangeLog
libjava/java/util/logging/ConsoleHandler.java [new file with mode: 0644]
libjava/java/util/logging/ErrorManager.java [new file with mode: 0644]
libjava/java/util/logging/FileHandler.java [new file with mode: 0644]
libjava/java/util/logging/Filter.java [new file with mode: 0644]
libjava/java/util/logging/Formatter.java [new file with mode: 0644]
libjava/java/util/logging/Handler.java [new file with mode: 0644]
libjava/java/util/logging/Level.java [new file with mode: 0644]
libjava/java/util/logging/LogManager.java [new file with mode: 0644]
libjava/java/util/logging/LogRecord.java [new file with mode: 0644]
libjava/java/util/logging/Logger.java [new file with mode: 0644]
libjava/java/util/logging/LoggingPermission.java [new file with mode: 0644]
libjava/java/util/logging/MemoryHandler.java [new file with mode: 0644]
libjava/java/util/logging/SimpleFormatter.java [new file with mode: 0644]
libjava/java/util/logging/SocketHandler.java [new file with mode: 0644]
libjava/java/util/logging/StreamHandler.java [new file with mode: 0644]
libjava/java/util/logging/XMLFormatter.java [new file with mode: 0644]

index 9c2413a55132f84619ac7466fa3117d88b327a65..d00354d257b784e81ea23b2c5cd81615f91aab5c 100644 (file)
@@ -1,3 +1,23 @@
+2003-06-21  Michael Koch  <konqueror@gmx.de>
+
+       * java/util/logging/LogRecord.java,
+       java/util/logging/Logger.java,
+       java/util/logging/SocketHandler.java,
+       java/util/logging/SimpleFormatter.java,
+       java/util/logging/Formatter.java,
+       java/util/logging/ErrorManager.java,
+       java/util/logging/Handler.java,
+       java/util/logging/FileHandler.java,
+       java/util/logging/LogManager.java,
+       java/util/logging/Level.java,
+       java/util/logging/ConsoleHandler.java,
+       java/util/logging/StreamHandler.java,
+       java/util/logging/LoggingPermission.java,
+       java/util/logging/Filter.java,
+       java/util/logging/MemoryHandler.java,
+       java/util/logging/XMLFormatter.java:
+       New files from classpath.
+
 2003-06-20  Michael Koch  <konqueror@gmx.de>
 
        * java/io/ObjectStreamField.java
diff --git a/libjava/java/util/logging/ConsoleHandler.java b/libjava/java/util/logging/ConsoleHandler.java
new file mode 100644 (file)
index 0000000..dd519b6
--- /dev/null
@@ -0,0 +1,129 @@
+/* ConsoleHandler.java
+   -- a class for publishing log messages to System.err
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+/**
+ * A <code>ConsoleHandler</code> publishes log records to
+ * <code>System.err</code>.
+ *
+ * <p><strong>Configuration:</strong> Values of the subsequent
+ * <code>LogManager</code> properties are taken into consideration
+ * when a <code>ConsoleHandler</code> is initialized.
+ * If a property is not defined, or if it has an invalid
+ * value, a default is taken without an exception being thrown.
+ *
+ * <ul>
+ *
+ * <li><code>java.util.logging.ConsoleHandler.level</code> - specifies
+ *     the initial severity level threshold. Default value:
+ *     <code>Level.INFO</code>.</li>
+ *
+ * <li><code>java.util.logging.ConsoleHandler.filter</code> - specifies
+ *     the name of a Filter class. Default value: No Filter.</li>
+ *
+ * <li><code>java.util.logging.ConsoleHandler.formatter</code> - specifies
+ *     the name of a Formatter class. Default value:
+ *     <code>java.util.logging.SimpleFormatter</code>.</li>
+ *
+ * <li><code>java.util.logging.ConsoleHandler.encoding</code> - specifies
+ *     the name of the character encoding. Default value:
+ *     the default platform encoding.
+ *
+ * </ul>
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class ConsoleHandler
+  extends StreamHandler
+{
+  /**
+   * Constructs a <code>StreamHandler</code> that publishes
+   * log records to <code>System.err</code>.  The initial
+   * configuration is determined by the <code>LogManager</code>
+   * properties described above.
+   */
+  public ConsoleHandler()
+  {
+    super(System.err, "java.util.logging.ConsoleHandler", Level.INFO,
+        /* formatter */ null, SimpleFormatter.class);
+  }
+
+
+  /**
+   * Forces any data that may have been buffered to the underlying
+   * output device, but does <i>not</i> close <code>System.err</code>.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>ConsoleHandler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   */
+  public void close()
+  {
+    flush();
+  }
+
+
+  /**
+   * Publishes a <code>LogRecord</code> to the console, provided the
+   * record passes all tests for being loggable.
+   *
+   * <p>Most applications do not need to call this method directly.
+   * Instead, they will use use a <code>Logger</code>, which will
+   * create LogRecords and distribute them to registered handlers.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>SocketHandler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   *
+   * <p>The GNU implementation of <code>ConsoleHandler.publish</code>
+   * calls flush() for every request to publish a record, so
+   * they appear immediately on the console.
+   *
+   * @param record the log event to be published.
+   */
+  public void publish(LogRecord record)
+  {
+    super.publish(record);
+    flush();
+  }
+}
diff --git a/libjava/java/util/logging/ErrorManager.java b/libjava/java/util/logging/ErrorManager.java
new file mode 100644 (file)
index 0000000..cc36bf6
--- /dev/null
@@ -0,0 +1,182 @@
+/* ErrorManager.java
+   -- a class for dealing with errors that a Handler encounters
+      during logging
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+/**
+ * An <code>ErrorManager</code> deals with errors that a <code>Handler</code>
+ * encounters while logging.
+ *
+ * @see Handler#setErrorManager(ErrorManager)
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class ErrorManager
+{
+  /* The values have been taken from Sun's public J2SE 1.4 API
+   * documentation.
+   * See http://java.sun.com/j2se/1.4/docs/api/constant-values.html
+   */
+
+  /**
+   * Indicates that there was a failure that does not readily
+   * fall into any of the other categories.
+   */
+  public static final int GENERIC_FAILURE = 0;
+
+
+  /**
+   * Indicates that there was a problem upon writing to
+   * an output stream.
+   */
+  public static final int WRITE_FAILURE = 1;
+
+
+  /**
+   * Indicates that there was a problem upon flushing
+   * an output stream.
+   */
+  public static final int FLUSH_FAILURE = 2;
+
+
+  /**
+   * Indicates that there was a problem upon closing
+   * an output stream.
+   */
+  public static final int CLOSE_FAILURE = 3;
+
+    
+  /**
+   * Indicates that there was a problem upon opening
+   * an output stream.
+   */
+  public static final int OPEN_FAILURE = 4;
+
+
+  /**
+   * Indicates that there was a problem upon formatting
+   * the message of a log record.
+   */
+  public static final int FORMAT_FAILURE = 5;
+
+
+  private boolean everUsed = false;
+
+  public ErrorManager()
+  {
+  }
+
+
+  /**
+   * Reports an error that occured upon logging.  The default implementation
+   * emits the very first error to System.err, ignoring subsequent errors.
+   *
+   * @param message a message describing the error, or <code>null</code> if
+   *                there is no suitable description.
+   *
+   * @param ex      an exception, or <code>null</code> if the error is not
+   *                related to an exception.
+   *
+   * @param errorCode  one of the defined error codes, for example
+   *                   <code>ErrorManager.CLOSE_FAILURE</code>.
+   */
+  public void error(String message, Exception ex, int errorCode)
+  {
+    if (everUsed)
+      return;
+
+    synchronized (ErrorManager.class)
+    {
+      /* The double check is intentional. If the first check was
+       * omitted, the monitor would have to be entered every time
+       * error() method was called. If the second check was
+       * omitted, the code below could be executed by multiple
+       * threads simultaneously.
+       */
+      if (everUsed)
+       return;
+
+      everUsed = true;
+    }
+
+    String codeMsg;
+    switch (errorCode)
+    {
+    case GENERIC_FAILURE:
+      codeMsg = "GENERIC_FAILURE";
+      break;
+
+    case WRITE_FAILURE:
+      codeMsg = "WRITE_FAILURE";
+      break;
+
+    case FLUSH_FAILURE:
+      codeMsg = "FLUSH_FAILURE";
+      break;
+
+    case CLOSE_FAILURE:
+      codeMsg = "CLOSE_FAILURE";
+      break;
+
+    case OPEN_FAILURE:
+      codeMsg = "OPEN_FAILURE";
+      break;
+
+    case FORMAT_FAILURE:
+      codeMsg = "FORMAT_FAILURE";
+      break;
+
+    default:
+      codeMsg = String.valueOf(errorCode);
+      break;
+    }
+
+    System.err.println("Error upon logging: " + codeMsg);
+    if ((message != null) && (message.length() > 0))
+      System.err.println(message);
+
+    if (ex != null)
+      ex.printStackTrace();
+  }
+}
+
diff --git a/libjava/java/util/logging/FileHandler.java b/libjava/java/util/logging/FileHandler.java
new file mode 100644 (file)
index 0000000..b9f6110
--- /dev/null
@@ -0,0 +1,509 @@
+/* FileHandler.java
+   -- a class for publishing log messages to log files
+
+Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+package java.util.logging;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileOutputStream;
+
+/**
+ * A <code>FileHandler</code> publishes log records to a set of log
+ * files.  A maximum file size can be specified; as soon as a log file
+ * reaches the size limit, it is closed and the next file in the set
+ * is taken.
+ *
+ * <p><strong>Configuration:</strong> Values of the subsequent
+ * <code>LogManager</code> properties are taken into consideration
+ * when a <code>FileHandler</code> is initialized.  If a property is
+ * not defined, or if it has an invalid value, a default is taken
+ * without an exception being thrown.
+ *
+ * <ul>
+ *
+ * <li><code>java.util.FileHandler.level</code> - specifies
+ *     the initial severity level threshold. Default value:
+ *     <code>Level.ALL</code>.</li>
+ *
+ * <li><code>java.util.FileHandler.filter</code> - specifies
+ *     the name of a Filter class. Default value: No Filter.</li>
+ *
+ * <li><code>java.util.FileHandler.formatter</code> - specifies
+ *     the name of a Formatter class. Default value:
+ *     <code>java.util.logging.XMLFormatter</code>.</li>
+ *
+ * <li><code>java.util.FileHandler.encoding</code> - specifies
+ *     the name of the character encoding. Default value:
+ *     the default platform encoding.</li>
+ *
+ * <li><code>java.util.FileHandler.limit</code> - specifies the number
+ *     of bytes a log file is approximately allowed to reach before it
+ *     is closed and the handler switches to the next file in the
+ *     rotating set.  A value of zero means that files can grow
+ *     without limit.  Default value: 0 (unlimited growth).</li>
+ *
+ * <li><code>java.util.FileHandler.count</code> - specifies the number
+ *     of log files through which this handler cycles.  Default value:
+ *     1.</li>
+ *
+ * <li><code>java.util.FileHandler.pattern</code> - specifies a
+ *     pattern for the location and name of the produced log files.
+ *     See the section on <a href="#filePatterns">file name
+ *     patterns</a> for details.  Default value:
+ *     <code>"%h/java%u.log"</code>.</li>
+ *
+ * <li><code>java.util.FileHandler.append</code> - specifies
+ *     whether the handler will append log records to existing
+ *     files, or whether the handler will clear log files
+ *     upon switching to them. Default value: <code>false</code>,
+ *     indicating that files will be cleared.</li>
+ *
+ * </ul>
+ *
+ * <p><a name="filePatterns"><strong>File Name Patterns:</strong></a>
+ * The name and location and log files are specified with pattern
+ * strings. The handler will replace the following character sequences
+ * when opening log files:
+ *
+ * <p><ul>
+ * <li><code>/</code> - replaced by the platform-specific path name
+ *     separator.  This value is taken from the system property
+ *     <code>file.separator</code>.</li>
+ *
+ * <li><code>%t</code> - replaced by the platform-specific location of
+ *     the directory intended for temporary files.  This value is
+ *     taken from the system property <code>java.io.tmpdir</code>.</li>
+ *
+ * <li><code>%h</code> - replaced by the location of the home
+ *     directory of the current user.  This value is taken from the
+ *     system property <code>file.separator</code>.</li>
+ *
+ * <li><code>%g</code> - replaced by a generation number for
+ *     distinguisthing the individual items in the rotating set 
+ *     of log files.  The generation number cycles through the
+ *     sequence 0, 1, ..., <code>count</code> - 1.</li>
+ *
+ * <li><code>%u</code> - replaced by a unique number for
+ *     distinguisthing the output files of several concurrently
+ *     running processes.  The <code>FileHandler</code> starts
+ *     with 0 when it tries to open a log file.  If the file
+ *     cannot be opened because it is currently in use,
+ *     the unique number is incremented by one and opening
+ *     is tried again.  These steps are repeated until the
+ *     opening operation succeeds.
+ *
+ *     <p>FIXME: Is the following correct? Please review.  The unique
+ *     number is determined for each log file individually when it is
+ *     opened upon switching to the next file.  Therefore, it is not
+ *     correct to assume that all log files in a rotating set bear the
+ *     same unique number.
+ *
+ *     <p>FIXME: The Javadoc for the Sun reference implementation
+ *     says: "Note that the use of unique ids to avoid conflicts is
+ *     only guaranteed to work reliably when using a local disk file
+ *     system." Why? This needs to be mentioned as well, in case
+ *     the reviewers decide the statement is true.  Otherwise,
+ *     file a bug report with Sun.</li>
+ *
+ * <li><code>%%</code> - replaced by a single percent sign.</li>
+ * </ul>
+ *
+ * <p>If the pattern string does not contain <code>%g</code> and
+ * <code>count</code> is greater than one, the handler will append
+ * the string <code>.%g</code> to the specified pattern.
+ *
+ * <p>If the handler attempts to open a log file, this log file
+ * is being used at the time of the attempt, and the pattern string
+ * does not contain <code>%u</code>, the handler will append
+ * the string <code>.%u</code> to the specified pattern. This
+ * step is performed after any generation number has been
+ * appended.
+ *
+ * <p><em>Examples for the GNU platform:</em> 
+ *
+ * <p><ul>
+ *
+ * <li><code>%h/java%u.log</code> will lead to a single log file
+ *     <code>/home/janet/java0.log</code>, assuming <code>count</code>
+ *     equals 1, the user's home directory is
+ *     <code>/home/janet</code>, and the attempt to open the file
+ *     succeeds.</li>
+ *
+ * <li><code>%h/java%u.log</code> will lead to three log files
+ *     <code>/home/janet/java0.log.0</code>,
+ *     <code>/home/janet/java0.log.1</code>, and
+ *     <code>/home/janet/java0.log.2</code>,
+ *     assuming <code>count</code> equals 3, the user's home
+ *     directory is <code>/home/janet</code>, and all attempts
+ *     to open files succeed.</li>
+ *
+ * <li><code>%h/java%u.log</code> will lead to three log files
+ *     <code>/home/janet/java0.log.0</code>,
+ *     <code>/home/janet/java1.log.1</code>, and
+ *     <code>/home/janet/java0.log.2</code>,
+ *     assuming <code>count</code> equals 3, the user's home
+ *     directory is <code>/home/janet</code>, and the attempt
+ *     to open <code>/home/janet/java0.log.1</code> fails.</li>
+ *
+ * </ul>
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class FileHandler
+  extends StreamHandler
+{
+  /**
+   * The number of bytes a log file is approximately allowed to reach
+   * before it is closed and the handler switches to the next file in
+   * the rotating set.  A value of zero means that files can grow
+   * without limit.
+   */
+  private final int limit;
+
+
+ /**
+  * The number of log files through which this handler cycles.
+  */
+  private final int count;
+
+
+  /**
+   * The pattern for the location and name of the produced log files.
+   * See the section on <a href="#filePatterns">file name patterns</a>
+   * for details.
+   */
+  private final String pattern;
+
+
+  /**
+   * Indicates whether the handler will append log records to existing
+   * files (<code>true</code>), or whether the handler will clear log files
+   * upon switching to them (<code>false</code>).
+   */
+  private final boolean append;
+
+
+  /**
+   * Constructs a <code>FileHandler</code>, taking all property values
+   * from the current {@link LogManager LogManager} configuration.
+   *
+   * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
+   *         there are IO problems opening the files."  This conflicts
+   *         with the general principle that configuration errors do
+   *         not prohibit construction. Needs review.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   */
+  public FileHandler()
+    throws IOException, SecurityException
+  {
+    this(/* pattern: use configiguration */ null,
+
+        LogManager.getIntProperty("java.util.logging.FileHandler.limit",
+                                  /* default */ 0),
+
+        LogManager.getIntProperty("java.util.logging.FileHandler.count",
+                                  /* default */ 1),
+
+        LogManager.getBooleanProperty("java.util.logging.FileHandler.append",
+                                      /* default */ false));
+  }
+
+
+  /* FIXME: Javadoc missing. */
+  public FileHandler(String pattern)
+    throws IOException, SecurityException
+  {
+    this(pattern,
+        /* limit */ 0,
+        /* count */ 1,
+        /* append */ false);
+  }
+
+
+  /* FIXME: Javadoc missing. */
+  public FileHandler(String pattern, boolean append)
+    throws IOException, SecurityException
+  {
+    this(pattern,
+        /* limit */ 0,
+        /* count */ 1,
+        append);
+  }
+
+
+  /* FIXME: Javadoc missing. */
+  public FileHandler(String pattern, int limit, int count)
+    throws IOException, SecurityException
+  {
+    this(pattern, limit, count, 
+        LogManager.getBooleanProperty(
+          "java.util.logging.FileHandler.append",
+          /* default */ false));
+  }
+
+
+  /**
+   * Constructs a <code>FileHandler</code> given the pattern for the
+   * location and name of the produced log files, the size limit, the
+   * number of log files thorough which the handler will rotate, and
+   * the <code>append</code> property.  All other property values are
+   * taken from the current {@link LogManager LogManager}
+   * configuration.
+   *
+   * @param pattern The pattern for the location and name of the
+   *        produced log files.  See the section on <a
+   *        href="#filePatterns">file name patterns</a> for details.
+   *        If <code>pattern</code> is <code>null</code>, the value is
+   *        taken from the {@link LogManager LogManager} configuration
+   *        property
+   *        <code>java.util.logging.FileHandler.pattern</code>.
+   *        However, this is a pecularity of the GNU implementation,
+   *        and Sun's API specification does not mention what behavior
+   *        is to be expected for <code>null</code>. Therefore,
+   *        applications should not rely on this feature.
+   *
+   * @param limit specifies the number of bytes a log file is
+   *        approximately allowed to reach before it is closed and the
+   *        handler switches to the next file in the rotating set.  A
+   *        value of zero means that files can grow without limit.
+   *
+   * @param count specifies the number of log files through which this
+   *        handler cycles.
+   *
+   * @param append specifies whether the handler will append log
+   *        records to existing files (<code>true</code>), or whether the
+   *        handler will clear log files upon switching to them
+   *        (<code>false</code>).
+   *
+   * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
+   *         there are IO problems opening the files."  This conflicts
+   *         with the general principle that configuration errors do
+   *         not prohibit construction. Needs review.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   *         <p>FIXME: This seems in contrast to all other handler
+   *         constructors -- verify this by running tests against
+   *         the Sun reference implementation.
+   */
+  public FileHandler(String pattern,
+                    int limit,
+                    int count,
+                    boolean append)
+    throws IOException, SecurityException
+  {
+    super(createFileStream(pattern, limit, count, append,
+                          /* generation */ 0),
+         "java.util.logging.FileHandler",
+         /* default level */ Level.ALL,
+         /* formatter */ null,
+         /* default formatter */ XMLFormatter.class);
+
+    if ((limit <0) || (count < 1))
+      throw new IllegalArgumentException();
+
+    this.pattern = pattern;
+    this.limit = limit;
+    this.count = count;
+    this.append = append;
+  }
+
+
+  /* FIXME: Javadoc missing. */
+  private static java.io.OutputStream createFileStream(String pattern,
+                                                      int limit,
+                                                      int count,
+                                                      boolean append,
+                                                      int generation)
+  {
+    String  path;
+    int     unique = 0;
+
+    /* Throws a SecurityException if the caller does not have
+     * LoggingPermission("control").
+     */
+    LogManager.getLogManager().checkAccess();
+
+    /* Default value from the java.util.logging.FileHandler.pattern
+     * LogManager configuration property.
+     */
+    if (pattern == null)
+      pattern = LogManager.getLogManager().getProperty(
+                              "java.util.logging.FileHandler.pattern");
+    if (pattern == null)
+      pattern = "%h/java%u.log";
+
+    do
+    {
+      path = replaceFileNameEscapes(pattern, generation, unique, count);
+
+      try
+      {
+       File file = new File(path);
+       if (file.createNewFile())
+         return new FileOutputStream(path, append);
+      }
+      catch (Exception ex)
+      {
+       ex.printStackTrace();   
+      }
+
+      unique = unique + 1;
+      if (pattern.indexOf("%u") < 0)
+        pattern = pattern + ".%u";
+    }
+    while (true);
+  }
+
+
+  /**
+   * Replaces the substrings <code>"/"</code> by the value of the
+   * system property <code>"file.separator"</code>, <code>"%t"</code>
+   * by the value of the system property
+   * <code>"java.io.tmpdir"</code>, <code>"%h"</code> by the value of
+   * the system property <code>"user.home"</code>, <code>"%g"</code>
+   * by the value of <code>generation</code>, <code>"%u"</code> by the
+   * value of <code>uniqueNumber</code>, and <code>"%%"</code> by a
+   * single percent character.  If <code>pattern<code> does
+   * <em>not</em> contain the sequence <code>"%g"</code>,
+   * the value of <code>generation</code> will be appended to
+   * the result.
+   *
+   * @throws NullPointerException if one of the system properties
+   *         <code>"file.separator"</code>,
+   *         <code>"java.io.tmpdir"</code>, or
+   *         <code>"user.home"</code> has no value and the
+   *         corresponding escape sequence appears in
+   *         <code>pattern</code>.
+   */
+  private static String replaceFileNameEscapes(String pattern,
+                                              int generation,
+                                              int uniqueNumber,
+                                              int count)
+  {
+    StringBuffer buf = new StringBuffer(pattern);
+    String       replaceWith;
+    boolean      foundGeneration = false;
+
+    int pos = 0;
+    do
+    {
+      // Uncomment the next line for finding bugs.
+      // System.out.println(buf.substring(0,pos) + '|' + buf.substring(pos));
+      
+      if (buf.charAt(pos) == '/')
+      {
+       /* The same value is also provided by java.io.File.separator. */
+       replaceWith = System.getProperty("file.separator");
+       buf.replace(pos, pos + 1, replaceWith);
+       pos = pos + replaceWith.length() - 1;
+       continue;
+      }
+
+      if (buf.charAt(pos) == '%')
+      {
+        switch (buf.charAt(pos + 1))
+       {
+       case 't':
+         replaceWith = System.getProperty("java.io.tmpdir");
+         break;
+
+       case 'h':
+         replaceWith = System.getProperty("user.home");
+         break;
+
+       case 'g':
+         replaceWith = Integer.toString(generation);
+         foundGeneration = true;
+         break;
+
+       case 'u':
+         replaceWith = Integer.toString(uniqueNumber);
+         break;
+
+       case '%':
+         replaceWith = "%";
+         break;
+
+       default:
+         replaceWith = "??";
+         break; // FIXME: Throw exception?
+       }
+
+       buf.replace(pos, pos + 2, replaceWith);
+       pos = pos + replaceWith.length() - 1;
+       continue;
+      }
+    }
+    while (++pos < buf.length() - 1);
+
+    if (!foundGeneration && (count > 1))
+    {
+      buf.append('.');
+      buf.append(generation);
+    }
+
+    return buf.toString();
+  }
+
+
+  /* FIXME: Javadoc missing, implementation incomplete. */
+  public void publish(LogRecord record)
+  {
+    super.publish(record);
+
+    /* FIXME: Decide when to switch over. How do we get to
+     * the number of bytes published so far? Two possibilities:
+     * 1. File.length, 2. have metering wrapper around
+     * output stream counting the number of written bytes.
+     */
+  
+    /* FIXME: Switch over if needed! This implementation always
+     * writes into a single file, i.e. behaves as if limit
+     * always was zero. So, the implementation is somewhat
+     * functional but incomplete.
+     */
+  }
+}
diff --git a/libjava/java/util/logging/Filter.java b/libjava/java/util/logging/Filter.java
new file mode 100644 (file)
index 0000000..a25fd3d
--- /dev/null
@@ -0,0 +1,68 @@
+/* Filter.java
+   -- an interface for filters that decide whether a LogRecord should
+      be published or discarded
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+/**
+ * By implementing the <code>Filter</code> interface, applications
+ * can control what is being logged based on arbitrary properties,
+ * not just the severity level.  Both <code>Handler</code> and
+ * <code>Logger</code> allow to register Filters whose
+ * <code>isLoggable</code> method will be called when a
+ * <code>LogRecord</code> has passed the test based on the
+ * severity level.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public interface Filter
+{
+  /**
+   * Determines whether a LogRecord should be published or discarded.
+   *
+   * @param record the <code>LogRecord</code> to be inspected.
+   *
+   * @return <code>true</code> if the record should be published,
+   *         <code>false</code> if it should be discarded.
+   */
+  public boolean isLoggable(LogRecord record);
+}
diff --git a/libjava/java/util/logging/Formatter.java b/libjava/java/util/logging/Formatter.java
new file mode 100644 (file)
index 0000000..c481969
--- /dev/null
@@ -0,0 +1,174 @@
+/* Formatter.java
+   -- a class for formatting log messages by localizing message texts
+      and performing substitution of parameters
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.util.ResourceBundle;
+import java.text.MessageFormat;
+
+/**
+ * A <code>Formatter</code> supports handlers by localizing
+ * message texts and by subsituting parameter values for their
+ * placeholders.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public abstract class Formatter
+{
+  /**
+   * Constructs a new Formatter.
+   */
+  protected Formatter()
+  {
+  }
+
+
+  /**
+   * Formats a LogRecord into a string.  Usually called by handlers
+   * which need a string for a log record, for example to append
+   * a record to a log file or to transmit a record over the network.
+   *
+   * @param record the log record for which a string form is requested.
+   */
+  public abstract String format(LogRecord record);
+
+
+  /**
+   * Returns a string that handlers are supposed to emit before
+   * the first log record.  The base implementation returns an
+   * empty string, but subclasses such as {@link XMLFormatter}
+   * override this method in order to provide a suitable header.
+   *
+   * @return a string for the header.
+   *
+   * @param handler the handler which will prepend the returned
+   *     string in front of the first log record.  This method
+   *     may inspect certain properties of the handler, for
+   *     example its encoding, in order to construct the header.
+   */
+  public String getHead(Handler handler)
+  {
+    return "";
+  }
+
+
+  /**
+   * Returns a string that handlers are supposed to emit after
+   * the last log record.  The base implementation returns an
+   * empty string, but subclasses such as {@link XMLFormatter}
+   * override this method in order to provide a suitable tail.
+   *
+   * @return a string for the header.
+   *
+   * @param handler the handler which will append the returned
+   *     string after the last log record.  This method
+   *     may inspect certain properties of the handler
+   *     in order to construct the tail.
+   */
+  public String getTail(Handler handler)
+  {
+    return "";
+  }
+
+
+  /**
+   * Formats the message part of a log record.
+   *
+   * <p>First, the Formatter localizes the record message to the
+   * default locale by looking up the message in the record's
+   * localization resource bundle.  If this step fails because there
+   * is no resource bundle associated with the record, or because the
+   * record message is not a key in the bundle, the raw message is
+   * used instead.
+   *
+   * <p>Second, the Formatter substitutes appropriate strings for
+   * the message parameters. If the record returns a non-empty
+   * array for <code>getParameters()</code> and the localized
+   * message string contains the character sequence "{0", the
+   * formatter uses <code>java.text.MessageFormat</code> to format
+   * the message.  Otherwise, no parameter substitution is performed.
+   *
+   * @param record the log record to be localized and formatted.
+   *
+   * @return the localized message text where parameters have been
+   *         substituted by suitable strings.
+   *
+   * @throws NullPointerException if <code>record</code>
+   *         is <code>null</code>.
+   */
+  public String formatMessage(LogRecord record)
+  {
+    String          msg;
+    ResourceBundle  bundle;
+    Object[]        params;
+
+    /* This will throw a NullPointerExceptionif record is null. */
+    msg = record.getMessage();
+    if (msg == null)
+      msg = "";
+
+    /* Try to localize the message. */
+    bundle = record.getResourceBundle();
+    if (bundle != null)
+    {
+      try
+      {
+       msg = bundle.getString(msg);
+      }
+      catch (java.util.MissingResourceException _)
+      {
+      }
+    }
+
+    /* Format the message if there are parameters. */
+    params = record.getParameters();
+    if ((params != null)
+       && (params.length > 0)
+       && (msg.indexOf("{0") >= 0))
+    {
+      msg = MessageFormat.format(msg, params);
+    }
+
+    return msg;
+  }
+}
diff --git a/libjava/java/util/logging/Handler.java b/libjava/java/util/logging/Handler.java
new file mode 100644 (file)
index 0000000..3245321
--- /dev/null
@@ -0,0 +1,390 @@
+/* Handler.java
+   -- a class for publishing log messages
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.io.UnsupportedEncodingException;
+import java.security.AccessController;
+
+/**
+ * A <code>Handler</code> publishes <code>LogRecords</code> to
+ * a sink, for example a file, the console or a network socket.
+ * There are different subclasses of <code>Handler</code>
+ * to deal with different kinds of sinks.
+ *
+ * <p>FIXME: Are handlers thread-safe, or is the assumption that only
+ * loggers are, and a handler can belong only to one single logger? If
+ * the latter, should we enforce it? (Spec not clear). In any
+ * case, it needs documentation.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public abstract class Handler
+{
+  Formatter     formatter;
+  Filter        filter;
+  Level         level;
+  ErrorManager  errorManager;
+  String        encoding;
+
+  /**
+   * Constructs a Handler with a logging severity level of
+   * <code>Level.ALL</code>, no formatter, no filter, and
+   * an instance of <code>ErrorManager</code> managing errors.
+   *
+   * <p><strong>Specification Note:</strong> The specification of the
+   * Java<sup>TM</sup> Logging API does not mention which character
+   * encoding is to be used by freshly constructed Handlers.  The GNU
+   * implementation uses the default platform encoding, but other
+   * Java implementations might behave differently.
+   *
+   * <p><strong>Specification Note:</strong> While a freshly constructed
+   * Handler is required to have <em>no filter</em> according to the
+   * specification, <code>null</code> is not a valid parameter for
+   * <code>Handler.setFormatter</code>.  Therefore, the following
+   * code will throw a <code>java.lang.NullPointerException</code>:
+   *
+   * <p><pre>Handler h = new MyConcreteSubclassOfHandler();
+h.setFormatter(h.getFormatter());</pre>
+   *
+   * It seems strange that a freshly constructed Handler is not
+   * supposed to provide a Formatter, but this is what the specification
+   * says.
+   */
+  {
+    level = Level.ALL;
+  }
+
+
+  /**
+   * Publishes a <code>LogRecord</code> to an appropriate sink,
+   * provided the record passes all tests for being loggable.  The
+   * <code>Handler</code> will localize the message of the log
+   * record and substitute any message parameters.
+   *
+   * <p>Most applications do not need to call this method directly.
+   * Instead, they will use use a {@link Logger}, which will
+   * create LogRecords and distribute them to registered handlers.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>Handler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   *
+   * @param record the log event to be published.
+   */
+  public abstract void publish(LogRecord record);
+
+
+  /**
+   * Forces any data that may have been buffered to the underlying
+   * output device.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>Handler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   */
+  public abstract void flush();
+
+
+  /**
+   * Closes this <code>Handler</code> after having flushed
+   * the buffers.  As soon as <code>close</code> has been called,
+   * a <code>Handler</code> should not be used anymore. Attempts
+   * to publish log records, to flush buffers, or to modify the
+   * <code>Handler</code> in any other way may throw runtime
+   * exceptions after calling <code>close</code>.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>Handler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   */
+  public abstract void close()
+    throws SecurityException;
+
+
+  /**
+   * Returns the <code>Formatter</code> which will be used to
+   * localize the text of log messages and to substitute
+   * message parameters.  A <code>Handler</code> is encouraged,
+   * but not required to actually use an assigned
+   * <code>Formatter</code>.
+   *
+   * @return the <code>Formatter</code> being used, or
+   *         <code>null</code> if this <code>Handler</code>
+   *         does not use formatters and no formatter has
+   *         ever been set by calling <code>setFormatter</code>.
+   */
+  public Formatter getFormatter()
+  {
+    return formatter;
+  }
+
+
+  /**
+   * Sets the <code>Formatter</code> which will be used to
+   * localize the text of log messages and to substitute
+   * message parameters.  A <code>Handler</code> is encouraged,
+   * but not required to actually use an assigned
+   * <code>Formatter</code>.
+   *
+   * @param formatter the new <code>Formatter</code> to use.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   *
+   * @throws NullPointerException if <code>formatter</code> is
+   *         <code>null</code>.
+   */
+  public void setFormatter(Formatter formatter)
+    throws SecurityException
+  {
+    LogManager.getLogManager().checkAccess();
+    
+    /* Throws a NullPointerException if formatter is null. */
+    formatter.getClass();
+
+    this.formatter = formatter;
+  }
+
+
+  /**
+   * Returns the character encoding which this handler uses for publishing
+   * log records.
+   *
+   * @param encoding the name of a character encoding, or <code>null</code>
+   *            for the default platform encoding.
+   */
+  public String getEncoding()
+  {
+    return encoding;
+  }
+
+
+  /**
+   * Sets the character encoding which this handler uses for publishing
+   * log records.  The encoding of a <code>Handler</code> must be
+   * set before any log records have been published.
+   *
+   * @param encoding the name of a character encoding, or <code>null</code>
+   *            for the default encoding.
+   *
+   * @exception SecurityException if a security manager exists and
+   *            the caller is not granted the permission to control
+   *            the logging infrastructure.
+   *
+   */
+  public void setEncoding(String encoding)
+    throws SecurityException, UnsupportedEncodingException
+  {
+    /* Should any developer ever change this implementation, they are
+     * advised to have a look at StreamHandler.setEncoding(String),
+     * which overrides this method without calling super.setEncoding.
+     */
+    LogManager.getLogManager().checkAccess();
+
+    /* Simple check for supported encodings. This is more expensive
+     * than it could be, but this method is overwritten by StreamHandler
+     * anyway.
+     */
+    if (encoding != null)
+      new String(new byte[0], encoding);
+
+    this.encoding = encoding;
+  }
+
+
+  /**
+   * Returns the <code>Filter</code> that currently controls which
+   * log records are being published by this <code>Handler</code>.
+   *
+   * @return the currently active <code>Filter</code>, or
+   *         <code>null</code> if no filter has been associated.
+   *         In the latter case, log records are filtered purely
+   *         based on their severity level.
+   */
+  public Filter getFilter()
+  {
+    return filter;
+  }
+
+
+  /**
+   * Sets the <code>Filter</code> for controlling which
+   * log records will be published by this <code>Handler</code>.
+   *
+   * @return the <code>Filter</code> to use, or
+   *         <code>null</code> to filter log records purely based
+   *         on their severity level.
+   */
+  public void setFilter(Filter filter)
+    throws SecurityException
+  {
+    LogManager.getLogManager().checkAccess();
+    this.filter = filter;
+  }
+
+
+  /**
+   * Returns the <code>ErrorManager</code> that currently deals
+   * with errors originating from this Handler.
+   *
+   * @exception SecurityException if a security manager exists and
+   *            the caller is not granted the permission to control
+   *            the logging infrastructure.
+   */
+  public ErrorManager getErrorManager()
+  {
+    LogManager.getLogManager().checkAccess();
+
+    /* Developers wanting to change the subsequent code should
+     * have a look at Handler.reportError -- it also can create
+     * an ErrorManager, but does so without checking permissions
+     * to control the logging infrastructure.
+     */
+    if (errorManager == null)
+      errorManager = new ErrorManager();
+
+    return errorManager;
+  }
+
+
+  public void setErrorManager(ErrorManager manager)
+  {
+    LogManager.getLogManager().checkAccess();
+
+    /* Make sure manager is not null. */
+    manager.getClass();
+
+    this.errorManager = manager;
+  }
+
+
+  protected void reportError(String message, Exception ex, int code)
+  {
+    if (errorManager == null)
+      errorManager = new ErrorManager();
+
+    errorManager.error(message, ex, code);
+  }
+
+
+  /**
+   * Returns the severity level threshold for this <code>Handler</code>
+   * All log records with a lower severity level will be discarded;
+   * a log record of the same or a higher level will be published
+   * unless an installed <code>Filter</code> decides to discard it.
+   *
+   * @return the severity level below which all log messages
+   *         will be discarded.
+   */
+  public Level getLevel()
+  {
+    return level;
+  }
+
+
+  /**
+   * Sets the severity level threshold for this <code>Handler</code>.
+   * All log records with a lower severity level will be discarded;
+   * a log record of the same or a higher level will be published
+   * unless an installed <code>Filter</code> decides to discard it.
+   *
+   * @param level the severity level below which all log messages
+   *              will be discarded.
+   *
+   * @exception SecurityException if a security manager exists and
+   *            the caller is not granted the permission to control
+   *            the logging infrastructure.
+   *
+   * @exception NullPointerException if <code>level</code> is
+   *            <code>null</code>.
+   */
+  public void setLevel(Level level)
+  {
+    LogManager.getLogManager().checkAccess();
+
+    /* Throw NullPointerException if level is null.  */
+    level.getClass();
+    this.level = level;
+  }
+
+
+  /**
+   * Checks whether a <code>LogRecord</code> would be logged
+   * if it was passed to this <code>Handler</code> for publication.
+   *
+   * <p>The <code>Handler</code> implementation considers a record as
+   * loggable if its level is greater than or equal to the severity
+   * level threshold.  In a second step, if a {@link Filter} has
+   * been installed, its {@link Filter#isLoggable(LogRecord) isLoggable}
+   * method is invoked. Subclasses of <code>Handler</code> can override
+   * this method to impose their own constraints.
+   *
+   * @param record the <code>LogRecord</code> to be checked.
+   *
+   * @return <code>true</code> if <code>record</code> would
+   *         be published by {@link #publish(LogRecord) publish},
+   *         <code>false</code> if it would be discarded.
+   *
+   * @see #setLevel(Level)
+   * @see #setFilter(Filter)
+   * @see Filter#isLoggable(LogRecord)
+   *
+   * @throws NullPointerException if <code>record</code>
+   *         is <code>null</code>.
+   */
+  public boolean isLoggable(LogRecord record)
+  {
+    if (record.getLevel().intValue() < level.intValue())
+      return false;
+    
+    if (filter != null)
+      return filter.isLoggable(record);
+    else
+      return true;
+  }
+}
diff --git a/libjava/java/util/logging/Level.java b/libjava/java/util/logging/Level.java
new file mode 100644 (file)
index 0000000..d8987f6
--- /dev/null
@@ -0,0 +1,417 @@
+/* Level.java -- a class for indicating logging levels
+   Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.util.ResourceBundle;
+
+
+/**
+ * A class for indicating logging levels.  A number of commonly used
+ * levels is pre-defined (such as <code>java.util.logging.Level.INFO</code>),
+ * and applications should utilize those whenever possible.  For specialized
+ * purposes, however, applications can sub-class Level in order to define
+ * custom logging levels.
+ *
+ * @author Sascha Brawer <brawer@acm.org>
+ */
+public class Level
+  implements java.io.Serializable
+{
+  /* The integer values are the same as in the Sun J2SE 1.4.
+   * They have been obtained with a test program. In J2SE 1.4.1,
+   * Sun has amended the API documentation; these values are now
+   * publicly documented.
+   */
+
+  /**
+   * The <code>OFF</code> level is used as a threshold for filtering
+   * log records, meaning that no message should be logged.
+   *
+   * @see Logger#setLevel(java.util.logging.Level)
+   */
+  public static final Level OFF = new Level ("OFF", Integer.MAX_VALUE);
+
+  /**
+   * Log records whose level is <code>SEVERE</code> indicate a serious
+   * failure that prevents normal program execution.  Messages at this
+   * level should be understandable to an inexperienced, non-technical
+   * end user.  Ideally, they explain in simple words what actions the
+   * user can take in order to resolve the problem.
+   */
+  public static final Level SEVERE = new Level ("SEVERE", 1000);
+
+
+  /**
+   * Log records whose level is <code>WARNING</code> indicate a
+   * potential problem that does not prevent normal program execution.
+   * Messages at this level should be understandable to an
+   * inexperienced, non-technical end user.  Ideally, they explain in
+   * simple words what actions the user can take in order to resolve
+   * the problem.
+   */
+  public static final Level WARNING = new Level ("WARNING", 900);
+
+
+  /**
+   * Log records whose level is <code>INFO</code> are used in purely
+   * informational situations that do not constitute serious errors or
+   * potential problems. In the default logging configuration, INFO
+   * messages will be written to the system console.  For this reason,
+   * the INFO level should be used only for messages that are
+   * important to end users and system administrators.  Messages at
+   * this level should be understandable to an inexperienced,
+   * non-technical user.
+   */
+  public static final Level INFO = new Level ("INFO", 800);
+
+
+  /**
+   * Log records whose level is <code>CONFIG</code> are used for
+   * describing the static configuration, for example the windowing
+   * environment, the operating system version, etc.
+   */
+  public static final Level CONFIG = new Level ("CONFIG", 700);
+
+
+  /**
+   * Log records whose level is <code>FINE</code> are typically used
+   * for messages that are relevant for developers using
+   * the component generating log messages.  Examples include minor,
+   * recoverable failures, or possible inefficiencies.
+   */
+  public static final Level FINE = new Level ("FINE", 500);
+
+
+  /**
+   * Log records whose level is <code>FINER</code> are intended for
+   * rather detailed tracing, for example entering a method, returning
+   * from a method, or throwing an exception.
+   */
+  public static final Level FINER = new Level ("FINER", 400);
+
+
+  /**
+   * Log records whose level is <code>FINEST</code> are used for
+   * highly detailed tracing, for example to indicate that a certain
+   * point inside the body of a method has been reached.
+   */
+  public static final Level FINEST = new Level ("FINEST", 300);
+
+
+  /**
+   * The <code>ALL</code> level is used as a threshold for filtering
+   * log records, meaning that every message should be logged.
+   *
+   * @see Logger#setLevel(java.util.logging.Level)
+   */
+  public static final Level ALL = new Level ("ALL", Integer.MIN_VALUE);
+
+
+  private static final Level[] knownLevels = {
+    ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF
+  };
+
+
+  /**
+   * The name of the Level without localizing it, for example
+   * "WARNING".
+   */
+  private String name;
+
+
+  /**
+   * The integer value of this <code>Level</code>.
+   */
+  private int value;
+
+
+  /**
+   * The name of the resource bundle used for localizing the level
+   * name, or <code>null</code> if the name does not undergo
+   * localization.
+   */
+  private String resourceBundleName;
+
+
+  /**
+   * Creates a logging level given a name and an integer value.
+   * It rarely is necessary to create custom levels,
+   * as most applications should be well served with one of the
+   * standard levels such as <code>Level.CONFIG</code>,
+   * <code>Level.INFO</code>, or <code>Level.FINE</code>.
+   *
+   * @param name the name of the level.
+   *
+   * @param value the integer value of the level.  Please note
+   *     that the Java<small><sup>TM</sup></small>
+   *     Logging API does not specify integer
+   *    values for standard levels (such as
+   *    Level.FINE).  Therefore, a custom
+   *    level should pass an integer value that
+   *    is calculated at run-time, e.g.
+   *    <code>(Level.FINE.intValue() + Level.CONFIG.intValue())
+   *    / 2</code> for a level between FINE and CONFIG.
+   */
+  protected Level(String name, int value)
+  {
+    this(name, value, null);
+  }
+
+
+  /**
+   * Create a logging level given a name, an integer value and a name
+   * of a resource bundle for localizing the level name.  It rarely
+   * is necessary to create custom levels, as most applications
+   * should be well served with one of the standard levels such as
+   * <code>Level.CONFIG</code>, <code>Level.INFO</code>, or
+   * <code>Level.FINE</code>.
+   *
+   * @param name the name of the level.
+   *
+   * @param value the integer value of the level.  Please note
+   *        that the Java<small><sup>TM</sup></small>
+   *       Logging API does not specify integer
+   *       values for standard levels (such as
+   *       Level.FINE).  Therefore, a custom
+   *       level should pass an integer value that
+   *       is calculated at run-time, e.g.
+   *       <code>(Level.FINE.intValue() + Level.CONFIG.intValue())
+   *       / 2</code> for a level between FINE and CONFIG.
+   *
+   * @param resourceBundleName the name of a resource bundle
+   *       for localizing the level name, or <code>null</code>
+   *       if the name does not need to be localized.
+   */
+  protected Level(String name, int value, String resourceBundleName)
+  {
+    this.name = name;
+    this.value = value;
+    this.resourceBundleName = resourceBundleName;
+  }
+
+
+  static final long serialVersionUID = -8176160795706313070L;
+
+
+  /**
+   * Checks whether the Level has the same intValue as one of the
+   * pre-defined levels.  If so, the pre-defined level object is
+   * returned.
+   *
+   * <br/>Since the resource bundle name is not taken into
+   * consideration, it is possible to resolve Level objects that have
+   * been de-serialized by another implementation, even if the other
+   * implementation uses a different resource bundle for localizing
+   * the names of pre-defined levels.
+   */
+  private Object readResolve()
+  {
+    for (int i = 0; i < knownLevels.length; i++)
+      if (value == knownLevels[i].intValue())
+       return knownLevels[i];
+
+    return this;
+  }
+
+
+  /**
+   * Returns the name of the resource bundle used for localizing the
+   * level name.
+   *
+   * @return the name of the resource bundle used for localizing the
+   * level name, or <code>null</code> if the name does not undergo
+   * localization.
+   */
+  public String getResourceBundleName()
+  {
+    return resourceBundleName;
+  }
+
+
+  /**
+   * Returns the name of the Level without localizing it, for example
+   * "WARNING".
+   */
+  public String getName()
+  {
+    return name;
+  }
+
+
+  /**
+   * Returns the name of the Level after localizing it, for example
+   * "WARNUNG".
+   */
+  public String getLocalizedName()
+  {
+    String localizedName = null;
+
+    if (resourceBundleName != null)
+    {
+      try
+      {
+        ResourceBundle b = ResourceBundle.getBundle(resourceBundleName);
+       localizedName = b.getString(name);
+      }
+      catch (Exception _)
+      {
+      }
+    }
+
+    if (localizedName != null)
+      return localizedName;
+    else
+      return name;
+  }
+
+
+  /**
+   * Returns the name of the Level without localizing it, for example
+   * "WARNING".
+   */
+  public final String toString()
+  {
+    return getName();
+  }
+
+
+  /**
+   * Returns the integer value of the Level.
+   */
+  public final int intValue()
+  {
+    return value;
+  }
+
+
+  /**
+   * Returns one of the standard Levels given either its name or its
+   * integer value.  Custom subclasses of Level will not be returned
+   * by this method.
+   *
+   * @throws IllegalArgumentException if <code>name</code> is neither
+   * the name nor the integer value of one of the pre-defined standard
+   * logging levels.
+   *
+   * @throws NullPointerException if <code>name</code> is null.
+   *
+   */
+  public static Level parse(String name)
+    throws IllegalArgumentException
+  {
+    /* This will throw a NullPointerException if name is null,
+     * as required by the API specification.
+     */
+    name = name.intern();
+
+    for (int i = 0; i < knownLevels.length; i++)
+    {
+      if (name == knownLevels[i].name)
+       return knownLevels[i];
+    }
+    
+    try
+    {
+      int num = Integer.parseInt(name);
+      for (int i = 0; i < knownLevels.length; i++)
+       if (num == knownLevels[i].value)
+         return knownLevels[i];
+    }
+    catch (NumberFormatException _)
+    {
+    }
+
+    String msg = "Not the name of a standard logging level: \"" + name + "\"";
+    throw new IllegalArgumentException(msg);
+  }
+
+
+  /**
+   * Checks whether this Level's integer value is equal to that of
+   * another object.
+   *
+   * @return <code>true</code> if <code>other</code> is an instance of
+   *    <code>java.util.logging.Level</code> and has the same integer
+   * value, <code>false</code> otherwise.
+   */
+  public boolean equals(Object other)
+  {
+    if (!(other instanceof Level))
+      return false;
+
+    return value == ((Level) other).value;
+  }
+
+
+  /**
+   * Returns a hash code for this Level which is based on its numeric
+   * value.
+   */
+  public int hashCode()
+  {
+    return value;
+  }  
+
+
+  /**
+   * Determines whether or not this Level is one of the standard
+   * levels specified in the Logging API.
+   *
+   * <p>This method is package-private because it is not part
+   * of the logging API specification.  However, an XMLFormatter
+   * is supposed to emit the numeric value for a custom log
+   * level, but the name for a pre-defined level. It seems
+   * cleaner to put this method to Level than to write some
+   * procedural code for XMLFormatter.
+   *
+   * @return <code>true</code> if this Level is a standard level,
+   *         <code>false</code> otherwise.
+   */
+  final boolean isStandardLevel()
+  {
+    for (int i = 0; i < knownLevels.length; i++)
+      if (knownLevels[i] == this)
+       return true;
+
+    return false;
+  }
+}
+
diff --git a/libjava/java/util/logging/LogManager.java b/libjava/java/util/logging/LogManager.java
new file mode 100644 (file)
index 0000000..d6536e7
--- /dev/null
@@ -0,0 +1,821 @@
+/* LogManager.java
+   -- a class for maintaining Loggers and managing configuration
+      properties
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+package java.util.logging;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.lang.ref.WeakReference;
+
+/**
+ * The <code>LogManager</code> maintains a hierarchical namespace
+ * of Logger objects and manages properties for configuring the logging
+ * framework. There exists only one single <code>LogManager</code>
+ * per virtual machine. This instance can be retrieved using the
+ * static method {@link #getLogManager()}.
+ *
+ * <p><strong>Configuration Process:</strong> The global LogManager
+ * object is created and configured when the class
+ * <code>java.util.logging.LogManager</code> is initialized.
+ * The configuration process includes the subsequent steps:
+ *
+ * <ol>
+ * <li>If the system property <code>java.util.logging.manager</code>
+ *     is set to the name of a subclass of
+ *     <code>java.util.logging.LogManager</code>, an instance of
+ *     that subclass is created and becomes the global LogManager.
+ *     Otherwise, a new instance of LogManager is created.</li>
+ *     
+ * <li>The <code>LogManager</code> constructor tries to create
+ *     a new instance of the class specified by the system
+ *     property <code>java.util.logging.config.class</code>.
+ *     Typically, the constructor of this class will call
+ *     <code>LogManager.getLogManager().readConfiguration(java.io.InputStream)</code>
+ *     for configuring the logging framework.
+ *     The configuration process stops at this point if
+ *     the system property <code>java.util.logging.config.class</code>
+ *     is set (irrespective of whether the class constructor
+ *     could be called or an exception was thrown).</li>
+ *
+ * <li>If the system property <code>java.util.logging.config.class</code>
+ *     is <em>not</em> set, the configuration parameters are read in from
+ *     a file and passed to
+ *     {@link #readConfiguration(java.io.InputStream)}.
+ *     The name and location of this file are specified by the system
+ *     property <code>java.util.logging.config.file</code>.</li>
+ *
+ * <li>If the system property <code>java.util.logging.config.file</code>
+ *     is not set, however, the contents of the URL
+ *     "{gnu.classpath.home.url}/logging.properties" are passed to
+ *     {@link #readConfiguration(java.io.InputStream)}.
+ *     Here, "{gnu.classpath.home.url}" stands for the value of
+ *     the system property <code>gnu.classpath.home.url</code>.</li>
+ * </ol>
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class LogManager
+{
+  /**
+   * The singleton LogManager instance.
+   */
+  private static LogManager logManager;
+  
+
+  /**
+   * The registered named loggers; maps the name of a Logger to
+   * a WeakReference to it.
+   */
+  private Map loggers;
+
+  final Logger rootLogger;
+
+
+  /**
+   * The properties for the logging framework which have been
+   * read in last.
+   */
+  private Properties properties;
+
+  /**
+   * A delegate object that provides support for handling
+   * PropertyChangeEvents.  The API specification does not
+   * mention which bean should be the source in the distributed
+   * PropertyChangeEvents, but Mauve test code has determined that
+   * the Sun J2SE 1.4 reference implementation uses the LogManager
+   * class object. This is somewhat strange, as the class object
+   * is not the bean with which listeners have to register, but
+   * there is no reason for the GNU Classpath implementation to
+   * behave differently from the reference implementation in
+   * this case.
+   */
+  private final PropertyChangeSupport pcs
+    = new PropertyChangeSupport(/* source bean */ LogManager.class);
+
+  protected LogManager()
+  {
+    if (logManager != null)
+      throw new IllegalStateException(
+        "there can be only one LogManager; use LogManager.getLogManager()");
+
+    logManager = this;
+    loggers = new java.util.HashMap();
+    rootLogger = new Logger("", null);
+    addLogger(rootLogger);
+    
+    /* Make sure that Logger.global has the rootLogger as its parent.
+     *
+     * Logger.global is set during class initialization of Logger,
+     * which may or may not be before this code is being executed.
+     * For example, on the Sun 1.3.1 and 1.4.0 JVMs, Logger.global
+     * has been set before this code is being executed. In contrast,
+     * Logger.global still is null on GCJ 3.2.  Since the LogManager
+     * and Logger classes are mutually dependent, both behaviors are
+     * correct.
+     *
+     * This means that we cannot depend on Logger.global to have its
+     * value when this code executes, although that variable is final.
+     * Since Logger.getLogger will always return the same logger for
+     * the same name, the subsequent line works fine irrespective of
+     * the order in which classes are initialized.
+     */
+    Logger.getLogger("global").setParent(rootLogger);
+  }
+
+
+  /**
+   * Returns the globally shared LogManager instance.
+   */
+  public static LogManager getLogManager()
+  {
+    return logManager;
+  }
+
+  static
+  {
+    makeLogManager();
+    
+    /* The Javadoc description of the class explains
+     * what is going on here.
+     */
+    Object configurator = createInstance(
+      System.getProperty("java.util.logging.config.class"),
+      /* must be instance of */ Object.class);
+
+    try
+    {
+      if (configurator == null)
+        getLogManager().readConfiguration();
+    }
+    catch (IOException ex)
+    {
+      /* FIXME: Is it ok to ignore exceptions here? */
+    }
+  };
+  
+
+  private static LogManager makeLogManager()
+  {
+    String      managerClassName;
+    LogManager  manager;
+
+    managerClassName = System.getProperty("java.util.logging.manager");
+    manager = (LogManager) createInstance(managerClassName, LogManager.class);
+    if (manager != null)
+      return manager;
+
+    if (managerClassName != null)
+      System.err.println("WARNING: System property \"java.util.logging.manager\""
+                        + " should be the name of a subclass of java.util.logging.LogManager");
+
+    return new LogManager();
+  }
+
+
+  /**
+   * Registers a listener which will be notified when the
+   * logging properties are re-read.
+   */
+  public synchronized void addPropertyChangeListener(PropertyChangeListener listener)
+  {
+    /* do not register null. */
+    listener.getClass();
+
+    pcs.addPropertyChangeListener(listener);
+  }
+
+
+  /**
+   * Unregisters a listener.
+   *
+   * If <code>listener</code> has not been registered previously,
+   * nothing happens.  Also, no exception is thrown if
+   * <code>listener</code> is <code>null</code>.
+   */
+  public synchronized void removePropertyChangeListener(PropertyChangeListener listener)
+  {
+    if (listener != null)
+      pcs.removePropertyChangeListener(listener);
+  }
+
+
+  /**
+   * Adds a named logger.  If a logger with the same name has
+   * already been registered, the method returns <code>false</code>
+   * without adding the logger.
+   *
+   * <p>The <code>LogManager</code> only keeps weak references
+   * to registered loggers.  Therefore, names can become available
+   * after automatic garbage collection.
+   *
+   * @param logger the logger to be added.
+   *
+   * @return <code>true<code>if <code>logger</code> was added,
+   *         <code>false</code> otherwise.
+   *
+   * @throws NullPointerException if <code>name<code> is
+   *         <code>null</code>.
+   */
+  public synchronized boolean addLogger(Logger logger)
+  {
+    /* To developers thinking about to remove the 'synchronized'
+     * declaration from this method: Please read the comment
+     * in java.util.logging.Logger.getLogger(String, String)
+     * and make sure that whatever you change wrt. synchronization
+     * does not endanger thread-safety of Logger.getLogger.
+     * The current implementation of Logger.getLogger assumes
+     * that LogManager does its synchronization on the globally
+     * shared instance of LogManager.
+     */
+
+    String name;
+    WeakReference  ref;
+
+    /* This will throw a NullPointerException if logger is null,
+     * as required by the API specification.
+     */
+    name = logger.getName();
+
+    ref = (WeakReference) loggers.get(name);
+    if (ref != null)
+    {
+      if (ref.get() != null)
+       return false;
+
+      /* There has been a logger under this name in the past,
+       * but it has been garbage collected.
+       */
+      loggers.remove(ref);
+    }
+
+    /* Adding a named logger requires a security permission. */
+    if ((name != null) && !name.equals(""))
+      checkAccess();
+
+    Logger parent = findAncestor(logger);
+    loggers.put(name, new WeakReference(logger));
+    if (parent != logger.getParent())
+      logger.setParent(parent);
+
+    /* It can happen that existing loggers should be children of
+     * the newly added logger. For example, assume that there
+     * already exist loggers under the names "", "foo", and "foo.bar.baz".
+     * When adding "foo.bar", the logger "foo.bar.baz" should change
+     * its parent to "foo.bar".
+     */
+    if (parent != rootLogger)
+    {
+      for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();)
+      {
+        Logger possChild = (Logger) ((WeakReference) loggers.get(iter.next())).get();
+        if ((possChild == null) || (possChild == logger) || (possChild.getParent() != parent))
+         continue;
+
+       if (!possChild.getName().startsWith(name))
+         continue;
+
+       if (possChild.getName().charAt(name.length()) != '.')
+         continue;
+
+       possChild.setParent(logger);
+      }
+    }
+
+    return true;
+  }
+
+
+  /**
+   * Finds the closest ancestor for a logger among the currently
+   * registered ones.  For example, if the currently registered
+   * loggers have the names "", "foo", and "foo.bar", the result for
+   * "foo.bar.baz" will be the logger whose name is "foo.bar".
+   *
+   * @param child a logger for whose name no logger has been
+   *        registered.
+   *
+   * @return the closest ancestor for <code>child</code>,
+   *         or <code>null</code> if <code>child</code>
+   *         is the root logger.
+   *
+   * @throws NullPointerException if <code>child</code>
+   *         is <code>null</code>.
+   */
+  private synchronized Logger findAncestor(Logger child)
+  {
+    String childName = child.getName();
+    Logger best = rootLogger;
+    int    bestNameLength = 0;
+
+    Logger  cand;
+    String  candName;
+    int     candNameLength;
+
+    if (child == rootLogger)
+      return null;
+
+    for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();)
+    {
+      candName = (String) iter.next();
+      candNameLength = candName.length();
+
+      if ((candNameLength > bestNameLength)
+         && childName.startsWith(candName)
+         && (childName.charAt(candNameLength) == '.'))
+      {
+        cand = (Logger) ((WeakReference) loggers.get(candName)).get();
+       if ((cand == null) || (cand == child))
+         continue;
+
+       bestNameLength = candName.length();
+       best = cand;
+      }
+    }
+
+    return best;
+  }
+
+
+  /**
+   * Returns a Logger given its name.
+   *
+   * @param name the name of the logger.
+   *
+   * @return a named Logger, or <code>null</code> if there is no
+   *     logger with that name.
+   *
+   * @throw java.lang.NullPointerException if <code>name</code>
+   *     is <code>null</code>.
+   */
+  public synchronized Logger getLogger(String name)
+  {
+    WeakReference  ref;
+
+    /* Throw a NullPointerException if name is null. */
+    name.getClass();
+
+    ref = (WeakReference) loggers.get(name);
+    if (ref != null)
+      return (Logger) ref.get();
+    else
+      return null;
+  }
+
+
+  /**
+   * Returns an Enumeration of currently registered Logger names.
+   * Since other threads can register loggers at any time, the
+   * result could be different any time this method is called.
+   *
+   * @return an Enumeration with the names of the currently
+   *    registered Loggers.
+   */
+  public synchronized Enumeration getLoggerNames()
+  {
+    return Collections.enumeration(loggers.keySet());
+  }
+
+
+  /**
+   * Resets the logging configuration by removing all handlers for
+   * registered named loggers and setting their level to <code>null</code>.
+   * The level of the root logger will be set to <code>Level.INFO</code>.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   */
+  public synchronized void reset()
+    throws SecurityException
+  {
+    /* Throw a SecurityException if the caller does not have the
+     * permission to control the logging infrastructure.
+     */
+    checkAccess();
+
+    properties = new Properties();
+
+    Iterator iter = loggers.values().iterator();
+    while (iter.hasNext())
+    {
+      WeakReference  ref;
+      Logger         logger;
+
+      ref = (WeakReference) iter.next();
+      if (ref != null)
+      {
+       logger = (Logger) ref.get();
+
+       if (logger == null)
+         iter.remove();
+       else if (logger != rootLogger)
+         logger.setLevel(null);
+      }
+    }
+
+    rootLogger.setLevel(Level.INFO);
+  }
+
+
+  /**
+   * Configures the logging framework by reading a configuration file.
+   * The name and location of this file are specified by the system
+   * property <code>java.util.logging.config.file</code>.  If this
+   * property is not set, the URL
+   * "{gnu.classpath.home.url}/logging.properties" is taken, where
+   * "{gnu.classpath.home.url}" stands for the value of the system
+   * property <code>gnu.classpath.home.url</code>.
+   *
+   * <p>The task of configuring the framework is then delegated to
+   * {@link #readConfiguration(java.io.InputStream)}, which will
+   * notify registered listeners after having read the properties.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure, or if the caller is
+   *         not granted the permission to read the configuration
+   *         file.
+   *
+   * @throws IOException if there is a problem reading in the
+   *         configuration file.
+   */
+  public synchronized void readConfiguration()
+    throws IOException, SecurityException
+  {
+    String       path;
+    InputStream  inputStream;
+
+    path = System.getProperty("java.util.logging.config.file");
+    if ((path == null) || (path.length() == 0))
+    {
+      String url = (System.getProperty("gnu.classpath.home.url")
+                   + "/logging.properties");
+      inputStream = new URL(url).openStream();
+    }
+    else
+    {
+      inputStream = new java.io.FileInputStream(path);
+    }
+
+    try
+    {
+      readConfiguration(inputStream);
+    }
+    finally
+    {
+      /* Close the stream in order to save
+       * resources such as file descriptors.
+       */
+      inputStream.close();
+    }
+  }
+
+
+  public synchronized void readConfiguration(InputStream inputStream)
+    throws IOException, SecurityException
+  {
+    Properties   newProperties;
+    Enumeration  keys;
+
+    checkAccess();
+    newProperties = new Properties();
+    newProperties.load(inputStream);
+    this.properties = newProperties;    
+    keys = newProperties.propertyNames();
+
+    while (keys.hasMoreElements())
+    {
+      String key = (String) keys.nextElement();
+      String value = newProperties.getProperty(key);
+
+      if (value == null)
+       continue;
+
+      if (key.endsWith(".level"))
+      {
+       String loggerName = key.substring(0, key.length() - 6);
+       Logger logger = getLogger(loggerName);
+       if (logger != null)
+       {
+         try
+         {
+           logger.setLevel(Level.parse(value));
+         }
+         catch (Exception _)
+         {
+         }
+         continue;
+       }
+      }
+    }
+
+    /* The API specification does not talk about the
+     * property name that is distributed with the
+     * PropertyChangeEvent.  With test code, it could
+     * be determined that the Sun J2SE 1.4 reference
+     * implementation uses null for the property name.
+     */
+    pcs.firePropertyChange(null, null, null);
+  }
+
+  
+  /**
+   * Returns the value of a configuration property as a String.
+   */
+  public synchronized String getProperty(String name)
+  {
+    if (properties != null)
+      return properties.getProperty(name);
+    else
+      return null;
+  }
+
+
+  /**
+   * Returns the value of a configuration property as an integer.
+   * This function is a helper used by the Classpath implementation
+   * of java.util.logging, it is <em>not</em> specified in the
+   * logging API.
+   *
+   * @param name the name of the configuration property.
+   *
+   * @param defaultValue the value that will be returned if the
+   *        property is not defined, or if its value is not an integer
+   *        number.
+   */
+  static int getIntProperty(String name, int defaultValue)
+  {
+    try
+    {
+      return Integer.parseInt(getLogManager().getProperty(name));
+    }
+    catch (Exception ex)
+    {
+      return defaultValue;
+    }
+  }
+
+
+  /**
+   * Returns the value of a configuration property as an integer,
+   * provided it is inside the acceptable range.
+   * This function is a helper used by the Classpath implementation
+   * of java.util.logging, it is <em>not</em> specified in the
+   * logging API.
+   *
+   * @param name the name of the configuration property.
+   *
+   * @param minValue the lowest acceptable value.
+   *
+   * @param maxValue the highest acceptable value.
+   *
+   * @param defaultValue the value that will be returned if the
+   *        property is not defined, or if its value is not an integer
+   *        number, or if it is less than the minimum value,
+   *        or if it is greater than the maximum value.
+   */
+  static int getIntPropertyClamped(String name, int defaultValue,
+                                  int minValue, int maxValue)
+  {
+    int val = getIntProperty(name, defaultValue);
+    if ((val < minValue) || (val > maxValue))
+      val = defaultValue;
+    return val;
+  }
+
+
+  /**
+   * Returns the value of a configuration property as a boolean.
+   * This function is a helper used by the Classpath implementation
+   * of java.util.logging, it is <em>not</em> specified in the
+   * logging API.
+   *
+   * @param name the name of the configuration property.
+   *
+   * @param defaultValue the value that will be returned if the
+   *        property is not defined, or if its value is neither
+   *        <code>"true"</code> nor <code>"false"</code>.
+   */
+  static boolean getBooleanProperty(String name, boolean defaultValue)
+  {
+    try
+    {
+      return (new Boolean(getLogManager().getProperty(name)))
+        .booleanValue();
+    }
+    catch (Exception ex)
+    {
+      return defaultValue;
+    }
+  }
+
+
+  /**
+   * Returns the value of a configuration property as a Level.
+   * This function is a helper used by the Classpath implementation
+   * of java.util.logging, it is <em>not</em> specified in the
+   * logging API.
+   *
+   * @param propertyName the name of the configuration property.
+   *
+   * @param defaultValue the value that will be returned if the
+   *        property is not defined, or if
+   *        {@link Level.parse(java.lang.String)} does not like
+   *        the property value.
+   */
+  static Level getLevelProperty(String propertyName, Level defaultValue)
+  {
+    try
+    {
+      return Level.parse(getLogManager().getProperty(propertyName));
+    }
+    catch (Exception ex)
+    {
+      return defaultValue;
+    }
+  }
+
+
+  /**
+   * Returns the value of a configuration property as a Class.
+   * This function is a helper used by the Classpath implementation
+   * of java.util.logging, it is <em>not</em> specified in the
+   * logging API.
+   *
+   * @param propertyName the name of the configuration property.
+   *
+   * @param defaultValue the value that will be returned if the
+   *        property is not defined, or if it does not specify
+   *        the name of a loadable class.
+   */
+  static final Class getClassProperty(String propertyName, Class defaultValue)
+  {
+    Class usingClass = null;
+
+    try
+    {
+      String propertyValue = logManager.getProperty(propertyName);
+      if (propertyValue != null)
+        usingClass = Class.forName(propertyValue);
+      if (usingClass != null)
+        return usingClass;
+    }
+    catch (Exception _)
+    {
+    }
+
+    return defaultValue;
+  }
+
+
+  static final Object getInstanceProperty(String propertyName,
+                                         Class ofClass,
+                                         Class defaultClass)
+  {
+    Class klass = getClassProperty(propertyName, defaultClass);
+    if (klass == null)
+      return null;
+
+    try
+    {
+      Object obj = klass.newInstance();
+      if (ofClass.isInstance(obj))
+       return obj;
+    }
+    catch (Exception _)
+    {
+    }
+
+    if (defaultClass == null)
+      return null;
+
+    try
+    {
+      return defaultClass.newInstance();
+    }
+    catch (java.lang.InstantiationException ex)
+    {
+      throw new RuntimeException(ex.getMessage());
+    }
+    catch (java.lang.IllegalAccessException ex)
+    {
+      throw new RuntimeException(ex.getMessage());
+    }
+  }
+
+
+  /**
+   * An instance of <code>LoggingPermission("control")</code>
+   * that is shared between calls to <code>checkAccess()</code>.
+   */
+  private static final LoggingPermission controlPermission
+    = new LoggingPermission("control", null);
+
+
+  /**
+   * Checks whether the current security context allows changing
+   * the configuration of the logging framework.  For the security
+   * context to be trusted, it has to be granted
+   * a LoggingPermission("control").
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   */
+  public void checkAccess()
+    throws SecurityException
+  {
+    SecurityManager sm = System.getSecurityManager();
+    if (sm != null)
+      sm.checkPermission(controlPermission);
+  }
+
+
+  /** 
+   * Creates a new instance of a class specified by name.
+   *
+   * @param className the name of the class of which a new instance
+   *        should be created.
+   *       
+   * @param ofClass the class to which the new instance should
+   *        be either an instance or an instance of a subclass.
+   *        FIXME: This description is just terrible.
+   *
+   * @return the new instance, or <code>null</code> if
+   *         <code>className</code> is <code>null</code>, if no class
+   *         with that name could be found, if there was an error
+   *         loading that class, or if the constructor of the class
+   *         has thrown an exception.
+   */
+  static final Object createInstance(String className, Class ofClass)
+  {
+    Class   klass;
+
+    if ((className == null) || (className.length() == 0))
+      return null;
+
+    try
+    {
+      klass = Class.forName(className);
+      if (!ofClass.isAssignableFrom(klass))
+       return null;
+
+      return klass.newInstance();
+    }
+    catch (Exception _)
+    {
+      return null;
+    }
+    catch (java.lang.LinkageError _)
+    {
+      return null;
+    }
+  }
+}
diff --git a/libjava/java/util/logging/LogRecord.java b/libjava/java/util/logging/LogRecord.java
new file mode 100644 (file)
index 0000000..9fd6cd8
--- /dev/null
@@ -0,0 +1,675 @@
+/* LogRecord.java
+   -- a class for the state associated with individual logging events
+
+Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.util.ResourceBundle;
+
+
+/**
+ * A <code>LogRecord</code> contains the state for an individual
+ * event to be logged.
+ *
+ * <p>As soon as a LogRecord instance has been handed over to the
+ * logging framework, applications should not manipulate it anymore.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class LogRecord
+  implements java.io.Serializable
+{
+  /**
+   * The severity level of this <code>LogRecord</code>.
+   */
+  private Level      level;
+
+
+  /**
+   * The sequence number of this <code>LogRecord</code>.
+   */
+  private long       sequenceNumber;
+
+
+  /**
+   * The name of the class that issued the logging request, or
+   * <code>null</code> if this information could not be obtained.
+   */
+  private String     sourceClassName;
+
+
+  /**
+   * The name of the method that issued the logging request, or
+   * <code>null</code> if this information could not be obtained.
+   */
+  private String sourceMethodName;
+
+
+  /**
+   * The message for this <code>LogRecord</code> before
+   * any localization or formatting.
+   */
+  private String message;
+
+
+  /**
+   * An identifier for the thread in which this <code>LogRecord</code>
+   * was created.  The identifier is not necessarily related to any
+   * thread identifiers used by the operating system.
+   */
+  private int threadID;
+
+
+  /**
+   * The time when this <code>LogRecord</code> was created,
+   * in milliseconds since the beginning of January 1, 1970.
+   */
+  private long millis;
+
+
+  /**
+   * The Throwable associated with this <code>LogRecord</code>, or
+   * <code>null</code> if the logged event is not related to an
+   * exception or error.
+   */
+  private Throwable thrown;
+
+
+  /**
+   * The name of the logger where this <code>LogRecord</code> has
+   * originated, or <code>null</code> if this <code>LogRecord</code>
+   * does not originate from a <code>Logger</code>.
+   */
+  private String  loggerName;
+
+
+  /**
+   * The name of the resource bundle used for localizing log messages,
+   * or <code>null</code> if no bundle has been specified.
+   */
+  private String resourceBundleName;
+
+  private transient Object[] parameters;
+
+  private transient ResourceBundle bundle;
+
+
+  /**
+   * Constructs a <code>LogRecord</code> given a severity level and
+   * an unlocalized message text.  In addition, the sequence number,
+   * creation time (as returned by <code>getMillis()</code>) and
+   * thread ID are assigned. All other properties are set to
+   * <code>null</code>.
+   *
+   * @param level the severity level, for example <code>Level.WARNING</code>.
+   *
+   * @param message the message text (which will be used as key
+   *                for looking up the localized message text
+   *                if a resource bundle has been associated). 
+   */
+  public LogRecord(Level level, String message)
+  {
+    this.level = level;
+    this.message = message;
+    this.millis = System.currentTimeMillis();
+
+    /* A subclass of java.lang.Thread could override hashCode(),
+     * in which case the result would not be guaranteed anymore
+     * to be unique among all threads.  While System.identityHashCode
+     * is not necessarily unique either, it at least cannot be
+     * overridden by user code.  However, is might be a good idea
+     * to use something better for generating thread IDs.
+     */
+    this.threadID = System.identityHashCode(Thread.currentThread());
+
+    sequenceNumber = allocateSeqNum();
+  }
+
+
+  /**
+   * Determined with the serialver tool of the Sun J2SE 1.4.
+   */
+  static final long serialVersionUID = 5372048053134512534L;
+
+  private void readObject(java.io.ObjectInputStream in)
+    throws java.io.IOException, java.lang.ClassNotFoundException
+  {
+    in.defaultReadObject();
+
+    /* We assume that future versions will be downwards compatible,
+     * so we can ignore the versions.
+     */
+    byte majorVersion = in.readByte();
+    byte minorVersion = in.readByte();
+
+    int numParams = in.readInt();
+    if (numParams >= 0)
+    {
+      parameters = new Object[numParams];
+      for (int i = 0; i < numParams; i++)
+       parameters[i] = in.readObject();
+    }
+  }
+
+
+  /**
+   * @serialData The default fields, followed by a major byte version
+   * number, followed by a minor byte version number, followed by
+   * information about the log record parameters.  If
+   * <code>parameters</code> is <code>null</code>, the integer -1 is
+   * written, otherwise the length of the <code>parameters</code>
+   * array (which can be zero), followed by the result of calling
+   * {@link Object#toString() toString()} on the parameter (or
+   * <code>null</code> if the parameter is <code>null</code>).
+   *
+   * <p><strong>Specification Note:</strong> The Javadoc for the
+   * Sun reference implementation does not specify the version
+   * number. FIXME: Reverse-engineer the JDK and file a bug
+   * report with Sun, asking for amendment of the specification.
+   */
+  private void writeObject(java.io.ObjectOutputStream out)
+    throws java.io.IOException
+  {
+    out.defaultWriteObject();
+
+    /* Major, minor version number: The Javadoc for J2SE1.4 does not
+     * specify the values.
+     */
+    out.writeByte(0);
+    out.writeByte(0);
+
+    if (parameters == null)
+      out.writeInt(-1);
+    else
+    {
+      out.writeInt(parameters.length);
+      for (int i = 0; i < parameters.length; i++)
+      {
+       if (parameters[i] == null)
+         out.writeObject(null);
+       else
+         out.writeObject(parameters[i].toString());
+      }
+    }
+  }
+
+
+  /**
+   * Returns the name of the logger where this <code>LogRecord</code>
+   * has originated.
+   *
+   * @return the name of the source {@link Logger}, or
+   *         <code>null</code> if this <code>LogRecord</code>
+   *         does not originate from a <code>Logger</code>.
+   */
+  public String getLoggerName()
+  {
+    return loggerName;
+  }
+
+
+  /**
+   * Sets the name of the logger where this <code>LogRecord</code>
+   * has originated.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param name the name of the source logger, or <code>null</code> to
+   *             indicate that this <code>LogRecord</code> does not
+   *             originate from a <code>Logger</code>.
+   */
+  public void setLoggerName(String name)
+  {
+    loggerName = name;
+  }
+
+
+  /**
+   * Returns the resource bundle that is used when the message
+   * of this <code>LogRecord</code> needs to be localized.
+   *
+   * @return the resource bundle used for localization,
+   *         or <code>null</code> if this message does not need
+   *         to be localized.
+   */
+  public ResourceBundle getResourceBundle()
+  {
+    return bundle;
+  }
+
+
+  /**
+   * Sets the resource bundle that is used when the message
+   * of this <code>LogRecord</code> needs to be localized.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param bundle  the resource bundle to be used, or
+   *                <code>null</code> to indicate that this
+   *                message does not need to be localized.
+   */
+  public void setResourceBundle(ResourceBundle bundle)
+  {
+    this.bundle = bundle;
+
+    /* FIXME: Is there a way to infer the name
+     * of a resource bundle from a ResourceBundle object?
+     */
+    this.resourceBundleName = null;
+  }
+
+
+  /**
+   * Returns the name of the resource bundle that is used when the
+   * message of this <code>LogRecord</code> needs to be localized.
+   *
+   * @return the name of the resource bundle used for localization,
+   *         or <code>null</code> if this message does not need
+   *         to be localized.
+   */
+  public String getResourceBundleName()
+  {
+    return resourceBundleName;
+  }
+
+
+  /**
+   * Sets the name of the resource bundle that is used when the
+   * message of this <code>LogRecord</code> needs to be localized.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param name the name of the resource bundle to be used, or
+   *             <code>null</code> to indicate that this message
+   *             does not need to be localized.
+   */
+  public void setResourceBundleName(String name)
+  {
+    resourceBundleName = name;
+    bundle = null;
+    
+    try
+    {
+      if (resourceBundleName != null)
+       bundle = ResourceBundle.getBundle(resourceBundleName);
+    }
+    catch (java.util.MissingResourceException _)
+    {
+    }
+  }
+
+
+  /**
+   * Returns the level of the LogRecord.
+   *
+   * <p>Applications should be aware of the possibility that the
+   *  result is not necessarily one of the standard logging levels,
+   *  since the logging framework allows to create custom subclasses
+   *  of <code>java.util.logging.Level</code>.  Therefore, filters
+   *  should perform checks like <code>theRecord.getLevel().intValue()
+   *  == Level.INFO.intValue()</code> instead of <code>theRecord.getLevel()
+   *  == Level.INFO</code>.
+   */
+  public Level getLevel()
+  {
+    return level;
+  }
+
+
+  /**
+   * Sets the severity level of this <code>LogRecord</code> to a new
+   * value.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param level the new severity level, for example
+   *              <code>Level.WARNING</code>.
+   */
+  public void setLevel(Level level)
+  {
+    this.level = level;
+  }
+
+
+  /**
+   * The last used sequence number for any LogRecord.
+   */
+  private static long lastSeqNum = 0;
+
+
+  /**
+   * Allocates a sequence number for a new LogRecord.  This class
+   * method is only called by the LogRecord constructor.
+   */
+  private synchronized static long allocateSeqNum()
+  {
+    lastSeqNum += 1;
+    return lastSeqNum;
+  }
+
+
+  /**
+   * Returns the sequence number of this <code>LogRecord</code>.
+   */
+  public long getSequenceNumber()
+  {
+    return sequenceNumber;
+  }
+
+
+  /**
+   * Sets the sequence number of this <code>LogRecord</code> to a new
+   * value.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param seqNum the new sequence number.
+   */
+  public void setSequenceNumber(long seqNum)
+  {
+    this.sequenceNumber = seqNum;
+  }
+
+
+  /**
+   * Returns the name of the class where the event being logged
+   * has had its origin.  This information can be passed as
+   * parameter to some logging calls, and in certain cases, the
+   * logging framework tries to determine an approximation
+   * (which may or may not be accurate).
+   * 
+   * @return the name of the class that issued the logging request,
+   *         or <code>null</code> if this information could not
+   *         be obtained.
+   */
+  public String getSourceClassName()
+  {
+    if (sourceClassName != null)
+      return sourceClassName;
+
+    /*  FIXME: Should infer this information from the call stack. */
+    return null;
+  }
+
+
+  /**
+   * Sets the name of the class where the event being logged
+   * has had its origin.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   * 
+   * @param sourceClassName the name of the class that issued the
+   *          logging request, or <code>null</code> to indicate that
+   *          this information could not be obtained.
+   */
+  public void setSourceClassName(String sourceClassName)
+  {
+    this.sourceClassName = sourceClassName;
+  }
+
+
+  /**
+   * Returns the name of the method where the event being logged
+   * has had its origin.  This information can be passed as
+   * parameter to some logging calls, and in certain cases, the
+   * logging framework tries to determine an approximation
+   * (which may or may not be accurate).
+   * 
+   * @return the name of the method that issued the logging request,
+   *         or <code>null</code> if this information could not
+   *         be obtained.
+   */
+  public String getSourceMethodName()
+  {
+    if (sourceMethodName != null)
+      return sourceMethodName;
+
+    /* FIXME: Should infer this information from the call stack. */
+    return null;
+  }
+
+
+  /**
+   * Sets the name of the method where the event being logged
+   * has had its origin.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   * 
+   * @param sourceMethodName the name of the method that issued the
+   *          logging request, or <code>null</code> to indicate that
+   *          this information could not be obtained.
+   */
+  public void setSourceMethodName(String sourceMethodName)
+  {
+    this.sourceMethodName = sourceMethodName;
+  }
+
+
+  /**
+   * Returns the message for this <code>LogRecord</code> before
+   * any localization or parameter substitution.
+   *
+   * <p>A {@link Logger} will try to localize the message
+   * if a resource bundle has been associated with this
+   * <code>LogRecord</code>.  In this case, the logger will call
+   * <code>getMessage()</code> and use the result as the key
+   * for looking up the localized message in the bundle.
+   * If no bundle has been associated, or if the result of
+   * <code>getMessage()</code> is not a valid key in the
+   * bundle, the logger will use the raw message text as
+   * returned by this method.
+   *
+   * @return the message text, or <code>null</code> if there
+   *         is no message text.
+   */
+  public String getMessage()
+  {
+    return message;
+  }
+
+
+  /**
+   * Sets the message for this <code>LogRecord</code>.
+   *
+   * <p>A <code>Logger</code> will try to localize the message
+   * if a resource bundle has been associated with this
+   * <code>LogRecord</code>.  In this case, the logger will call
+   * <code>getMessage()</code> and use the result as the key
+   * for looking up the localized message in the bundle.
+   * If no bundle has been associated, or if the result of
+   * <code>getMessage()</code> is not a valid key in the
+   * bundle, the logger will use the raw message text as
+   * returned by this method.
+   *
+   * <p>It is possible to set the message to either an empty String or
+   * <code>null</code>, although this does not make the the message
+   * very helpful to human users.
+   *
+   * @param message the message text (which will be used as key
+   *                for looking up the localized message text
+   *                if a resource bundle has been associated). 
+   */
+  public void setMessage(String message)
+  {
+    this.message = message;
+  }
+
+
+  /**
+   * Returns the parameters to the log message.
+   *
+   * @return the parameters to the message, or <code>null</code> if
+   *         the message has no parameters.
+   */
+  public Object[] getParameters()
+  {
+    return parameters;
+  }
+
+
+  /**
+   * Sets the parameters to the log message.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param parameters the parameters to the message, or <code>null</code>
+   *                   to indicate that the message has no parameters.
+   */
+  public void setParameters(Object[] parameters)
+  {
+    this.parameters = parameters;
+  }
+
+
+  /**
+   * Returns an identifier for the thread in which this
+   * <code>LogRecord</code> was created.  The identifier is not
+   * necessarily related to any thread identifiers used by the
+   * operating system.
+   *
+   * @return an identifier for the source thread.
+   */
+  public int getThreadID()
+  {
+    return threadID;
+  }
+
+
+  /**
+   * Sets the identifier indicating in which thread this
+   * <code>LogRecord</code> was created.  The identifier is not
+   * necessarily related to any thread identifiers used by the
+   * operating system.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param threadID the identifier for the source thread.
+   */
+  public void setThreadID(int threadID)
+  {
+    this.threadID = threadID;
+  }
+
+
+  /**
+   * Returns the time when this <code>LogRecord</code> was created.
+   *
+   * @return the time of creation in milliseconds since the beginning
+   *         of January 1, 1970.
+   */
+  public long getMillis()
+  {
+    return millis;
+  }
+
+
+  /**
+   * Sets the time when this <code>LogRecord</code> was created.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param millis the time of creation in milliseconds since the
+   *               beginning of January 1, 1970.
+   */
+  public void setMillis(long millis)
+  {
+    this.millis = millis;
+  }
+
+
+  /**
+   * Returns the Throwable associated with this <code>LogRecord</code>,
+   * or <code>null</code> if the logged event is not related to an exception
+   * or error.
+   */
+  public Throwable getThrown()
+  {
+    return thrown;
+  }
+
+
+  /**
+   * Associates this <code>LogRecord</code> with an exception or error.
+   *
+   * <p>As soon as a <code>LogRecord</code> has been handed over
+   * to the logging framework, applications should not modify it
+   * anymore.  Therefore, this method should only be called on
+   * freshly constructed LogRecords.
+   *
+   * @param thrown the exception or error to associate with, or
+   *               <code>null</code> if this <code>LogRecord</code>
+   *               should be made unrelated to an exception or error.
+   */
+  public void setThrown(Throwable thrown)
+  {
+    this.thrown = thrown;
+  }
+}
diff --git a/libjava/java/util/logging/Logger.java b/libjava/java/util/logging/Logger.java
new file mode 100644 (file)
index 0000000..e142e20
--- /dev/null
@@ -0,0 +1,1167 @@
+/* Logger.java
+   -- a class for logging messages
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.util.ResourceBundle;
+import java.util.MissingResourceException;
+import java.util.List;
+
+/**
+ * A Logger is used for logging information about events. Usually, there
+ * is a seprate logger for each subsystem or component, although there
+ * is a shared instance for components that make only occasional use of
+ * the logging framework.
+ *
+ * <p>It is common to name a logger after the name of a corresponding
+ * Java package.  Loggers are organized into a hierarchical namespace;
+ * for example, the logger <code>"org.gnu.foo"</code> is the
+ * <em>parent</em> of logger <code>"org.gnu.foo.bar"</code>.
+ *
+ * <p>A logger for a named subsystem can be obtained through {@link
+ * java.util.logging.Logger#getLogger(java.lang.String)}.  However,
+ * only code which has been granted the permission to control the
+ * logging infrastructure will be allowed to customize that logger.
+ * Untrusted code can obtain a private, anonymous logger through
+ * {@link #getAnonymousLogger()} if it wants to perform any
+ * modifications to the logger.
+ *
+ * <p>FIXME: Write more documentation.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class Logger
+{
+  /**
+   * A logger provided to applications that make only occasional use
+   * of the logging framework, typically early prototypes.  Serious
+   * products are supposed to create and use their own Loggers, so
+   * they can be controlled individually.
+   */
+  public static final Logger global = getLogger("global");
+
+
+  /**
+   * The name of the Logger, or <code>null</code> if the logger is
+   * anonymous.
+   *
+   * <p>A previous version of the GNU Classpath implementation granted
+   * untrusted code the permission to control any logger whose name
+   * was null.  However, test code revealed that the Sun J2SE 1.4
+   * reference implementation enforces the security control for any
+   * logger that was not created through getAnonymousLogger, even if
+   * it has a null name.  Therefore, a separate flag {@link
+   * Logger#anonymous} was introduced.
+   */
+  private final String name;
+
+
+  /**
+   * The name of the resource bundle used for localization.
+   *
+   * <p>This variable cannot be declared as <code>final</code>
+   * because its value can change as a result of calling
+   * getLogger(String,String).
+   */
+  private String resourceBundleName;
+
+
+  /**
+   * The resource bundle used for localization.
+   *
+   * <p>This variable cannot be declared as <code>final</code>
+   * because its value can change as a result of calling
+   * getLogger(String,String).
+   */
+  private ResourceBundle resourceBundle;
+
+  private Filter filter;
+
+  private final List handlerList = new java.util.ArrayList(4);
+  private Handler[] handlers = new Handler[0];
+
+  /**
+   * Indicates whether or not this logger is anonymous.  While
+   * a LoggingPermission is required for any modifications to
+   * a normal logger, untrusted code can obtain an anonymous logger
+   * and modify it according to its needs.
+   *
+   * <p>A previous version of the GNU Classpath implementation
+   * granted access to every logger whose name was null.
+   * However, test code revealed that the Sun J2SE 1.4 reference
+   * implementation enforces the security control for any logger
+   * that was not created through getAnonymousLogger, even
+   * if it has a null name.
+   */
+  private boolean anonymous;
+
+
+  private boolean useParentHandlers;
+
+  private Level level;
+
+  private Logger parent;
+
+  /**
+   * Constructs a Logger for a subsystem.  Most applications do not
+   * need to create new Loggers explicitly; instead, they should call
+   * the static factory methods
+   * {@link #getLogger(java.lang.String,java.lang.String) getLogger}
+   * (with ResourceBundle for localization) or
+   * {@link #getLogger(java.lang.String) getLogger} (without
+   * ResourceBundle), respectively.
+   *
+   * @param name the name for the logger, for example "java.awt"
+   *             or "com.foo.bar". The name should be based on
+   *             the name of the package issuing log records
+   *             and consist of dot-separated Java identifiers.
+   *
+   * @param resourceBundleName the name of a resource bundle
+   *        for localizing messages, or <code>null</code>
+   *       to indicate that messages do not need to be localized.
+   *
+   * @throws java.util.MissingResourceException if
+   *         <code>resourceBundleName</code> is not <code>null</code>
+   *         and no such bundle could be located.
+   */
+  protected Logger(String name, String resourceBundleName)
+    throws MissingResourceException
+  {
+    this.name = name;
+    this.resourceBundleName = resourceBundleName;
+
+    if (resourceBundleName == null)
+      resourceBundle = null;
+    else
+      resourceBundle = ResourceBundle.getBundle(resourceBundleName);
+
+    level = null;
+
+    /* This is null when the root logger is being constructed,
+     * and the root logger afterwards.
+     */
+    parent = LogManager.getLogManager().rootLogger;
+
+    useParentHandlers = (parent != null);
+  }
+
+
+
+  /**
+   * Finds a registered logger for a subsystem, or creates one in
+   * case no logger has been registered yet.
+   *
+   * @param name the name for the logger, for example "java.awt"
+   *             or "com.foo.bar". The name should be based on
+   *             the name of the package issuing log records
+   *             and consist of dot-separated Java identifiers.
+   *
+   * @throws IllegalArgumentException if a logger for the subsystem
+   *         identified by <code>name</code> has already been created,
+   *         but uses a a resource bundle for localizing messages.
+   *
+   * @throws NullPointerException if <code>name</code> is
+   *         <code>null</code>.
+   *
+   * @return a logger for the subsystem specified by <code>name</code>
+   *         that does not localize messages.
+   */
+  public static Logger getLogger(String name)
+  {
+    return getLogger(name, null);
+  }
+
+    
+  /**
+   * Finds a registered logger for a subsystem, or creates one in case
+   * no logger has been registered yet.
+   *
+   * <p>If a logger with the specified name has already been
+   * registered, the behavior depends on the resource bundle that is
+   * currently associated with the existing logger.
+   *
+   * <ul><li>If the existing logger uses the same resource bundle as
+   * specified by <code>resourceBundleName</code>, the existing logger
+   * is returned.</li>
+   *
+   * <li>If the existing logger currently does not localize messages,
+   * the existing logger is modified to use the bundle specified by
+   * <code>resourceBundleName</code>.  The existing logger is then
+   * returned.  Therefore, all subsystems currently using this logger
+   * will produce localized messages from now on.</li>
+   *
+   * <li>If the existing logger already has an associated resource
+   * bundle, but a different one than specified by
+   * <code>resourceBundleName</code>, an
+   * <code>IllegalArgumentException</code> is thrown.</li></ul>
+   *
+   * @param name the name for the logger, for example "java.awt"
+   *             or "org.gnu.foo". The name should be based on
+   *             the name of the package issuing log records
+   *             and consist of dot-separated Java identifiers.
+   *
+   * @param resourceBundleName the name of a resource bundle
+   *        for localizing messages, or <code>null</code>
+   *       to indicate that messages do not need to be localized.
+   *
+   * @return a logger for the subsystem specified by <code>name</code>.
+   *
+   * @throws java.util.MissingResourceException if
+   *         <code>resourceBundleName</code> is not <code>null</code>
+   *         and no such bundle could be located.   
+   *
+   * @throws IllegalArgumentException if a logger for the subsystem
+   *         identified by <code>name</code> has already been created,
+   *         but uses a different resource bundle for localizing
+   *         messages.
+   *
+   * @throws NullPointerException if <code>name</code> is
+   *         <code>null</code>.
+   */
+  public static Logger getLogger(String name, String resourceBundleName)
+  {
+    LogManager lm = LogManager.getLogManager();
+    Logger     result;
+
+    /* Throw NullPointerException if name is null. */
+    name.getClass();
+
+    /* Without synchronized(lm), it could happen that another thread
+     * would create a logger between our calls to getLogger and
+     * addLogger.  While addLogger would indicate this by returning
+     * false, we could not be sure that this other logger was still
+     * existing when we called getLogger a second time in order
+     * to retrieve it -- note that LogManager is only allowed to
+     * keep weak references to registered loggers, so Loggers
+     * can be garbage collected at any time in general, and between
+     * our call to addLogger and our second call go getLogger
+     * in particular.
+     *
+     * Of course, we assume here that LogManager.addLogger etc.
+     * are synchronizing on the global LogManager object. There
+     * is a comment in the implementation of LogManager.addLogger
+     * referring to this comment here, so that any change in
+     * the synchronization of LogManager will be reflected here.
+     */
+    synchronized (lm)
+    {
+      result = lm.getLogger(name);
+      if (result == null)
+      {
+       boolean couldBeAdded;
+
+       result = new Logger(name, resourceBundleName);
+       couldBeAdded = lm.addLogger(result);
+       if (!couldBeAdded)
+         throw new IllegalStateException("cannot register new logger");
+      }
+      else
+      {
+       /* The logger already exists. Make sure it uses
+        * the same resource bundle for localizing messages.
+        */
+       String existingBundleName = result.getResourceBundleName();
+
+       /* The Sun J2SE 1.4 reference implementation will return the
+        * registered logger object, even if it does not have a resource
+        * bundle associated with it. However, it seems to change the
+        * resourceBundle of the registered logger to the bundle
+        * whose name was passed to getLogger.
+        */
+       if ((existingBundleName == null) && (resourceBundleName != null))
+       {
+         /* If ResourceBundle.getBundle throws an exception, the
+          * existing logger will be unchanged.  This would be
+          * different if the assignment to resourceBundleName
+          * came first.
+          */
+         result.resourceBundle = ResourceBundle.getBundle(resourceBundleName);
+         result.resourceBundleName = resourceBundleName;
+         return result;
+       }
+
+       if ((existingBundleName != resourceBundleName)
+           && ((existingBundleName == null)
+               || !existingBundleName.equals(resourceBundleName)))
+       {
+         throw new IllegalArgumentException();
+       }
+      }
+    }
+
+    return result;
+  }
+
+  
+  /**
+   * Creates a new, unnamed logger.  Unnamed loggers are not
+   * registered in the namespace of the LogManager, and no special
+   * security permission is required for changing their state.
+   * Therefore, untrusted applets are able to modify their private
+   * logger instance obtained through this method.
+   *
+   * <p>The parent of the newly created logger will the the root
+   * logger, from which the level threshold and the handlers are
+   * inherited.
+   */
+  public static Logger getAnonymousLogger()
+  {
+    return getAnonymousLogger(null);
+  }
+
+
+  /**
+   * Creates a new, unnamed logger.  Unnamed loggers are not
+   * registered in the namespace of the LogManager, and no special
+   * security permission is required for changing their state.
+   * Therefore, untrusted applets are able to modify their private
+   * logger instance obtained through this method.
+   *
+   * <p>The parent of the newly created logger will the the root
+   * logger, from which the level threshold and the handlers are
+   * inherited.
+   *
+   * @param resourceBundleName the name of a resource bundle
+   *        for localizing messages, or <code>null</code>
+   *       to indicate that messages do not need to be localized.
+   *
+   * @throws java.util.MissingResourceException if
+   *         <code>resourceBundleName</code> is not <code>null</code>
+   *         and no such bundle could be located.
+   */
+  public static Logger getAnonymousLogger(String resourceBundleName)
+    throws MissingResourceException
+  {
+    Logger  result;
+
+    result = new Logger(null, resourceBundleName);
+    result.anonymous = true;
+    return result;
+  }
+
+
+  /**
+   * Returns the name of the resource bundle that is being used for
+   * localizing messages.
+   *
+   * @return the name of the resource bundle used for localizing messages,
+   *         or <code>null</code> if the parent's resource bundle
+   *         is used for this purpose.
+   */
+  public synchronized String getResourceBundleName()
+  {
+    return resourceBundleName;
+  }
+
+
+  /**
+   * Returns the resource bundle that is being used for localizing
+   * messages.
+   *
+   * @return the resource bundle used for localizing messages,
+   *         or <code>null</code> if the parent's resource bundle
+   *         is used for this purpose.
+   */
+  public synchronized ResourceBundle getResourceBundle()
+  {
+    return resourceBundle;
+  }
+
+
+  /**
+   * Returns the severity level threshold for this <code>Handler</code>.
+   * All log records with a lower severity level will be discarded;
+   * a log record of the same or a higher level will be published
+   * unless an installed <code>Filter</code> decides to discard it.
+   *
+   * @return the severity level below which all log messages will be
+   *         discarded, or <code>null</code> if the logger inherits
+   *         the threshold from its parent.
+   */
+  public synchronized Level getLevel()
+  {
+    return level;
+  }
+
+
+  /**
+   * Returns whether or not a message of the specified level
+   * would be logged by this logger.
+   *
+   * @throws NullPointerException if <code>level</code>
+   *         is <code>null</code>.
+   */
+  public synchronized boolean isLoggable(Level level)
+  {
+    if (this.level != null)
+      return this.level.intValue() <= level.intValue();
+
+    if (parent != null)
+      return parent.isLoggable(level);
+    else
+      return false;
+  }
+
+
+  /**
+   * Sets the severity level threshold for this <code>Handler</code>.
+   * All log records with a lower severity level will be discarded
+   * immediately.  A log record of the same or a higher level will be
+   * published unless an installed <code>Filter</code> decides to
+   * discard it.
+   *
+   * @param level the severity level below which all log messages
+   *              will be discarded, or <code>null</code> to
+   *              indicate that the logger should inherit the
+   *              threshold from its parent.
+   *
+   * @throws SecurityException if this logger is not anonymous, a
+   *     security manager exists, and the caller is not granted
+   *     the permission to control the logging infrastructure by
+   *     having LoggingPermission("control").  Untrusted code can
+   *     obtain an anonymous logger through the static factory method
+   *     {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}.
+   */
+  public synchronized void setLevel(Level level)
+  {
+    /* An application is allowed to control an anonymous logger
+     * without having the permission to control the logging
+     * infrastructure.
+     */
+    if (!anonymous)
+      LogManager.getLogManager().checkAccess();
+
+    this.level = level;
+  }
+
+
+  public synchronized Filter getFilter()
+  {
+    return filter;
+  }
+
+
+  /**
+   * @throws SecurityException if this logger is not anonymous, a
+   *     security manager exists, and the caller is not granted
+   *     the permission to control the logging infrastructure by
+   *     having LoggingPermission("control").  Untrusted code can
+   *     obtain an anonymous logger through the static factory method
+   *     {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}.
+   */
+  public synchronized void setFilter(Filter filter)
+    throws SecurityException
+  {
+    /* An application is allowed to control an anonymous logger
+     * without having the permission to control the logging
+     * infrastructure.
+     */
+    if (!anonymous)
+      LogManager.getLogManager().checkAccess();
+
+    this.filter = filter;
+  }
+
+
+
+
+  /**
+   * Returns the name of this logger.
+   *
+   * @return the name of this logger, or <code>null</code> if
+   *         the logger is anonymous.
+   */
+  public String getName()
+  {
+    /* Note that the name of a logger cannot be changed during
+     * its lifetime, so no synchronization is needed.
+     */
+    return name;
+  }
+
+
+  /**
+   * Passes a record to registered handlers, provided the record
+   * is considered as loggable both by {@link #isLoggable(Level)}
+   * and a possibly installed custom {@link #setFilter(Filter) filter}.
+   *
+   * <p>If the logger has been configured to use parent handlers,
+   * the record will be forwarded to the parent of this logger
+   * in addition to being processed by the handlers registered with
+   * this logger.
+   *
+   * <p>The other logging methods in this class are convenience methods
+   * that merely create a new LogRecord and pass it to this method.
+   * Therefore, subclasses usually just need to override this single
+   * method for customizing the logging behavior.
+   *
+   * @param record the log record to be inspected and possibly forwarded.
+   */
+  public synchronized void log(LogRecord record)
+  {
+    if (!isLoggable(record.getLevel()))
+      return;
+
+    if ((filter != null) && !filter.isLoggable(record))
+      return;
+
+    /* If no logger name has been set for the log record,
+     * use the name of this logger.
+     */
+    if (record.getLoggerName() == null)
+      record.setLoggerName(name);
+
+    /* Avoid that some other thread is changing the logger hierarchy
+     * while we are traversing it.
+     */
+    synchronized (LogManager.getLogManager())
+    {
+      Logger curLogger = this;
+
+      do
+      {
+        /* The Sun J2SE 1.4 reference implementation seems to call the
+        * filter only for the logger whose log method is called,
+        * never for any of its parents.  Also, parent loggers publish
+        * log record whatever their level might be.  This is pretty
+        * weird, but GNU Classpath tries to be as compatible as
+        * possible to the reference implementation.
+        */
+        for (int i = 0; i < curLogger.handlers.length; i++)
+          curLogger.handlers[i].publish(record);
+
+       if (curLogger.getUseParentHandlers() == false)
+         break;
+       
+       curLogger = curLogger.getParent();
+      }
+      while (parent != null);
+    }
+  }
+
+
+  public void log(Level level, String message)
+  {
+    log(level, message, (Object[]) null);
+  }
+
+
+  public synchronized void log(Level level,
+                              String message,
+                              Object param)
+  {
+    logp(level,
+        /* sourceClass*/ null,
+        /* sourceMethod */ null,
+        message,
+        param);
+  }
+
+
+  public synchronized void log(Level level,
+                              String message,
+                              Object[] params)
+  {
+    logp(level,
+        /* sourceClass*/ null,
+        /* sourceMethod */ null,
+        message,
+        params);
+  }
+
+
+  public synchronized void log(Level level,
+                              String message,
+                              Throwable thrown)
+  {
+    logp(level,
+        /* sourceClass*/ null,
+        /* sourceMethod */ null,
+        message,
+        thrown);
+  }
+
+
+  public synchronized void logp(Level level,
+                               String sourceClass,
+                               String sourceMethod,
+                               String message)
+  {
+    logp(level, sourceClass, sourceMethod, message,
+        (Object[]) null);
+  }
+
+
+  public synchronized void logp(Level level,
+                               String sourceClass,
+                               String sourceMethod,
+                               String message,
+                               Object param)
+  {
+    logp(level, sourceClass, sourceMethod, message,
+        new Object[] { param });
+  }
+
+
+  private synchronized ResourceBundle findResourceBundle()
+  {
+    if (resourceBundle != null)
+      return resourceBundle;
+
+    if (parent != null)
+      return parent.findResourceBundle();
+
+    return null;
+  }
+
+
+  private synchronized void logImpl(Level level,
+                                   String sourceClass,
+                                   String sourceMethod,
+                                   String message,
+                                   Object[] params)
+  {
+    LogRecord rec = new LogRecord(level, message);
+
+    rec.setResourceBundle(findResourceBundle());
+    rec.setSourceClassName(sourceClass);
+    rec.setSourceMethodName(sourceMethod);
+    rec.setParameters(params);
+
+    log(rec);
+  }
+
+
+  public synchronized void logp(Level level,
+                               String sourceClass,
+                               String sourceMethod,
+                               String message,
+                               Object[] params)
+  {
+    logImpl(level, sourceClass, sourceMethod, message, params);
+  }
+
+
+  public synchronized void logp(Level level,
+                               String sourceClass,
+                               String sourceMethod,
+                               String message,
+                               Throwable thrown)
+  {
+    LogRecord rec = new LogRecord(level, message);
+
+    rec.setResourceBundle(resourceBundle);
+    rec.setSourceClassName(sourceClass);
+    rec.setSourceMethodName(sourceMethod);
+    rec.setThrown(thrown);
+
+    log(rec);
+  }
+
+
+  public synchronized void logrb(Level level,
+                                String sourceClass,
+                                String sourceMethod,
+                                String bundleName,
+                                String message)
+  {
+    logrb(level, sourceClass, sourceMethod, bundleName,
+         message, (Object[]) null);
+  }
+
+
+  public synchronized void logrb(Level level,
+                                String sourceClass,
+                                String sourceMethod,
+                                String bundleName,
+                                String message,
+                                Object param)
+  {
+    logrb(level, sourceClass, sourceMethod, bundleName,
+         message, new Object[] { param });
+  }
+
+
+  public synchronized void logrb(Level level,
+                                String sourceClass,
+                                String sourceMethod,
+                                String bundleName,
+                                String message,
+                                Object[] params)
+  {
+    LogRecord rec = new LogRecord(level, message);
+
+    rec.setResourceBundleName(bundleName);
+    rec.setSourceClassName(sourceClass);
+    rec.setSourceMethodName(sourceMethod);
+    rec.setParameters(params);
+
+    log(rec);
+  }
+
+
+  public synchronized void logrb(Level level,
+                                String sourceClass,
+                                String sourceMethod,
+                                String bundleName,
+                                String message,
+                                Throwable thrown)
+  {
+    LogRecord rec = new LogRecord(level, message);
+
+    rec.setResourceBundleName(bundleName);
+    rec.setSourceClassName(sourceClass);
+    rec.setSourceMethodName(sourceMethod);
+    rec.setThrown(thrown);
+
+    log(rec);
+  }
+
+
+  public synchronized void entering(String sourceClass,
+                                   String sourceMethod)
+  {
+    if (isLoggable(Level.FINER))
+      logp(Level.FINER, sourceClass, sourceMethod, "ENTRY");
+  }
+
+
+  public synchronized void entering(String sourceClass,
+                                   String sourceMethod,
+                                   Object param)
+  {
+    if (isLoggable(Level.FINER))
+      logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", param);
+  }
+
+
+  public synchronized void entering(String sourceClass,
+                                   String sourceMethod,
+                                   Object[] params)
+  {
+    if (isLoggable(Level.FINER))
+    {
+      StringBuffer buf = new StringBuffer(80);
+      buf.append("ENTRY");
+      for (int i = 0; i < params.length; i++)
+      {
+       buf.append(" {");
+       buf.append(i);
+       buf.append('}');
+      }
+      
+      logp(Level.FINER, sourceClass, sourceMethod, buf.toString(), params);
+    }
+  }
+
+
+  public synchronized void exiting(String sourceClass,
+                                  String sourceMethod)
+  {
+    if (isLoggable(Level.FINER))
+      logp(Level.FINER, sourceClass, sourceMethod, "RETURN");
+  }
+
+   
+  public synchronized void exiting(String sourceClass,
+                                  String sourceMethod,
+                                  Object result)
+  {
+    if (isLoggable(Level.FINER))
+      logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", result);
+  }
+
+  public synchronized void throwing(String sourceClass,
+                                   String sourceMethod,
+                                   Throwable thrown)
+  {
+    if (isLoggable(Level.FINER))
+      logp(Level.FINER, sourceClass, sourceMethod, "THROW", thrown);
+  }
+
+
+  /**
+   * Logs a message with severity level SEVERE, indicating a serious
+   * failure that prevents normal program execution.  Messages at this
+   * level should be understandable to an inexperienced, non-technical
+   * end user.  Ideally, they explain in simple words what actions the
+   * user can take in order to resolve the problem.
+   *
+   * @see Level#SEVERE
+   *
+   * @param message the message text, also used as look-up key if the
+   *                logger is localizing messages with a resource
+   *                bundle.  While it is possible to pass
+   *                <code>null</code>, this is not recommended, since
+   *                a logging message without text is unlikely to be
+   *                helpful.
+   */
+  public synchronized void severe(String message)
+  {
+    if (isLoggable(Level.SEVERE))
+      log(Level.SEVERE, message);
+  }
+
+
+  /**
+   * Logs a message with severity level WARNING, indicating a
+   * potential problem that does not prevent normal program execution.
+   * Messages at this level should be understandable to an
+   * inexperienced, non-technical end user.  Ideally, they explain in
+   * simple words what actions the user can take in order to resolve
+   * the problem.
+   *
+   * @see Level#WARNING
+   *
+   * @param message the message text, also used as look-up key if the
+   *                logger is localizing messages with a resource
+   *                bundle.  While it is possible to pass
+   *                <code>null</code>, this is not recommended, since
+   *                a logging message without text is unlikely to be
+   *                helpful.
+   */
+  public synchronized void warning(String message)
+  {
+    if (isLoggable(Level.WARNING))
+      log(Level.WARNING, message);
+  }
+
+
+  /**
+   * Logs a message with severity level INFO.  {@link Level#INFO} is
+   * intended for purely informational messages that do not indicate
+   * error or warning situations. In the default logging
+   * configuration, INFO messages will be written to the system
+   * console.  For this reason, the INFO level should be used only for
+   * messages that are important to end users and system
+   * administrators.  Messages at this level should be understandable
+   * to an inexperienced, non-technical user.
+   *
+   * @param message the message text, also used as look-up key if the
+   *                logger is localizing messages with a resource
+   *                bundle.  While it is possible to pass
+   *                <code>null</code>, this is not recommended, since
+   *                a logging message without text is unlikely to be
+   *                helpful.
+   */
+  public synchronized void info(String message)
+  {
+    if (isLoggable(Level.INFO))
+      log(Level.INFO, message);
+  }
+
+
+  /**
+   * Logs a message with severity level CONFIG.  {@link Level#CONFIG} is
+   * intended for static configuration messages, for example about the
+   * windowing environment, the operating system version, etc.
+   *
+   * @param message the message text, also used as look-up key if the
+   *     logger is localizing messages with a resource bundle.  While
+   *     it is possible to pass <code>null</code>, this is not
+   *     recommended, since a logging message without text is unlikely
+   *     to be helpful.
+   */
+  public synchronized void config(String message)
+  {
+    if (isLoggable(Level.CONFIG))
+      log(Level.CONFIG, message);
+  }
+
+
+  /**
+   * Logs a message with severity level FINE.  {@link Level#FINE} is
+   * intended for messages that are relevant for developers using
+   * the component generating log messages. Examples include minor,
+   * recoverable failures, or possible inefficiencies.
+   *
+   * @param message the message text, also used as look-up key if the
+   *                logger is localizing messages with a resource
+   *                bundle.  While it is possible to pass
+   *                <code>null</code>, this is not recommended, since
+   *                a logging message without text is unlikely to be
+   *                helpful.
+   */
+  public synchronized void fine(String message)
+  {
+    if (isLoggable(Level.FINE))
+      log(Level.FINE, message);
+  }
+
+
+  /**
+   * Logs a message with severity level FINER.  {@link Level#FINER} is
+   * intended for rather detailed tracing, for example entering a
+   * method, returning from a method, or throwing an exception.
+   *
+   * @param message the message text, also used as look-up key if the
+   *                logger is localizing messages with a resource
+   *                bundle.  While it is possible to pass
+   *                <code>null</code>, this is not recommended, since
+   *                a logging message without text is unlikely to be
+   *                helpful.
+   */
+  public synchronized void finer(String message)
+  {
+    if (isLoggable(Level.FINER))
+      log(Level.FINER, message);
+  }
+
+
+  /**
+   * Logs a message with severity level FINEST.  {@link Level#FINEST}
+   * is intended for highly detailed tracing, for example reaching a
+   * certain point inside the body of a method.
+   *
+   * @param message the message text, also used as look-up key if the
+   *                logger is localizing messages with a resource
+   *                bundle.  While it is possible to pass
+   *                <code>null</code>, this is not recommended, since
+   *                a logging message without text is unlikely to be
+   *                helpful.
+   */
+  public synchronized void finest(String message)
+  {
+    if (isLoggable(Level.FINEST))
+      log(Level.FINEST, message);
+  }
+
+
+  /**
+   * Adds a handler to the set of handlers that get notified
+   * when a log record is to be published.
+   *
+   * @param handler the handler to be added.
+   *
+   * @throws NullPointerException if <code>handler</code>
+   *     is <code>null</code>.
+   *
+   * @throws SecurityException if this logger is not anonymous, a
+   *     security manager exists, and the caller is not granted
+   *     the permission to control the logging infrastructure by
+   *     having LoggingPermission("control").  Untrusted code can
+   *     obtain an anonymous logger through the static factory method
+   *     {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}.
+   */
+  public synchronized void addHandler(Handler handler)
+    throws SecurityException
+  {
+    /* Throw a new NullPointerException if handler is null. */
+    handler.getClass();
+
+    /* An application is allowed to control an anonymous logger
+     * without having the permission to control the logging
+     * infrastructure.
+     */
+    if (!anonymous)
+      LogManager.getLogManager().checkAccess();
+
+    if (!handlerList.contains(handler))
+    {
+      handlerList.add(handler);
+      handlers = getHandlers();
+    }
+  }
+
+
+  /**
+   * Removes a handler from the set of handlers that get notified
+   * when a log record is to be published.
+   *
+   * @param handler the handler to be removed.
+   *
+   * @throws SecurityException if this logger is not anonymous, a
+   *     security manager exists, and the caller is not granted the
+   *     permission to control the logging infrastructure by having
+   *     LoggingPermission("control").  Untrusted code can obtain an
+   *     anonymous logger through the static factory method {@link
+   *     #getAnonymousLogger(java.lang.String) getAnonymousLogger}.
+   *
+   * @throws NullPointerException if <code>handler</code>
+   *     is <code>null</code>.
+   */
+  public synchronized void removeHandler(Handler handler)
+    throws SecurityException
+  {
+    /* An application is allowed to control an anonymous logger
+     * without having the permission to control the logging
+     * infrastructure.
+     */
+    if (!anonymous)
+      LogManager.getLogManager().checkAccess();
+
+    /* Throw a new NullPointerException if handler is null. */
+    handler.getClass();
+
+    handlerList.remove(handler);
+    handlers = getHandlers();
+  }
+
+
+  /**
+   * Returns the handlers currently registered for this Logger.
+   * When a log record has been deemed as being loggable,
+   * it will be passed to all registered handlers for
+   * publication.  In addition, if the logger uses parent handlers
+   * (see {@link #getUseParentHandlers() getUseParentHandlers}
+   * and {@link #setUseParentHandlers(boolean) setUseParentHandlers},
+   * the log record will be passed to the parent's handlers.
+   */
+  public synchronized Handler[] getHandlers()
+  {
+    /* We cannot return our internal handlers array
+     * because we do not have any guarantee that the
+     * caller would not change the array entries.
+     */
+    return (Handler[]) handlerList.toArray(new Handler[handlerList.size()]);
+  }
+
+
+  /**
+   * Returns whether or not this Logger forwards log records to
+   * handlers registered for its parent loggers.
+   *
+   * @return <code>false</code> if this Logger sends log records
+   *         merely to Handlers registered with itself;
+   *         <code>true</code> if this Logger sends log records
+   *         not only to Handlers registered with itself, but also
+   *         to those Handlers registered with parent loggers.
+   */
+  public synchronized boolean getUseParentHandlers()
+  {
+    return useParentHandlers;
+  }
+
+
+  /**
+   * Sets whether or not this Logger forwards log records to
+   * handlers registered for its parent loggers.
+   *
+   * @param useParentHandlers <code>false</code> to let this
+   *         Logger send log records merely to Handlers registered
+   *         with itself; <code>true</code> to let this Logger
+   *         send log records not only to Handlers registered
+   *         with itself, but also to those Handlers registered with
+   *         parent loggers.
+   *
+   * @throws SecurityException if this logger is not anonymous, a
+   *     security manager exists, and the caller is not granted
+   *     the permission to control the logging infrastructure by
+   *     having LoggingPermission("control").  Untrusted code can
+   *     obtain an anonymous logger through the static factory method
+   *     {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}.
+   *
+   */
+  public synchronized void setUseParentHandlers(boolean useParentHandlers)
+  {
+    /* An application is allowed to control an anonymous logger
+     * without having the permission to control the logging
+     * infrastructure.
+     */
+    if (!anonymous)
+      LogManager.getLogManager().checkAccess();
+
+    this.useParentHandlers = useParentHandlers;
+  }
+
+
+  /**
+   * Returns the parent of this logger.  By default, the parent is
+   * assigned by the LogManager by inspecting the logger's name.
+   *
+   * @return the parent of this logger (as detemined by the LogManager
+   *     by inspecting logger names), the root logger if no other
+   *     logger has a name which is a prefix of this logger's name, or
+   *     <code>null</code> for the root logger.
+   */
+  public synchronized Logger getParent()
+  {
+    return parent;
+  }
+
+
+  /**
+   * Sets the parent of this logger.  Usually, applications do not
+   * call this method directly.  Instead, the LogManager will ensure
+   * that the tree of loggers reflects the hierarchical logger
+   * namespace.  Basically, this method should not be public at all,
+   * but the GNU implementation follows the API specification.
+   *
+   * @throws NullPointerException if <code>parent</code> is
+   *     <code>null</code>.
+   *
+   * @throws SecurityException if this logger is not anonymous, a
+   *     security manager exists, and the caller is not granted
+   *     the permission to control the logging infrastructure by
+   *     having LoggingPermission("control").  Untrusted code can
+   *     obtain an anonymous logger through the static factory method
+   *     {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}.
+   */
+  public synchronized void setParent(Logger parent)
+  {
+    LogManager lm;
+
+    /* Throw a new NullPointerException if parent is null. */
+    parent.getClass();
+
+    lm = LogManager.getLogManager();
+
+    if (this == lm.rootLogger)
+    {
+      if (parent != null)
+        throw new IllegalArgumentException(
+          "only the root logger can have a null parent");
+      this.parent = null;
+      return;
+    }
+
+    /* An application is allowed to control an anonymous logger
+     * without having the permission to control the logging
+     * infrastructure.
+     */
+    if (!anonymous)
+      LogManager.getLogManager().checkAccess();
+
+    this.parent = parent;
+  }
+}
diff --git a/libjava/java/util/logging/LoggingPermission.java b/libjava/java/util/logging/LoggingPermission.java
new file mode 100644 (file)
index 0000000..e9b5c4a
--- /dev/null
@@ -0,0 +1,75 @@
+/* LoggingPermission.java -- a class for logging permissions.
+   Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+public final class LoggingPermission
+  extends java.security.BasicPermission
+{
+  /**
+   * Creates a new LoggingPermission.
+   *
+   * @param name the name of the permission, which must be "control".
+   *
+   * @param actions the list of actions for the permission, which
+   *                must be either <code>null</code> or an empty
+   *                string.
+   *
+   * @exception IllegalArgumentException if <code>name</code>
+   *            is not "control", or <code>actions</code> is
+   *            neither <code>null</code> nor empty.
+   */
+  public LoggingPermission(String name, String actions)
+  {
+    super("control", "");
+    
+    if (!"control".equals(name))
+    {
+      throw new IllegalArgumentException(
+        "name of LoggingPermission must be \"control\"");
+    }
+    
+    if ((actions != null) && (actions.length() != 0))
+    {
+      throw new IllegalArgumentException(
+       "actions of LoggingPermissions must be null or empty");
+    }    
+  }
+}
diff --git a/libjava/java/util/logging/MemoryHandler.java b/libjava/java/util/logging/MemoryHandler.java
new file mode 100644 (file)
index 0000000..825a6fa
--- /dev/null
@@ -0,0 +1,356 @@
+/* MemoryHandler.java
+   -- a class for buffering log messages in a memory buffer
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+/**
+ * A <code>MemoryHandler</code> maintains a circular buffer of
+ * log records.
+ *
+ * <p><strong>Configuration:</strong> Values of the subsequent
+ * <code>LogManager</code> properties are taken into consideration
+ * when a <code>MemoryHandler</code> is initialized.
+ * If a property is not defined, or if it has an invalid
+ * value, a default is taken without an exception being thrown.
+ *
+ * <ul>
+ *
+ * <li><code>java.util.MemoryHandler.level</code> - specifies
+ *     the initial severity level threshold. Default value:
+ *     <code>Level.ALL</code>.</li>
+ *
+ * <li><code>java.util.MemoryHandler.filter</code> - specifies
+ *     the name of a Filter class. Default value: No Filter.</li>
+ *
+ * <li><code>java.util.MemoryHandler.size</code> - specifies the
+ *     maximum number of log records that are kept in the circular
+ *     buffer.  Default value: 1000.</li>
+ *
+ * <li><code>java.util.MemoryHandler.push</code> - specifies the
+ *     <code>pushLevel</code>. Default value:
+ *     <code>Level.SEVERE</code>.</li>
+ *
+ * <li><code>java.util.MemoryHandler.target</code> - specifies the
+ *     name of a subclass of {@link Handler} that will be used as the
+ *     target handler.  There is no default value for this property;
+ *     if it is not set, the no-argument MemoryHandler constructor
+ *     will throw an exception.</li>
+ *
+ * </ul>
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class MemoryHandler
+  extends Handler
+{
+  /**
+   * The storage area used for buffering the unpushed log records in
+   * memory.
+   */
+  private final LogRecord[] buffer;
+
+
+  /**
+   * The current position in the circular buffer. For a new
+   * MemoryHandler, or immediately after {@link #push()} was called,
+   * the value of this variable is zero.  Each call to {@link
+   * #publish(LogRecord)} will store the published LogRecord into
+   * <code>buffer[position]</code> before position is incremented by
+   * one.  If position becomes greater than the size of the buffer, it
+   * is reset to zero.
+   */
+  private int position;
+
+
+  /**
+   * The number of log records which have been published, but not
+   * pushed yet to the target handler.
+   */
+  private int numPublished;
+
+
+  /**
+   * The push level threshold for this <code>Handler</code>.  When a
+   * record is published whose severity level is greater than or equal
+   * to the <code>pushLevel</code> of this <code>MemoryHandler</code>,
+   * the {@link #push()} method will be invoked for pushing the buffer
+   * contents to the target <code>Handler</code>.
+   */
+  private Level pushLevel;
+
+
+  /**
+   * The Handler to which log records are forwarded for actual
+   * publication.
+   */
+  private final Handler target;
+
+
+  /**
+   * Constructs a <code>MemoryHandler</code> for keeping a circular
+   * buffer of LogRecords; the initial configuration is determined by
+   * the <code>LogManager</code> properties described above.
+   */
+  public MemoryHandler()
+  {
+    this((Handler) LogManager.getInstanceProperty(
+          "java.util.logging.MemoryHandler.target",
+          Handler.class, /* default */ null),
+        LogManager.getIntPropertyClamped(
+          "java.util.logging.MemoryHandler.size",
+          /* default */ 1000,
+          /* minimum value */ 1,
+          /* maximum value */ Integer.MAX_VALUE),
+        LogManager.getLevelProperty(
+          "java.util.logging.MemoryHandler.push",
+          /* default push level */ Level.SEVERE));
+  }
+
+  
+  /**
+   * Constructs a <code>MemoryHandler</code> for keeping a circular
+   * buffer of LogRecords, given some parameters. The values of the
+   * other parameters are taken from LogManager properties, as
+   * described above.
+   *
+   * @param target the target handler that will receive those
+   *               log records that are passed on for publication.
+   *
+   * @param size the number of log records that are kept in the buffer.
+   *             The value must be a at least one.
+   *
+   * @param pushLevel the push level threshold for this
+   *     <code>MemoryHandler</code>.  When a record is published whose
+   *     severity level is greater than or equal to
+   *     <code>pushLevel</code>, the {@link #push()} method will be
+   *     invoked in order to push the bufffer contents to
+   *     <code>target</code>.
+   *
+   * @throws java.lang.IllegalArgumentException if <code>size</code>
+   *         is negative or zero. The GNU implementation also throws
+   *         an IllegalArgumentException if <code>target</code> or
+   *         <code>pushLevel</code> are <code>null</code>, but the
+   *         API specification does not prescribe what should happen
+   *         in those cases.
+   */
+  public MemoryHandler(Handler target, int size, Level pushLevel)
+  { 
+    if ((target == null) || (size <= 0) || (pushLevel == null))
+      throw new IllegalArgumentException();
+
+    buffer = new LogRecord[size];
+    this.pushLevel = pushLevel;
+    this.target = target;
+
+    setLevel(LogManager.getLevelProperty(
+      "java.util.logging.MemoryHandler.level",
+      /* default value */ Level.ALL));
+
+    setFilter((Filter) LogManager.getInstanceProperty(
+      "java.util.logging.MemoryHandler.filter",
+      /* must be instance of */ Filter.class,
+      /* default value */ null));
+  }
+
+
+  /**
+   * Stores a <code>LogRecord</code> in a fixed-size circular buffer,
+   * provided the record passes all tests for being loggable.  If the
+   * buffer is full, the oldest record will be discarded.
+   *
+   * <p>If the record has a severity level which is greater than or
+   * equal to the <code>pushLevel</code> of this
+   * <code>MemoryHandler</code>, the {@link #push()} method will be
+   * invoked for pushing the buffer contents to the target
+   * <code>Handler</code>.
+   *
+   * <p>Most applications do not need to call this method directly.
+   * Instead, they will use use a {@link Logger}, which will create
+   * LogRecords and distribute them to registered handlers.
+   *
+   * @param record the log event to be published.
+   */
+  public void publish(LogRecord record)
+  {
+    if (!isLoggable(record))
+      return;
+
+    buffer[position] = record;
+    position = (position + 1) % buffer.length;
+    numPublished = numPublished + 1;
+
+    if (record.getLevel().intValue() >= pushLevel.intValue())
+      push();
+  }
+
+
+  /**
+   * Pushes the contents of the memory buffer to the target
+   * <code>Handler</code> and clears the buffer. Note that
+   * the target handler will discard those records that do
+   * not satisfy its own severity level threshold, or that are
+   * not considered loggable by an installed {@link Filter}.
+   *
+   * <p>In case of an I/O failure, the {@link ErrorManager} of the
+   * target <code>Handler</code> will be notified, but the caller of
+   * this method will not receive an exception.
+   */
+  public void push()
+  {
+    int i;
+
+    if (numPublished < buffer.length)
+    {
+      for (i = 0; i < position; i++)
+        target.publish(buffer[i]);
+    }
+    else
+    {
+      for (i = position; i < buffer.length; i++)
+       target.publish(buffer[i]);
+      for (i = 0; i < position; i++)
+       target.publish(buffer[i]);
+    }
+
+    numPublished = 0;
+    position = 0;
+  }
+
+
+  /**
+   * Forces any data that may have been buffered by the target
+   * <code>Handler</code> to the underlying output device, but
+   * does <em>not</em> push the contents of the circular memory
+   * buffer to the target handler.
+   *
+   * <p>In case of an I/O failure, the {@link ErrorManager} of the
+   * target <code>Handler</code> will be notified, but the caller of
+   * this method will not receive an exception.
+   *
+   * @see #push()
+   */
+  public void flush()
+  {
+    target.flush();
+  }
+
+
+  /**
+   * Closes this <code>MemoryHandler</code> and its associated target
+   * handler, discarding the contents of the memory buffer.  However,
+   * any data that may have been buffered by the target
+   * <code>Handler</code> is forced to the underlying output device.
+   *
+   * <p>As soon as <code>close</code> has been called,
+   * a <code>Handler</code> should not be used anymore. Attempts
+   * to publish log records, to flush buffers, or to modify the
+   * <code>Handler</code> in any other way may throw runtime
+   * exceptions after calling <code>close</code>.</p>
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code> of
+   * the associated target <code>Handler</code> will be informed, but
+   * the caller of this method will not receive an exception.</p>
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   *
+   * @see #push()
+   */
+  public void close()
+    throws SecurityException
+  {
+    push();
+
+    /* This will check for LoggingPermission("control"). If the
+     * current security context does not grant this permission,
+     * push() has been executed, but this does not impose a
+     * security risk.
+     */
+    target.close();
+  }
+
+    
+
+  /**
+   * Returns the push level threshold for this <code>Handler</code>.
+   * When a record is published whose severity level is greater
+   * than or equal to the <code>pushLevel</code> of this
+   * <code>MemoryHandler</code>, the {@link #push()} method will be
+   * invoked for pushing the buffer contents to the target
+   * <code>Handler</code>.
+   *
+   * @return the push level threshold for automatic pushing.
+   */
+  public Level getPushLevel()
+  {
+    return pushLevel;
+  }
+
+
+  /**
+   * Sets the push level threshold for this <code>Handler</code>.
+   * When a record is published whose severity level is greater
+   * than or equal to the <code>pushLevel</code> of this
+   * <code>MemoryHandler</code>, the {@link #push()} method will be
+   * invoked for pushing the buffer contents to the target
+   * <code>Handler</code>.
+   *
+   * @param pushLevel the push level threshold for automatic pushing.
+   *
+   * @exception SecurityException if a security manager exists and
+   *            the caller is not granted the permission to control
+   *            the logging infrastructure.
+   *
+   * @exception NullPointerException if <code>pushLevel</code> is
+   *            <code>null</code>.
+   */
+  public void setPushLevel(Level pushLevel)
+  {
+    LogManager.getLogManager().checkAccess();
+
+    /* Throws a NullPointerException if pushLevel is null. */
+    pushLevel.getClass();
+
+    this.pushLevel = pushLevel;
+  }
+}
diff --git a/libjava/java/util/logging/SimpleFormatter.java b/libjava/java/util/logging/SimpleFormatter.java
new file mode 100644 (file)
index 0000000..8a95638
--- /dev/null
@@ -0,0 +1,120 @@
+/* SimpleFormatter.java
+   -- a class for formatting log records into short human-readable messages
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.util.Date;
+import java.text.DateFormat;
+
+/**
+ * A <code>SimpleFormatter</code> formats log records into
+ * short human-readable messages, typically one or two lines.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class SimpleFormatter
+  extends Formatter
+{
+  /**
+   * Constructs a SimpleFormatter.
+   */
+  public SimpleFormatter()
+  {
+  }
+
+
+  /**
+   * An instance of a DateFormatter that is used for formatting
+   * the time of a log record into a human-readable string,
+   * according to the rules of the current locale.  The value
+   * is set after the first invocation of format, since it is
+   * common that a JVM will instantiate a SimpleFormatter without
+   * ever using it.
+   */
+  private DateFormat dateFormat;
+
+  /**
+   * The character sequence that is used to separate lines in the
+   * generated stream. Somewhat surprisingly, the Sun J2SE 1.4
+   * reference implementation always uses UNIX line endings, even on
+   * platforms that have different line ending conventions (i.e.,
+   * DOS). The GNU implementation does not replicate this bug.
+   *
+   * @see Sun bug parade, bug #4462871,
+   * "java.util.logging.SimpleFormatter uses hard-coded line separator".
+   */
+  static final String lineSep = System.getProperty("line.separator");
+
+
+  /**
+   * Formats a log record into a String.
+   *
+   * @param the log record to be formatted.
+   *
+   * @return a short human-readable message, typically one or two
+   *   lines.  Lines are separated using the default platform line
+   *   separator.
+   *
+   * @throws NullPointerException if <code>record</code>
+   *         is <code>null</code>.
+   */
+  public String format(LogRecord record)
+  {
+    StringBuffer buf = new StringBuffer(180);
+
+    if (dateFormat == null)
+      dateFormat = DateFormat.getDateTimeInstance();
+
+    buf.append(dateFormat.format(new Date(record.getMillis())));
+    buf.append(' ');
+    buf.append(record.getLoggerName());
+    buf.append(lineSep);
+
+    buf.append(record.getLevel());
+    buf.append(": ");
+    buf.append(formatMessage(record));
+
+    buf.append(lineSep);
+
+    return buf.toString();
+  }
+}
diff --git a/libjava/java/util/logging/SocketHandler.java b/libjava/java/util/logging/SocketHandler.java
new file mode 100644 (file)
index 0000000..d9939a0
--- /dev/null
@@ -0,0 +1,225 @@
+/* SocketHandler.java
+   -- a class for publishing log messages to network sockets
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+
+/**
+ * A <code>SocketHandler</code> publishes log records to
+ * a TCP/IP socket.
+ *
+ * <p><strong>Configuration:</strong> Values of the subsequent
+ * <code>LogManager</code> properties are taken into consideration
+ * when a <code>SocketHandler</code> is initialized.
+ * If a property is not defined, or if it has an invalid
+ * value, a default is taken without an exception being thrown.
+ *
+ * <ul>
+ *
+ * <li><code>java.util.SocketHandler.level</code> - specifies
+ *     the initial severity level threshold. Default value:
+ *     <code>Level.ALL</code>.</li>
+ *
+ * <li><code>java.util.SocketHandler.filter</code> - specifies
+ *     the name of a Filter class. Default value: No Filter.</li>
+ *
+ * <li><code>java.util.SocketHandler.formatter</code> - specifies
+ *     the name of a Formatter class. Default value:
+ *     <code>java.util.logging.XMLFormatter</code>.</li>
+ *
+ * <li><code>java.util.SocketHandler.encoding</code> - specifies
+ *     the name of the character encoding. Default value:
+ *     the default platform encoding.
+ *
+ * <li><code>java.util.SocketHandler.host</code> - specifies
+ *     the name of the host to which records are published.
+ *     There is no default value for this property; if it is
+ *     not set, the SocketHandler constructor will throw
+ *     an exception.</li>
+ *
+ * <li><code>java.util.SocketHandler.port</code> - specifies
+ *     the TCP/IP port to which records are published.
+ *     There is no default value for this property; if it is
+ *     not set, the SocketHandler constructor will throw
+ *     an exception.</li>
+ *
+ * </ul>
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class SocketHandler
+  extends StreamHandler
+{
+  /**
+   * Constructs a <code>SocketHandler</code> that publishes log
+   * records to a TCP/IP socket.  Tthe initial configuration is
+   * determined by the <code>LogManager</code> properties described
+   * above.
+   *
+   * @throws java.io.IOException if the connection to the specified
+   *         network host and port cannot be established.
+   *
+   * @throws java.lang.IllegalArgumentException if either the
+   *         <code>java.util.logging.SocketHandler.host</code>
+   *         or <code>java.util.logging.SocketHandler.port</code>
+   *         LogManager properties is not defined, or specifies
+   *         an invalid value.
+   */
+  public SocketHandler()
+    throws java.io.IOException
+  {
+    this(LogManager.getLogManager().getProperty("java.util.logging.SocketHandler.host"),
+        getPortNumber());
+  }
+
+    
+  /**
+   * Constructs a <code>SocketHandler</code> that publishes log
+   * records to a TCP/IP socket.  With the exception of the internet
+   * host and port, the initial configuration is determined by the
+   * <code>LogManager</code> properties described above.
+   *
+   * @param host the Internet host to which log records will be
+   *        forwarded.
+   *
+   * @param port the port at the host which will accept a request
+   *        for a TCP/IP connection.
+   *
+   * @throws java.io.IOException if the connection to the specified
+   *         network host and port cannot be established.
+   *
+   * @throws java.lang.IllegalArgumentException if either
+   *         <code>host</code> or <code>port</code> specify
+   *         an invalid value.
+   */
+  public SocketHandler(String host, int port)
+    throws java.io.IOException
+  {
+    super(createSocket(host, port),
+         "java.util.logging.SocketHandler",
+         /* default level */ Level.ALL,
+         /* formatter */ null,
+         /* default formatter */ XMLFormatter.class);
+  }
+
+
+  /**
+   * Retrieves the port number from the java.util.logging.SocketHandler.port
+   * LogManager property.
+   *
+   * @throws IllegalArgumentException if the property is not defined or
+   *         does not specify an integer value.
+   */
+  private static int getPortNumber()
+  {
+    try {
+      return Integer.parseInt(LogManager.getLogManager().getProperty("java.util.logging.SocketHandler.port"));
+    } catch (Exception ex) {
+      throw new IllegalArgumentException();
+    }
+  }
+
+
+  /**
+   * Creates an OutputStream for publishing log records to an Internet
+   * host and port.  This private method is a helper for use by the
+   * constructor of SocketHandler.
+   *
+   * @param host the Internet host to which log records will be
+   *        forwarded.
+   *
+   * @param port the port at the host which will accept a request
+   *        for a TCP/IP connection.
+   *
+   * @throws java.io.IOException if the connection to the specified
+   *         network host and port cannot be established.
+   *
+   * @throws java.lang.IllegalArgumentException if either
+   *         <code>host</code> or <code>port</code> specify
+   *         an invalid value.
+   */
+  private static java.io.OutputStream createSocket(String host, int port)
+    throws java.io.IOException, java.lang.IllegalArgumentException
+  {
+    java.net.Socket  socket;
+
+    if ((host == null) || (port < 1))
+      throw new IllegalArgumentException();
+
+    socket = new java.net.Socket(host, port);
+
+    socket.shutdownInput();
+
+    /* The architecture of the logging framework provides replaceable
+     * formatters.  Because these formatters perform their task by
+     * returning one single String for each LogRecord to be formatted,
+     * there is no need to buffer.
+     */
+    socket.setTcpNoDelay(true);
+
+    return socket.getOutputStream();
+  }
+
+
+  /**
+   * Publishes a <code>LogRecord</code> to the network socket,
+   * provided the record passes all tests for being loggable.
+   * In addition, all data that may have been buffered will
+   * be forced to the network stream.
+   *
+   * <p>Most applications do not need to call this method directly.
+   * Instead, they will use a {@link Logger} instance, which will
+   * create LogRecords and distribute them to registered handlers.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>SocketHandler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   *
+   * @param record the log event to be published.
+   */
+  public void publish(LogRecord record)
+  {
+    super.publish(record);
+    flush();
+  }
+}
+    
diff --git a/libjava/java/util/logging/StreamHandler.java b/libjava/java/util/logging/StreamHandler.java
new file mode 100644 (file)
index 0000000..add2d3a
--- /dev/null
@@ -0,0 +1,524 @@
+/* StreamHandler.java
+   -- a class for publishing log messages to instances of java.io.OutputStream
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ * A <code>StreamHandler</code> publishes <code>LogRecords</code> to
+ * a instances of <code>java.io.OutputStream</code>.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class StreamHandler
+  extends Handler
+{
+  private OutputStream  out;
+  private Writer        writer;
+
+
+ /**
+  * Indicates the current state of this StreamHandler.  The value
+  * should be one of STATE_FRESH, STATE_PUBLISHED, or STATE_CLOSED.
+  */
+  private int streamState = STATE_FRESH;
+
+
+  /**
+   * streamState having this value indicates that the StreamHandler
+   * has been created, but the publish(LogRecord) method has not been
+   * called yet.  If the StreamHandler has been constructed without an
+   * OutputStream, writer will be null, otherwise it is set to a
+   * freshly created OutputStreamWriter.
+   */
+  private static final int STATE_FRESH = 0;
+
+
+  /**
+   * streamState having this value indicates that the publish(LocRecord)
+   * method has been called at least once.
+   */
+  private static final int STATE_PUBLISHED = 1;
+
+
+  /**
+   * streamState having this value indicates that the close() method
+   * has been called.
+   */
+  private static final int STATE_CLOSED = 2;
+
+
+  /**
+   * Creates a <code>StreamHandler</code> without an output stream.
+   * Subclasses can later use {@link
+   * #setOutputStream(java.io.OutputStream)} to associate an output
+   * stream with this StreamHandler.
+   */
+  public StreamHandler()
+  {
+    this(null, null);
+  }
+
+
+  /**
+   * Creates a <code>StreamHandler</code> that formats log messages
+   * with the specified Formatter and publishes them to the specified
+   * output stream.
+   *
+   * @param out the output stream to which the formatted log messages
+   *     are published.
+   *
+   * @param formatter the <code>Formatter</code> that will be used
+   *     to format log messages.
+   */
+  public StreamHandler(OutputStream out, Formatter formatter)
+  {
+    this(out, "java.util.logging.StreamHandler", Level.INFO,
+        formatter, SimpleFormatter.class);
+  }
+
+
+  StreamHandler(
+    OutputStream out,
+    String propertyPrefix,
+    Level defaultLevel,
+    Formatter formatter, Class defaultFormatterClass)
+  {
+    this.level = LogManager.getLevelProperty(propertyPrefix + ".level",
+                                            defaultLevel);
+
+    this.filter = (Filter) LogManager.getInstanceProperty(
+      propertyPrefix + ".filter",
+      /* must be instance of */       Filter.class,
+      /* default: new instance of */  null);
+
+    if (formatter != null)
+      this.formatter = formatter;
+    else
+      this.formatter = (Formatter) LogManager.getInstanceProperty(
+       propertyPrefix + ".formatter",
+        /* must be instance of */       Formatter.class,
+        /* default: new instance of */  defaultFormatterClass);
+
+    try
+    {
+      String enc = LogManager.getLogManager().getProperty(propertyPrefix
+                                                         + ".encoding");
+
+      /* make sure enc actually is a valid encoding */
+      if ((enc != null) && (enc.length() > 0))
+        new String(new byte[0], enc);
+
+      this.encoding = enc;
+    }
+    catch (Exception _)
+    {
+    }
+
+    if (out != null)
+    {
+      try
+      {
+        changeWriter(out, getEncoding());
+      }
+      catch (UnsupportedEncodingException uex)
+      {
+       /* This should never happen, since the validity of the encoding
+        * name has been checked above.
+        */
+       throw new RuntimeException(uex.getMessage());
+      }
+    }
+  }
+
+
+  private void checkOpen()
+  {
+    if (streamState == STATE_CLOSED)
+      throw new IllegalStateException(this.toString() + " has been closed");
+  }
+
+  private void checkFresh()
+  {
+    checkOpen();
+    if (streamState != STATE_FRESH)
+      throw new IllegalStateException("some log records have been published to " + this);
+  }
+
+
+  private void changeWriter(OutputStream out, String encoding)
+    throws UnsupportedEncodingException
+  {
+    OutputStreamWriter writer;
+
+    /* The logging API says that a null encoding means the default
+     * platform encoding. However, java.io.OutputStreamWriter needs
+     * another constructor for the default platform encoding,
+     * passing null would throw an exception.
+     */
+    if (encoding == null)
+      writer = new OutputStreamWriter(out);
+    else
+      writer = new OutputStreamWriter(out, encoding);
+
+    /* Closing the stream has side effects -- do this only after
+     * creating a new writer has been successful.
+     */
+    if ((streamState != STATE_FRESH) || (this.writer != null))
+      close();
+
+    this.writer = writer;
+    this.out = out;
+    this.encoding = encoding;
+    streamState = STATE_FRESH;
+  }
+
+
+  /**
+   * Sets the character encoding which this handler uses for publishing
+   * log records.  The encoding of a <code>StreamHandler</code> must be
+   * set before any log records have been published.
+   *
+   * @param encoding the name of a character encoding, or <code>null</code>
+   *            for the default encoding.
+   *
+   * @throws SecurityException if a security manager exists and
+   *     the caller is not granted the permission to control the
+   *     the logging infrastructure.
+   *
+   * @exception IllegalStateException if any log records have been
+   *     published to this <code>StreamHandler</code> before.  Please
+   *     be aware that this is a pecularity of the GNU implementation.
+   *     While the API specification indicates that it is an error
+   *     if the encoding is set after records have been published,
+   *     it does not mandate any specific behavior for that case.
+   */
+  public void setEncoding(String encoding)
+    throws SecurityException, UnsupportedEncodingException
+  {
+    /* The inherited implementation first checks whether the invoking
+     * code indeed has the permission to control the logging infra-
+     * structure, and throws a SecurityException if this was not the
+     * case.
+     *
+     * Next, it verifies that the encoding is supported and throws
+     * an UnsupportedEncodingExcpetion otherwise. Finally, it remembers
+     * the name of the encoding.
+     */
+    super.setEncoding(encoding);
+
+    checkFresh();
+
+    /* If out is null, setEncoding is being called before an output
+     * stream has been set. In that case, we need to check that the
+     * encoding is valid, and remember it if this is the case.  Since
+     * this is exactly what the inherited implementation of
+     * Handler.setEncoding does, we can delegate.
+     */
+    if (out != null)
+    {
+      /* The logging API says that a null encoding means the default
+       * platform encoding. However, java.io.OutputStreamWriter needs
+       * another constructor for the default platform encoding, passing
+       * null would throw an exception.
+       */
+      if (encoding == null)
+       writer = new OutputStreamWriter(out);
+      else
+       writer = new OutputStreamWriter(out, encoding);
+    }
+  }
+
+
+  /**
+   * Changes the output stream to which this handler publishes
+   * logging records.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   *
+   * @throws NullPointerException if <code>out</code>
+   *         is <code>null</code>.
+   */
+  protected void setOutputStream(OutputStream out)
+    throws SecurityException
+  {
+    LogManager.getLogManager().checkAccess();
+
+    /* Throw a NullPointerException if out is null. */
+    out.getClass();
+
+    try
+    {
+      changeWriter(out, getEncoding());
+    }
+    catch (UnsupportedEncodingException ex)
+    {
+      /* This seems quite unlikely to happen, unless the underlying
+       * implementation of java.io.OutputStreamWriter changes its
+       * mind (at runtime) about the set of supported character
+       * encodings.
+       */
+      throw new RuntimeException(ex.getMessage());
+    }
+  }
+
+
+  /**
+   * Publishes a <code>LogRecord</code> to the associated output
+   * stream, provided the record passes all tests for being loggable.
+   * The <code>StreamHandler</code> will localize the message of the
+   * log record and substitute any message parameters.
+   *
+   * <p>Most applications do not need to call this method directly.
+   * Instead, they will use use a {@link Logger}, which will create
+   * LogRecords and distribute them to registered handlers.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>Handler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   *
+   * <p>If a log record is being published to a
+   * <code>StreamHandler</code> that has been closed earlier, the Sun
+   * J2SE 1.4 reference can be observed to silently ignore the
+   * call. The GNU implementation, however, intentionally behaves
+   * differently by informing the <code>ErrorManager</code> associated
+   * with this <code>StreamHandler</code>.  Since the condition
+   * indicates a programming error, the programmer should be
+   * informed. It also seems extremely unlikely that any application
+   * would depend on the exact behavior in this rather obscure,
+   * erroneous case -- especially since the API specification does not
+   * prescribe what is supposed to happen.
+   * 
+   * @param record the log event to be published.
+   */
+  public void publish(LogRecord record)
+  {
+    String formattedMessage;
+
+    if (!isLoggable(record))
+      return;
+
+    if (streamState == STATE_FRESH)
+    {
+      try
+      {
+        writer.write(formatter.getHead(this));
+      }
+      catch (java.io.IOException ex)
+      {
+       reportError(null, ex, ErrorManager.WRITE_FAILURE);
+       return;
+      }
+      catch (Exception ex)
+      {
+       reportError(null, ex, ErrorManager.GENERIC_FAILURE);
+       return;
+      }
+
+      streamState = STATE_PUBLISHED;
+    }
+
+    try
+    {
+      formattedMessage = formatter.format(record);
+    }
+    catch (Exception ex)
+    {
+      reportError(null, ex, ErrorManager.FORMAT_FAILURE);
+      return;
+    }
+
+    try
+    {
+      writer.write(formattedMessage);
+    }
+    catch (Exception ex)
+    {
+      reportError(null, ex, ErrorManager.WRITE_FAILURE);
+    }
+  }
+
+
+  /**
+   * Checks whether or not a <code>LogRecord</code> would be logged
+   * if it was passed to this <code>StreamHandler</code> for publication.
+   *
+   * <p>The <code>StreamHandler</code> implementation first checks
+   * whether a writer is present and the handler's level is greater
+   * than or equal to the severity level threshold.  In a second step,
+   * if a {@link Filter} has been installed, its {@link
+   * Filter#isLoggable(LogRecord) isLoggable} method is
+   * invoked. Subclasses of <code>StreamHandler</code> can override
+   * this method to impose their own constraints.
+   *
+   * @param record the <code>LogRecord</code> to be checked.
+   *
+   * @return <code>true</code> if <code>record</code> would
+   *         be published by {@link #publish(LogRecord) publish},
+   *         <code>false</code> if it would be discarded.
+   *
+   * @see #setLevel(Level)
+   * @see #setFilter(Filter)
+   * @see Filter#isLoggable(LogRecord)
+   *
+   * @throws NullPointerException if <code>record</code> is
+   *         <code>null</code>.  */
+  public boolean isLoggable(LogRecord record)
+  {
+    return (writer != null) && super.isLoggable(record);
+  }
+
+
+  /**
+   * Forces any data that may have been buffered to the underlying
+   * output device.
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>Handler</code> will be informed, but the caller
+   * of this method will not receive an exception.
+   *
+   * <p>If a <code>StreamHandler</code> that has been closed earlier
+   * is closed a second time, the Sun J2SE 1.4 reference can be
+   * observed to silently ignore the call. The GNU implementation,
+   * however, intentionally behaves differently by informing the
+   * <code>ErrorManager</code> associated with this
+   * <code>StreamHandler</code>.  Since the condition indicates a
+   * programming error, the programmer should be informed. It also
+   * seems extremely unlikely that any application would depend on the
+   * exact behavior in this rather obscure, erroneous case --
+   * especially since the API specification does not prescribe what is
+   * supposed to happen.
+   */
+  public void flush()
+  {
+    try
+    {
+      checkOpen();
+      if (writer != null)
+        writer.flush();
+    }
+    catch (Exception ex)
+    {
+      reportError(null, ex, ErrorManager.FLUSH_FAILURE);
+    }
+  }
+
+
+  /**
+   * Closes this <code>StreamHandler</code> after having forced any
+   * data that may have been buffered to the underlying output
+   * device. 
+   *
+   * <p>As soon as <code>close</code> has been called,
+   * a <code>Handler</code> should not be used anymore. Attempts
+   * to publish log records, to flush buffers, or to modify the
+   * <code>Handler</code> in any other way may throw runtime
+   * exceptions after calling <code>close</code>.</p>
+   *
+   * <p>In case of an I/O failure, the <code>ErrorManager</code>
+   * of this <code>Handler</code> will be informed, but the caller
+   * of this method will not receive an exception.</p>
+   *
+   * <p>If a <code>StreamHandler</code> that has been closed earlier
+   * is closed a second time, the Sun J2SE 1.4 reference can be
+   * observed to silently ignore the call. The GNU implementation,
+   * however, intentionally behaves differently by informing the
+   * <code>ErrorManager</code> associated with this
+   * <code>StreamHandler</code>.  Since the condition indicates a
+   * programming error, the programmer should be informed. It also
+   * seems extremely unlikely that any application would depend on the
+   * exact behavior in this rather obscure, erroneous case --
+   * especially since the API specification does not prescribe what is
+   * supposed to happen.
+   *
+   * @throws SecurityException if a security manager exists and
+   *         the caller is not granted the permission to control
+   *         the logging infrastructure.
+   */
+  public void close()
+    throws SecurityException
+  {
+    LogManager.getLogManager().checkAccess();
+
+    try
+    {
+      /* Although  flush also calls checkOpen, it catches
+       * any exceptions and reports them to the ErrorManager
+       * as flush failures.  However, we want to report
+       * a closed stream as a close failure, not as a
+       * flush failure here.  Therefore, we call checkOpen()
+       * before flush().
+       */
+      checkOpen();
+      flush();
+
+      if (writer != null)
+      {
+       if (formatter != null)
+       {
+         /* Even if the StreamHandler has never published a record,
+          * it emits head and tail upon closing. An earlier version
+          * of the GNU Classpath implementation did not emitted
+          * anything. However, this had caused XML log files to be
+          * entirely empty instead of containing no log records.
+          */
+         if (streamState == STATE_FRESH)
+            writer.write(formatter.getHead(this));
+         if (streamState != STATE_CLOSED)
+           writer.write(formatter.getTail(this));
+       }
+       streamState = STATE_CLOSED;
+        writer.close();
+      }
+    }
+    catch (Exception ex)
+    {
+      reportError(null, ex, ErrorManager.CLOSE_FAILURE);
+    }
+  }
+}
diff --git a/libjava/java/util/logging/XMLFormatter.java b/libjava/java/util/logging/XMLFormatter.java
new file mode 100644 (file)
index 0000000..fbaab1c
--- /dev/null
@@ -0,0 +1,395 @@
+/* XMLFormatter.java
+   -- a class for formatting log messages into a standard XML format
+
+Copyright (C) 2002 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+
+*/
+
+
+package java.util.logging;
+
+import java.util.Date;
+import java.util.ResourceBundle;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+
+/**
+ * An <code>XMLFormatter</code> formats LogRecords into
+ * a standard XML format.
+ *
+ * @author Sascha Brawer (brawer@acm.org)
+ */
+public class XMLFormatter
+  extends Formatter
+{
+  /**
+   * Constructs a new XMLFormatter.
+   */
+  public XMLFormatter()
+  {
+  }
+
+
+  /**
+   * The character sequence that is used to separate lines in the
+   * generated XML stream. Somewhat surprisingly, the Sun J2SE 1.4
+   * reference implementation always uses UNIX line endings, even on
+   * platforms that have different line ending conventions (i.e.,
+   * DOS). The GNU Classpath implementation does not replicates this
+   * bug.
+   *
+   * See also the Sun bug parade, bug #4462871,
+   * "java.util.logging.SimpleFormatter uses hard-coded line separator".
+   */
+  private static final String lineSep = SimpleFormatter.lineSep;
+
+    
+  /**
+   * A DateFormat for emitting time in the ISO 8601 format.
+   * Since the API specification of SimpleDateFormat does not talk
+   * about its thread-safety, we cannot share a singleton instance.
+   */
+  private final SimpleDateFormat iso8601
+    = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+
+  /**
+   * Appends a line consisting of indentation, opening element tag,
+   * element content, closing element tag and line separator to
+   * a StringBuffer, provided that the element content is
+   * actually existing.
+   *
+   * @param buf the StringBuffer to which the line will be appended.
+   *
+   * @param indent the indentation level.
+   *
+   * @param tag the element tag name, for instance <code>method</code>.
+   *
+   * @param content the element content, or <code>null</code> to
+   *        have no output whatsoever appended to <code>buf</code>.
+   */
+  private static final void appendTag(StringBuffer buf,
+                                     int indent,
+                                     String tag,
+                                     String content)
+  {
+    int i;
+
+    if (content == null)
+      return;
+
+    for (i = 0; i < indent * 2; i++)
+      buf.append(' ');
+
+    buf.append("<");
+    buf.append(tag);
+    buf.append('>');
+
+    /* Append the content, but escape for XML by replacing
+     * '&', '<', '>' and all non-ASCII characters with
+     * appropriate escape sequences.
+     * The Sun J2SE 1.4 reference implementation does not
+     * escape non-ASCII characters. This is a bug in their
+     * implementation which has been reported in the Java
+     * bug parade as bug number (FIXME: Insert number here).
+     */
+    for (i = 0; i < content.length(); i++)
+    {
+      char c = content.charAt(i);
+      switch (c)
+      {
+      case '&':
+       buf.append("&amp;");
+       break;
+
+      case '<':
+       buf.append("&lt;");
+       break;
+
+      case '>':
+       buf.append("&gt;");
+       break;
+
+      default:
+       if (((c >= 0x20) && (c <= 0x7e))
+           || (c == /* line feed */ 10)
+           || (c == /* carriage return */ 13))
+         buf.append(c);
+       else
+       {
+         buf.append("&#");
+         buf.append((int) c);
+         buf.append(';');
+       }
+       break;
+      } /* switch (c) */
+    } /* for i */
+
+    buf.append("</");
+    buf.append(tag);
+    buf.append(">");
+    buf.append(lineSep);
+  }
+
+
+  /**
+   * Appends a line consisting of indentation, opening element tag,
+   * numeric element content, closing element tag and line separator
+   * to a StringBuffer.
+   *
+   * @param buf the StringBuffer to which the line will be appended.
+   *
+   * @param indent the indentation level.
+   *
+   * @param tag the element tag name, for instance <code>method</code>.
+   *
+   * @param content the element content.
+   */
+  private static final void appendTag(StringBuffer buf,
+                                     int indent,
+                                     String tag,
+                                     long content)
+  {
+    appendTag(buf, indent, tag, Long.toString(content));
+  }
+
+
+  public String format(LogRecord record)
+  {
+    StringBuffer    buf = new StringBuffer(400);
+    Level           level = record.getLevel();
+    long            millis = record.getMillis();
+    Object[]        params = record.getParameters();
+    ResourceBundle  bundle = record.getResourceBundle();
+    String          key, message;
+    
+    buf.append("<record>");
+    buf.append(lineSep);
+    
+    
+    appendTag(buf, 1, "date", iso8601.format(new Date(millis)));
+    appendTag(buf, 1, "millis", record.getMillis());
+    appendTag(buf, 1, "sequence", record.getSequenceNumber());
+    appendTag(buf, 1, "logger", record.getLoggerName());
+
+    if (level.isStandardLevel())
+      appendTag(buf, 1, "level", level.toString());
+    else
+      appendTag(buf, 1, "level", level.intValue());
+
+    appendTag(buf, 1, "class", record.getSourceClassName());
+    appendTag(buf, 1, "method", record.getSourceMethodName());
+    appendTag(buf, 1, "thread", record.getThreadID());
+
+    /* The Sun J2SE 1.4 reference implementation does not emit the
+     * message in localized form. This is in violation of the API
+     * specification. The GNU Classpath implementation intentionally
+     * replicates the buggy behavior of the Sun implementation, as
+     * different log files might be a big nuisance to users.
+     */
+    try
+    {
+      record.setResourceBundle(null);
+      message = formatMessage(record);
+    }
+    finally
+    {
+      record.setResourceBundle(bundle);
+    }
+    appendTag(buf, 1, "message", message);
+
+    /* The Sun J2SE 1.4 reference implementation does not
+     * emit key, catalog and param tags. This is in violation
+     * of the API specification.  The Classpath implementation
+     * intentionally replicates the buggy behavior of the
+     * Sun implementation, as different log files might be
+     * a big nuisance to users.
+     *
+     * FIXME: File a bug report with Sun. Insert bug number here.
+     *
+     *
+     * key = record.getMessage();
+     * if (key == null)
+     *   key = "";
+     *
+     * if ((bundle != null) && !key.equals(message))
+     * {
+     *   appendTag(buf, 1, "key", key);
+     *   appendTag(buf, 1, "catalog", record.getResourceBundleName());
+     * }
+     *
+     * if (params != null)
+     * {
+     *   for (int i = 0; i < params.length; i++)
+     *     appendTag(buf, 1, "param", params[i].toString());
+     * }
+     */
+
+    /* FIXME: We have no way to obtain the stacktrace before free JVMs
+     * support the corresponding method in java.lang.Throwable.  Well,
+     * it would be possible to parse the output of printStackTrace,
+     * but this would be pretty kludgy. Instead, we postpose the
+     * implementation until Throwable has made progress.
+     */
+    Throwable thrown = record.getThrown();
+    if (thrown != null)
+    {
+      buf.append("  <exception>");
+      buf.append(lineSep);
+
+      /* The API specification is not clear about what exactly
+       * goes into the XML record for a thrown exception: It
+       * could be the result of getMessage(), getLocalizedMessage(),
+       * or toString(). Therefore, it was necessary to write a
+       * Mauve testlet and run it with the Sun J2SE 1.4 reference
+       * implementation. It turned out that the we need to call
+       * toString().
+       *
+       * FIXME: File a bug report with Sun, asking for clearer
+       * specs.
+       */
+      appendTag(buf, 2, "message", thrown.toString());
+
+      /* FIXME: The Logging DTD specifies:
+       *
+       * <!ELEMENT exception (message?, frame+)>
+       *
+       * However, java.lang.Throwable.getStackTrace() is
+       * allowed to return an empty array. So, what frame should
+       * be emitted for an empty stack trace? We probably
+       * should file a bug report with Sun, asking for the DTD
+       * to be changed.
+       */
+
+      buf.append("  </exception>");
+      buf.append(lineSep);
+    }
+
+
+    buf.append("</record>");
+    buf.append(lineSep);
+
+    return buf.toString();
+  }
+
+
+  /**
+   * Returns a string that handlers are supposed to emit before
+   * the first log record.  The base implementation returns an
+   * empty string, but subclasses such as {@link XMLFormatter}
+   * override this method in order to provide a suitable header.
+   *
+   * @return a string for the header.
+   *
+   * @param handler the handler which will prepend the returned
+   *     string in front of the first log record.  This method
+   *     will inspect certain properties of the handler, for
+   *     example its encoding, in order to construct the header.
+   */
+  public String getHead(Handler h)
+  {
+    StringBuffer  buf;
+    String        encoding;
+
+    buf = new StringBuffer(80);
+    buf.append("<?xml version=\"1.0\" encoding=\"");
+
+    encoding = h.getEncoding();
+
+    /* file.encoding is a system property with the Sun JVM, indicating
+     * the platform-default file encoding. Unfortunately, the API
+     * specification for java.lang.System.getProperties() does not
+     * list this property.
+     */
+    if (encoding == null)
+      encoding = System.getProperty("file.encoding");
+
+    /* Since file.encoding is not listed with the API specification of
+     * java.lang.System.getProperties(), there might be some VMs that
+     * do not define this system property.  Therefore, we use UTF-8 as
+     * a reasonable default. Please note that if the platform encoding
+     * uses the same codepoints as US-ASCII for the US-ASCII character
+     * set (e.g, 65 for A), it does not matter whether we emit the
+     * wrong encoding into the XML header -- the GNU Classpath will
+     * emit XML escape sequences like &#1234; for any non-ASCII
+     * character.  Virtually all character encodings use the same code
+     * points as US-ASCII for ASCII characters.  Probably, EBCDIC is
+     * the only exception.
+     */
+    if (encoding == null)
+      encoding = "UTF-8";
+    
+    /* On Windows XP localized for Swiss German (this is one of
+     * my [Sascha Brawer's] test machines), the default encoding
+     * has the canonical name "windows-1252". The "historical" name
+     * of this encoding is "Cp1252" (see the Javadoc for the class
+     * java.nio.charset.Charset for the distinction). Now, that class
+     * does have a method for mapping historical to canonical encoding
+     * names. However, if we used it here, we would be come dependent
+     * on java.nio.*, which was only introduced with J2SE 1.4.
+     * Thus, we do this little hack here. As soon as Classpath supports
+     * java.nio.charset.CharSet, this hack should be replaced by
+     * code that correctly canonicalizes the encoding name.
+     */
+    if ((encoding.length() > 2) && encoding.startsWith("Cp"))
+      encoding = "windows-" + encoding.substring(2);
+
+    buf.append(encoding);
+
+    buf.append("\" standalone=\"no\"?>");
+    buf.append(lineSep);
+
+    /* SYSTEM is not a fully qualified URL so that validating
+     * XML parsers do not need to connect to the Internet in
+     * order to read in a log file.  See also the Sun Bug Parade,
+     * bug #4372790, "Logging APIs: need to use relative URL for XML
+     * doctype".
+     */
+    buf.append("<!DOCTYPE log SYSTEM \"logger.dtd\">");
+    buf.append(lineSep);
+    buf.append("<log>");
+    buf.append(lineSep);
+
+    return buf.toString();
+  }
+
+
+  public String getTail(Handler h)
+  {
+    return "</log>" + lineSep;
+  }
+}