diff options
Diffstat (limited to 'libjava/classpath/java/util/logging')
17 files changed, 6412 insertions, 0 deletions
diff --git a/libjava/classpath/java/util/logging/ConsoleHandler.java b/libjava/classpath/java/util/logging/ConsoleHandler.java new file mode 100644 index 00000000000..3cf4bca3354 --- /dev/null +++ b/libjava/classpath/java/util/logging/ConsoleHandler.java @@ -0,0 +1,125 @@ +/* ConsoleHandler.java -- a class for publishing log messages to System.err + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.</li> + * + * </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/classpath/java/util/logging/ErrorManager.java b/libjava/classpath/java/util/logging/ErrorManager.java new file mode 100644 index 00000000000..57c079fe25b --- /dev/null +++ b/libjava/classpath/java/util/logging/ErrorManager.java @@ -0,0 +1,194 @@ +/* ErrorManager.java -- + A class for dealing with errors that a Handler encounters + during logging + 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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; + + + /** + * Indicates whether the {@link #error} method of this ErrorManager + * has ever been used. + * + * Declared volatile in order to correctly support the + * double-checked locking idiom (once the revised Java Memory Model + * gets adopted); see Classpath bug #2944. + */ + private volatile 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 (this) + { + /* 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. + * + * This is the 'double-checked locking' idiom, which is broken + * with the current version of the Java memory model. However, + * we assume that JVMs will have adopted a revised version of + * the Java Memory Model by the time GNU Classpath gains + * widespread acceptance. See Classpath bug #2944. + */ + 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/classpath/java/util/logging/FileHandler.java b/libjava/classpath/java/util/logging/FileHandler.java new file mode 100644 index 00000000000..3d958b7d760 --- /dev/null +++ b/libjava/classpath/java/util/logging/FileHandler.java @@ -0,0 +1,646 @@ +/* FileHandler.java -- a class for publishing log messages to log files + Copyright (C) 2002, 2003, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; + +import java.util.LinkedList; +import java.util.ListIterator; + +/** + * 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; + + + /** + * The number of bytes that have currently been written to the stream. + * Package private for use in inner classes. + */ + long written; + + + /** + * A linked list of files we are, or have written to. The entries + * are file path strings, kept in the order + */ + private LinkedList logFiles; + + + /** + * 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(/* output stream, created below */ null, + "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; + this.written = 0; + this.logFiles = new LinkedList (); + + setOutputStream (createFileStream (pattern, limit, count, append, + /* generation */ 0)); + } + + + /* FIXME: Javadoc missing. */ + private 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"; + + if (count > 1 && !has (pattern, 'g')) + pattern = pattern + ".%g"; + + do + { + path = replaceFileNameEscapes(pattern, generation, unique, count); + + try + { + File file = new File(path); + if (!file.exists () || append) + { + FileOutputStream fout = new FileOutputStream (file, append); + // FIXME we need file locks for this to work properly, but they + // are not implemented yet in Classpath! Madness! +// FileChannel channel = fout.getChannel (); +// FileLock lock = channel.tryLock (); +// if (lock != null) // We've locked the file. +// { + if (logFiles.isEmpty ()) + logFiles.addFirst (path); + return new ostr (fout); +// } + } + } + catch (Exception ex) + { + reportError (null, ex, ErrorManager.OPEN_FAILURE); + } + + unique = unique + 1; + if (!has (pattern, 'u')) + 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. */ + public void publish(LogRecord record) + { + if (limit > 0 && written >= limit) + rotate (); + super.publish(record); + flush (); + } + + /** + * Rotates the current log files, possibly removing one if we + * exceed the file count. + */ + private synchronized void rotate () + { + if (logFiles.size () > 0) + { + File f1 = null; + ListIterator lit = null; + + // If we reach the file count, ditch the oldest file. + if (logFiles.size () == count) + { + f1 = new File ((String) logFiles.getLast ()); + f1.delete (); + lit = logFiles.listIterator (logFiles.size () - 1); + } + // Otherwise, move the oldest to a new location. + else + { + String path = replaceFileNameEscapes (pattern, logFiles.size (), + /* unique */ 0, count); + f1 = new File (path); + logFiles.addLast (path); + lit = logFiles.listIterator (logFiles.size () - 1); + } + + // Now rotate the files. + while (lit.hasPrevious ()) + { + String s = (String) lit.previous (); + File f2 = new File (s); + f2.renameTo (f1); + f1 = f2; + } + } + + setOutputStream (createFileStream (pattern, limit, count, append, + /* generation */ 0)); + + // Reset written count. + written = 0; + } + + /** + * Tell if <code>pattern</code> contains the pattern sequence + * with character <code>escape</code>. That is, if <code>escape</code> + * is 'g', this method returns true if the given pattern contains + * "%g", and not just the substring "%g" (for example, in the case of + * "%%g"). + * + * @param pattern The pattern to test. + * @param escape The escape character to search for. + * @return True iff the pattern contains the escape sequence with the + * given character. + */ + private static boolean has (final String pattern, final char escape) + { + final int len = pattern.length (); + boolean sawPercent = false; + for (int i = 0; i < len; i++) + { + char c = pattern.charAt (i); + if (sawPercent) + { + if (c == escape) + return true; + if (c == '%') // Double percent + { + sawPercent = false; + continue; + } + } + sawPercent = (c == '%'); + } + return false; + } + + /** + * An output stream that tracks the number of bytes written to it. + */ + private final class ostr extends FilterOutputStream + { + private ostr (OutputStream out) + { + super (out); + } + + public void write (final int b) throws IOException + { + out.write (b); + FileHandler.this.written++; // FIXME: synchronize? + } + + public void write (final byte[] b) throws IOException + { + write (b, 0, b.length); + } + + public void write (final byte[] b, final int offset, final int length) + throws IOException + { + out.write (b, offset, length); + FileHandler.this.written += length; // FIXME: synchronize? + } + } +} diff --git a/libjava/classpath/java/util/logging/Filter.java b/libjava/classpath/java/util/logging/Filter.java new file mode 100644 index 00000000000..ec4597670d5 --- /dev/null +++ b/libjava/classpath/java/util/logging/Filter.java @@ -0,0 +1,64 @@ +/* Filter.java -- an interface for filters that decide whether a + LogRecord should be published or discarded + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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. + */ + boolean isLoggable(LogRecord record); +} diff --git a/libjava/classpath/java/util/logging/Formatter.java b/libjava/classpath/java/util/logging/Formatter.java new file mode 100644 index 00000000000..ee747b0b978 --- /dev/null +++ b/libjava/classpath/java/util/logging/Formatter.java @@ -0,0 +1,171 @@ +/* Formatter.java -- + A class for formatting log messages by localizing message texts + and performing substitution of parameters + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.text.MessageFormat; +import java.util.ResourceBundle; + +/** + * 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/classpath/java/util/logging/Handler.java b/libjava/classpath/java/util/logging/Handler.java new file mode 100644 index 00000000000..c3227d6f531 --- /dev/null +++ b/libjava/classpath/java/util/logging/Handler.java @@ -0,0 +1,386 @@ +/* Handler.java -- a class for publishing log messages + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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; + +/** + * 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. + */ + protected Handler() + { + 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/classpath/java/util/logging/Level.java b/libjava/classpath/java/util/logging/Level.java new file mode 100644 index 00000000000..2c400dc3a90 --- /dev/null +++ b/libjava/classpath/java/util/logging/Level.java @@ -0,0 +1,414 @@ +/* Level.java -- a class for indicating logging levels + Copyright (C) 2002, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.Serializable; +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 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/classpath/java/util/logging/LogManager.java b/libjava/classpath/java/util/logging/LogManager.java new file mode 100644 index 00000000000..7e3fd97d01f --- /dev/null +++ b/libjava/classpath/java/util/logging/LogManager.java @@ -0,0 +1,829 @@ +/* LogManager.java -- a class for maintaining Loggers and managing + configuration properties + Copyright (C) 2002,2005 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.lang.ref.WeakReference; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * 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: + * + * <ul> + * <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> + * </ul> + * + * <p>The <code>LogManager</code> has a level of <code>INFO</code> by + * default, and this will be inherited by <code>Logger</code>s unless they + * override it either by properties or programmatically. + * + * @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); + rootLogger.setLevel(Level.INFO); + 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); + Logger.getLogger("global").setUseParentHandlers(true); + } + + /** + * 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(); + int childNameLength = childName.length(); + 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 + && childNameLength > candNameLength + && 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.resetLogger(); + logger.setLevel(null); + } + } + } + + rootLogger.setLevel(Level.INFO); + rootLogger.resetLogger(); + } + + /** + * 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); + reset(); + this.properties = newProperties; + keys = newProperties.propertyNames(); + + while (keys.hasMoreElements()) + { + String key = ((String) keys.nextElement()).trim(); + String value = newProperties.getProperty(key); + + if (value == null) + continue; + + value = value.trim(); + + if ("handlers".equals(key)) + { + StringTokenizer tokenizer = new StringTokenizer(value); + while (tokenizer.hasMoreTokens()) + { + String handlerName = tokenizer.nextToken(); + try + { + Class handlerClass = ClassLoader.getSystemClassLoader().loadClass(handlerName); + getLogger("").addHandler((Handler) handlerClass + .newInstance()); + } + catch (ClassCastException ex) + { + System.err.println("[LogManager] class " + handlerName + + " is not subclass of java.util.logging.Handler"); + } + catch (Exception ex) + { + //System.out.println("[LogManager.readConfiguration]"+ex); + } + } + } + + if (key.endsWith(".level")) + { + String loggerName = key.substring(0, key.length() - 6); + Logger logger = getLogger(loggerName); + + if (logger == null) + { + logger = Logger.getLogger(loggerName); + addLogger(logger); + } + try + { + logger.setLevel(Level.parse(value)); + } + catch (Exception _) + { + //System.out.println("[LogManager.readConfiguration] "+_); + } + 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/classpath/java/util/logging/LogRecord.java b/libjava/classpath/java/util/logging/LogRecord.java new file mode 100644 index 00000000000..af7f2058dbe --- /dev/null +++ b/libjava/classpath/java/util/logging/LogRecord.java @@ -0,0 +1,672 @@ +/* LogRecord.java -- + A class for the state associated with individual logging events + Copyright (C) 2002, 2003, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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; + + + /** + * Allocates a sequence number for a new LogRecord. This class + * method is only called by the LogRecord constructor. + */ + private static synchronized 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/classpath/java/util/logging/Logger.java b/libjava/classpath/java/util/logging/Logger.java new file mode 100644 index 00000000000..ae985a93cbc --- /dev/null +++ b/libjava/classpath/java/util/logging/Logger.java @@ -0,0 +1,1199 @@ +/* Logger.java -- a class for logging messages + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.List; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * 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) + { + StackTraceElement caller = getCallerStackFrame(); + logp(level, + caller != null ? caller.getClassName() : "<unknown>", + caller != null ? caller.getMethodName() : "<unknown>", + message, + param); + } + + + public synchronized void log(Level level, + String message, + Object[] params) + { + StackTraceElement caller = getCallerStackFrame(); + logp(level, + caller != null ? caller.getClassName() : "<unknown>", + caller != null ? caller.getMethodName() : "<unknown>", + message, + params); + } + + + public synchronized void log(Level level, + String message, + Throwable thrown) + { + StackTraceElement caller = getCallerStackFrame(); + logp(level, + caller != null ? caller.getClassName() : "<unknown>", + caller != null ? caller.getMethodName() : "<unknown>", + 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) + throw new IllegalArgumentException( + "only the root logger can have a null parent"); + + /* 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; + } + + /** + * Gets the StackTraceElement of the first class that is not this class. + * That should be the initial caller of a logging method. + * @return caller of the initial logging method or null if unknown. + */ + private StackTraceElement getCallerStackFrame() + { + Throwable t = new Throwable(); + StackTraceElement[] stackTrace = t.getStackTrace(); + int index = 0; + + // skip to stackentries until this class + while(index < stackTrace.length + && !stackTrace[index].getClassName().equals(getClass().getName())) + index++; + + // skip the stackentries of this class + while(index < stackTrace.length + && stackTrace[index].getClassName().equals(getClass().getName())) + index++; + + return index < stackTrace.length ? stackTrace[index] : null; + } + + /** + * Reset and close handlers attached to this logger. This function is package + * private because it must only be avaiable to the LogManager. + */ + void resetLogger() + { + for (int i = 0; i < handlers.length; i++) + { + handlers[i].close(); + handlerList.remove(handlers[i]); + } + handlers = getHandlers(); + } +} diff --git a/libjava/classpath/java/util/logging/LoggingPermission.java b/libjava/classpath/java/util/logging/LoggingPermission.java new file mode 100644 index 00000000000..c7a2255ecea --- /dev/null +++ b/libjava/classpath/java/util/logging/LoggingPermission.java @@ -0,0 +1,73 @@ +/* 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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/classpath/java/util/logging/MemoryHandler.java b/libjava/classpath/java/util/logging/MemoryHandler.java new file mode 100644 index 00000000000..ffa589f1668 --- /dev/null +++ b/libjava/classpath/java/util/logging/MemoryHandler.java @@ -0,0 +1,345 @@ +/* MemoryHandler.java -- a class for buffering log messages in a memory buffer + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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/classpath/java/util/logging/SimpleFormatter.java b/libjava/classpath/java/util/logging/SimpleFormatter.java new file mode 100644 index 00000000000..f7a442792f9 --- /dev/null +++ b/libjava/classpath/java/util/logging/SimpleFormatter.java @@ -0,0 +1,119 @@ +/* SimpleFormatter.java -- + A class for formatting log records into short human-readable messages + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.text.DateFormat; +import java.util.Date; + +/** + * 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.getSourceClassName()); + buf.append(' '); + buf.append(record.getSourceMethodName()); + buf.append(lineSep); + + buf.append(record.getLevel()); + buf.append(": "); + buf.append(formatMessage(record)); + + buf.append(lineSep); + + return buf.toString(); + } +} diff --git a/libjava/classpath/java/util/logging/SocketHandler.java b/libjava/classpath/java/util/logging/SocketHandler.java new file mode 100644 index 00000000000..002dfdbbe5e --- /dev/null +++ b/libjava/classpath/java/util/logging/SocketHandler.java @@ -0,0 +1,221 @@ +/* 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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> + * + * <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/classpath/java/util/logging/StreamHandler.java b/libjava/classpath/java/util/logging/StreamHandler.java new file mode 100644 index 00000000000..5c35c1e481d --- /dev/null +++ b/libjava/classpath/java/util/logging/StreamHandler.java @@ -0,0 +1,521 @@ +/* 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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/classpath/java/util/logging/XMLFormatter.java b/libjava/classpath/java/util/logging/XMLFormatter.java new file mode 100644 index 00000000000..4dd63281727 --- /dev/null +++ b/libjava/classpath/java/util/logging/XMLFormatter.java @@ -0,0 +1,387 @@ +/* XMLFormatter.java -- + A class for formatting log messages into a standard XML format + Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.text.SimpleDateFormat; +import java.util.Date; +import java.util.ResourceBundle; + +/** + * 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 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("&"); + break; + + case '<': + buf.append("<"); + break; + + case '>': + buf.append(">"); + 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 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 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 Ӓ 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; + } +} diff --git a/libjava/classpath/java/util/logging/package.html b/libjava/classpath/java/util/logging/package.html new file mode 100644 index 00000000000..31f0494fcd7 --- /dev/null +++ b/libjava/classpath/java/util/logging/package.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in java.util.logging package. + 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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. --> + +<html> +<head><title>GNU Classpath - java.util.logging</title></head> + +<body> +<p>Utility classes for logging events.</p> + +</body> +</html> |