/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.util.logging;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;
/**
* An adapter that redirect all Java logging events to an other logging framework. This
* class redefines the {@link #severe(String) severe}, {@link #warning(String) warning},
* {@link #info(String) info}, {@link #config(String) config}, {@link #fine(String) fine},
* {@link #finer(String) finer} and {@link #finest(String) finest} methods as <em>abstract</em>
* ones. Subclasses should implement those methods in order to map Java logging levels to
* the backend logging framework.
* <p>
* All {@link #log(Level,String) log} methods are overriden in order to redirect to one of the
* above-cited methods. Note that this is the opposite approach than the Java logging framework
* one, which implemented everything on top of {@link Logger#log(LogRecord)}. This adapter is
* defined in terms of {@link #severe(String) severe} … {@link #finest(String) finest}
* methods instead because external frameworks like
* <a href="http://commons.apache.org/logging/">Commons-logging</a>
* don't work with {@link LogRecord}, and sometime provides nothing else than convenience methods
* equivalent to {@link #severe(String) severe} … {@link #finest(String) finest}.
* <p>
* <b>Restrictions</b><br>
* Because the configuration is expected to be fully controled by the external logging
* framework, every configuration methods inherited from {@link Logger} are disabled:
* <p>
* <ul>
* <li>{@link #addHandler}
* since the handling is performed by the external framework.</li>
*
* <li>{@link #setUseParentHandlers}
* since this adapter never delegates to the parent handlers. This is consistent with the
* previous item and avoid mixing loggings from the external framework with Java loggings.</li>
*
* <li>{@link #setParent}
* since this adapter should not inherits any configuration from a parent logger using the
* Java logging framework.</li>
*
* <li>{@link #setFilter}
* for keeping this {@code LoggerAdapter} simple.</li>
* </ul>
* <p>
* Since {@code LoggerAdapter}s do not hold any configuration by themself, it is not strictly
* necessary to {@linkplain java.util.logging.LogManager#addLogger add them to the log manager}.
* The adapters can be created, garbage-collected and recreated again while preserving their
* behavior since their configuration is entirely contained in the external logging framework.
* <p>
* <b>Localization</b><br>
* This logger is always created without resource bundles. Localizations must be performed through
* explicit calls to {@code logrb} or {@link #log(LogRecord)} methods. This is suffisient for
* GeoTools needs, which performs all localizations through the later. Note that those methods
* will be slower in this {@code LoggerAdapter} than the default {@link Logger} because this
* adapter localizes and formats records immediately instead of letting the {@linkplain Handler}
* performs this work only if needed.
* <p>
* <b>Logging levels</b><br>
* If a log record {@linkplain Level level} is not one of the predefined ones, then this class
* maps to the first level below the specified one. For example if a log record has some level
* between {@link Level#FINE FINE} and {@link Level#FINER FINER}, then the {@link #finer finer}
* method will be invoked. See {@link #isLoggable} for implementation tips taking advantage of
* this rule.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
*
* @see Logging
*/
public abstract class LoggerAdapter extends Logger {
/**
* The pattern to use for detecting {@link MessageFormat}.
*/
private static final Pattern MESSAGE_FORMAT = Pattern.compile("\\{\\d+\\}");
/**
* Creates a new logger.
*
* @param name The logger name.
*/
protected LoggerAdapter(final String name) {
super(name, null);
/*
* Must invokes the super-class method, because LoggerAdapter overrides it as a no-op.
*/
super.setUseParentHandlers(false);
/*
* Sets the level to ALL as a matter of principle, but we will never check the level
* anyway (we will let the external logging framework do its own check). Note that we
* must invoke the method in the super-class because we want to set the java logging
* level, not the external framework level.
*/
super.setLevel(Level.ALL);
}
/**
* Sets the level for this logger. Subclasses must redirect the call to the external
* logging framework, or do nothing if the level can not be changed programmatically.
*/
@Override
public abstract void setLevel(Level level);
/**
* Returns the level for this logger. Subclasses shall get this level from the
* external logging framework.
*/
@Override
public abstract Level getLevel();
/**
* Returns the level for {@link #entering}, {@link #exiting} and {@link #throwing} methods.
* The default implementation returns {@link Level#FINER}, which is consistent with the
* value used in the Java logging framework. Subclasses should override this method if
* a different debug level is wanted.
*/
protected Level getDebugLevel() {
return Level.FINER;
}
/**
* Returns {@code true} if the specified level is loggable.
* <p>
* <b>Implementation tip</b><br>
* Given that {@link Level#intValue} for all predefined levels are documented in the {@link Level}
* specification and are multiple of 100, given that integer divisions are rounded toward zero and
* given rule documented in this class javadoc, then logging levels can be efficiently mapped to
* predefined levels using {@code switch} statements as below. This statement has good chances to
* be compiled to the {@code tableswitch} bytecode rather than {@code lookupswitch} (see
* <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Compiling.doc.html#14942">Compiling
* Switches</a> in <cite>The Java Virtual Machine Specification</cite>).
*
* <blockquote><pre>
* @SuppressWarnings("fallthrough")
* public boolean isLoggable(Level level) {
* final int n = level.intValue();
* switch (n / 100) {
* default: {
* // MAX_VALUE is a special value for Level.OFF. Otherwise and
* // if positive, fallthrough since we are greater than SEVERE.
* switch (n) {
* case Integer.MIN_VALUE: return true; // Level.ALL
* case Integer.MAX_VALUE: return false; // Level.OFF
* default: if (n < 0) return false;
* }
* }
* case 10: return isSevereEnabled();
* case 9: return isWarningEnabled();
* case 8: return isInfoEnabled();
* case 7: return isConfigEnabled();
* case 6: // fallthrough
* case 5: return isFineEnabled();
* case 4: return isFinerEnabled();
* case 3: return isFinestEnabled();
* case 2: // fallthrough
* case 1: // fallthrough
* case 0: return false;
* }
* }
* </pre></blockquote>
*/
@Override
public abstract boolean isLoggable(Level level);
/**
* Logs a {@link Level#SEVERE SEVERE} message.
*/
@Override
public abstract void severe(String message);
/**
* Logs a {@link Level#WARNING WARNING} message.
*/
@Override
public abstract void warning(String message);
/**
* Logs an {@link Level#INFO INFO} message.
*/
@Override
public abstract void info(String message);
/**
* Logs an {@link Level#CONFIG CONFIG} message.
*/
@Override
public abstract void config(String message);
/**
* Logs a {@link Level#FINE FINE} message.
*/
@Override
public abstract void fine(String message);
/**
* Logs a {@link Level#FINER FINER} message.
*/
@Override
public abstract void finer(String message);
/**
* Logs a {@link Level#FINEST FINEST} message.
*/
@Override
public abstract void finest(String message);
/**
* Logs a method entry to the {@linkplain #getDebugLevel debug level}. Compared to the
* default {@link Logger}, this implementation bypass the level check in order to let
* the backing logging framework do its own check.
*/
@Override
public void entering(final String sourceClass, final String sourceMethod) {
logp(getDebugLevel(), sourceClass, sourceMethod, "ENTRY");
}
/**
* Logs a method entry to the {@linkplain #getDebugLevel debug level} with one parameter.
* Compared to the default {@link Logger}, this implementation bypass the level check in
* order to let the backing logging framework do its own check.
*/
@Override
public void entering(String sourceClass, String sourceMethod, Object param) {
logp(getDebugLevel(), sourceClass, sourceMethod, "ENTRY {0}", param);
}
/**
* Logs a method entry to the {@linkplain #getDebugLevel debug level} with many parameters.
* Compared to the default {@link Logger}, this implementation bypass the level check in
* order to let the backing logging framework do its own check.
*/
@Override
public void entering(final String sourceClass, final String sourceMethod, final Object[] params) {
final String message;
if (params == null) {
message = "ENTRY";
} else switch (params.length) {
case 0: message = "ENTRY"; break;
case 1: message = "ENTRY {0}"; break;
case 2: message = "ENTRY {0} {1}"; break;
default: {
final StringBuilder builder = new StringBuilder("ENTRY");
for (int i=0; i<params.length; i++) {
builder.append(" {").append(i).append('}');
}
message = builder.toString();
break;
}
}
logp(getDebugLevel(), sourceClass, sourceMethod, message, params);
}
/**
* Logs a method return to the {@linkplain #getDebugLevel debug level}. Compared to the
* default {@link Logger}, this implementation bypass the level check in order to let
* the backing logging framework do its own check.
*/
@Override
public void exiting(final String sourceClass, final String sourceMethod) {
logp(getDebugLevel(), sourceClass, sourceMethod, "RETURN");
}
/**
* Logs a method return to the {@linkplain #getDebugLevel debug level}. Compared to the
* default {@link Logger}, this implementation bypass the level check in order to let
* the backing logging framework do its own check.
*/
@Override
public void exiting(String sourceClass, String sourceMethod, Object result) {
logp(getDebugLevel(), sourceClass, sourceMethod, "RETURN {0}", result);
}
/**
* Logs a method failure to the {@linkplain #getDebugLevel debug level}. Compared to the
* default {@link Logger}, this implementation bypass the level check in order to let
* the backing logging framework do its own check.
*/
@Override
public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
logp(getDebugLevel(), sourceClass, sourceMethod, "THROW", thrown);
}
/**
* Logs a record. The default implementation delegates to
* {@link #logrb(Level,String,String,String,String,Object[]) logrb}.
*/
@Override
public void log(final LogRecord record) {
/*
* The filter should always be null since we overrode the 'setFilter' method as a no-op.
* But we check it anyway as matter of principle just in case some subclass overrides the
* 'getFilter()' method. This is the only method where we can do this check cheaply. Note
* that this is NOT the check for logging level; Filters are for user-specified criterions.
*/
final Filter filter = getFilter();
if (filter != null && !filter.isLoggable(record)) {
return;
}
Level level = record.getLevel();
String sourceClass = record.getSourceClassName();
String sourceMethod = record.getSourceMethodName();
String bundleName = record.getResourceBundleName();
String message = record.getMessage();
Object[] params = record.getParameters();
Throwable thrown = record.getThrown();
ResourceBundle bundle = record.getResourceBundle();
boolean localized = false;
if (bundle != null) try {
message = bundle.getString(message);
localized = true; // Sets only if the above succeed.
} catch (MissingResourceException e) {
// The default Formatter.messageFormat implementation ignores this exception
// and uses the bundle key as the message, so we mimic its behavior here.
}
final boolean useThrown = (thrown != null) && (params == null || params.length == 0);
if (localized) {
// The message is already localized.
if (useThrown) {
logp(level, sourceClass, sourceMethod, message, thrown);
} else {
logp(level, sourceClass, sourceMethod, message, params);
}
} else {
// The message needs to be localized. The bundle was null but maybe bundleName is not.
// Futhermore subclass may have overriden the 'logrb' methods.
if (useThrown) {
logrb(level, sourceClass, sourceMethod, bundleName, message, thrown);
} else {
logrb(level, sourceClass, sourceMethod, bundleName, message, params);
}
}
}
/**
* Logs a record at the specified level. The default implementation delegates to one of the
* {@link #severe(String) severe}, {@link #warning(String) warning}, {@link #info(String) info},
* {@link #config(String) config}, {@link #fine(String) fine}, {@link #finer(String) finer} or
* {@link #finest(String) finest} methods according the supplied level.
*/
@Override
@SuppressWarnings("fallthrough")
public void log(final Level level, final String message) {
final int n = level.intValue();
switch (n / 100) {
default: {
if (n < 0 || n == Integer.MAX_VALUE) break;
// MAX_VALUE is a special value for Level.OFF. Otherwise and
// if positive, fallthrough since we are greater than SEVERE.
}
case 10: severe (message); break;
case 9: warning(message); break;
case 8: info (message); break;
case 7: config (message); break;
case 6:
case 5: fine (message); break;
case 4: finer (message); break;
case 3: finest (message); break;
case 2: /* Logging OFF */
case 1: /* Logging OFF */
case 0: /* Logging OFF */ break;
}
}
/**
* Logs a record at the specified level. The default implementation discards the exception
* and delegates to <code>{@linkplain #log(Level,String) log}(level, message)</code>.
*/
@Override
public void log(final Level level, final String message, final Throwable thrown) {
log(level, message);
}
/**
* Logs a record at the specified level. The defaut implementation delegates to
* <code>{@linkplain #log(Level,String,Object[]) log}(level, message, params)</code>
* where the {@code params} array is built from the {@code param} object.
*/
@Override
public void log(final Level level, final String message, final Object param) {
if (isLoggable(level)) {
log(level, message, asArray(param));
}
}
/**
* Logs a record at the specified level.
* The defaut implementation formats the message immediately, then delegates to
* <code>{@linkplain #log(Level,String) log}(level, message)</code>.
*/
@Override
public void log(final Level level, final String message, final Object[] params) {
if (isLoggable(level)) {
log(level, format(message, params));
}
}
/**
* Logs a record at the specified level. The defaut implementation discards
* the source class and source method, then delegates to
* <code>{@linkplain #log(Level,String) log}(level, message)</code>.
*/
@Override
public void logp(final Level level, final String sourceClass, final String sourceMethod,
final String message)
{
log(level, message);
}
/**
* Logs a record at the specified level. The defaut implementation discards
* the source class and source method, then delegates to
* <code>{@linkplain #log(Level,String,Throwable) log}(level, message, thrown)</code>.
*/
@Override
public void logp(final Level level, final String sourceClass, final String sourceMethod,
final String message, final Throwable thrown)
{
log(level, message, thrown);
}
/**
* Logs a record at the specified level. The defaut implementation delegates to
* <code>{@linkplain #logp(Level,String,String,String,Object[]) logp}(level, sourceClass,
* sourceMethod, message, params)</code> where the {@code params} array is built from the
* {@code param} object.
* <p>
* Note that {@code sourceClass} and {@code sourceMethod} will be discarted unless the
* target {@link #logp(Level,String,String,String) logp} method has been overriden.
*/
@Override
public void logp(final Level level, final String sourceClass, final String sourceMethod,
final String message, final Object param)
{
if (isLoggable(level)) {
logp(level, sourceClass, sourceMethod, message, asArray(param));
}
}
/**
* Logs a record at the specified level. The defaut implementation formats the message
* immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String)
* logp}(level, sourceClass, sourceMethod, message)</code>.
* <p>
* Note that {@code sourceClass} and {@code sourceMethod} will be discarted unless the
* target {@link #logp(Level,String,String,String) logp} method has been overriden.
*/
@Override
public void logp(final Level level, final String sourceClass, final String sourceMethod,
final String message, final Object[] params)
{
if (isLoggable(level)) {
logp(level, sourceClass, sourceMethod, format(message, params));
}
}
/**
* Logs a localizable record at the specified level. The defaut implementation localizes the
* message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String)
* logp}(level, sourceClass, sourceMethod, message)</code>.
*/
@Override
public void logrb(final Level level, final String sourceClass, final String sourceMethod,
final String bundleName, final String message)
{
if (isLoggable(level)) {
logp(level, sourceClass, sourceMethod, localize(bundleName, message));
}
}
/**
* Logs a localizable record at the specified level. The defaut implementation localizes the
* message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String,
* Throwable) logp}(level, sourceClass, sourceMethod, message, thrown)</code>.
*/
@Override
public void logrb(final Level level, final String sourceClass, final String sourceMethod,
final String bundleName, final String message, final Throwable thrown)
{
if (isLoggable(level)) {
logp(level, sourceClass, sourceMethod, localize(bundleName, message), thrown);
}
}
/**
* Logs a localizable record at the specified level. The defaut implementation localizes the
* message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String,
* Object) logp}(level, sourceClass, sourceMethod, message, param)</code>.
*/
@Override
public void logrb(final Level level, final String sourceClass, final String sourceMethod,
final String bundleName, final String message, final Object param)
{
if (isLoggable(level)) {
logp(level, sourceClass, sourceMethod, localize(bundleName, message), param);
}
}
/**
* Logs a localizable record at the specified level. The defaut implementation localizes the
* message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String,
* Object[]) logp}(level, sourceClass, sourceMethod, message, params)</code>.
*/
@Override
public void logrb(final Level level, final String sourceClass, final String sourceMethod,
final String bundleName, String message, final Object[] params)
{
if (isLoggable(level)) {
logp(level, sourceClass, sourceMethod, localize(bundleName, message), params);
}
}
/**
* Do nothing since this logger adapter does not supports handlers.
* The configuration should be fully controlled by the external logging framework
* (e.g. <a href="http://commons.apache.org/logging/">Commons-logging</a>) instead,
* which is not expected to use {@link Handler} objects.
*/
@Override
public void addHandler(Handler handler) {
}
/**
* Do nothing since this logger adapter does not support handlers.
*/
@Override
public void removeHandler(Handler handler) {
}
/**
* Do nothing since this logger never use parent handlers. This is consistent
* with {@link #addHandler} not allowing to add any handlers, and avoid mixing
* loggings from the external framework with Java loggings.
*/
@Override
public void setUseParentHandlers(boolean useParentHandlers) {
}
/**
* Do nothing since this logger adapter does not support arbitrary parents.
* More specifically, it should not inherits any configuration from a parent
* logger using the Java logging framework.
*/
@Override
public void setParent(Logger parent) {
}
/**
* Do nothing since this logger adapter does not support filters. It is difficult to query
* efficiently the filter in this {@code LoggerAdapter} architecture (e.g. we would need to
* make sure that {@link Filter#isLoggable} is invoked only once even if a {@code log} call
* is cascaded into many other {@code log} calls, and this test must works in multi-threads
* environment).
*/
@Override
public void setFilter(Filter filter) {
}
/**
* Wraps the specified object in an array. This is a helper method for
* {@code log(..., Object)} methods that delegate their work to {@code log(..., Object[])}
*/
private static Object[] asArray(final Object param) {
return (param != null) ? new Object[] {param} : null;
}
/**
* Formats the specified message. This is a helper method for
* {@code log(..., Object[])} methods that delegate their work to {@code log(...)}
*/
private static String format(String message, final Object[] params) {
if (params != null && params.length != 0) {
if (MESSAGE_FORMAT.matcher(message).find()) try {
message = MessageFormat.format(message, params);
} catch (IllegalArgumentException e) {
// The default Formatter.messageFormat implementation ignores this exception
// and uses the pattern as the message, so we mimic its behavior here.
}
}
return message;
}
/**
* Localize the specified message. This is a helper method for
* {@code logrb(...)} methods that delegate their work to {@code logp(...)}
*/
private static String localize(final String bundleName, String message) {
if (bundleName != null) try {
message = ResourceBundle.getBundle(bundleName).getString(message);
} catch (MissingResourceException e) {
// The default Formatter.messageFormat implementation ignores this exception
// and uses the bundle key as the message, so we mimic its behavior here.
}
return message;
}
}