sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1387950 [1/2] - in /incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util: ./ logging/
Date Thu, 20 Sep 2012 10:35:58 GMT
Author: desruisseaux
Date: Thu Sep 20 10:35:57 2012
New Revision: 1387950

URL: http://svn.apache.org/viewvc?rev=1387950&view=rev
Log:
Added org.apache.sis.util.logging package.

Added:
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java   (with props)
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java   (with props)
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java   (with props)
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java   (with props)
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java   (with props)
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java   (with props)
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/package-info.java   (with props)
Modified:
    incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java

Modified: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java?rev=1387950&r1=1387949&r2=1387950&view=diff
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java (original)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Static.java Thu Sep 20 10:35:57 2012
@@ -39,6 +39,9 @@ package org.apache.sis.util;
  *     <td>Perform argument checks and throw {@link IllegalArgumentException} if needed.</td></tr>
  * <tr><td>{@link org.apache.sis.util.Exceptions}</td>
  *     <td>Format a stack trace summary or change the exception message.</td></tr>
+ * <tr><td>{@link org.apache.sis.util.logging.Logging}</td>
+ *     <td>Get a JDK {@linkplain java.util.logging.Logger logger}, which may be a wrapper around
+ *         the <cite>Apache Commons Logging</cite> or <cite>Log4J</cite> framework.</td></tr>
  * </table>
  *
  * @author Martin Desruisseaux (Geomatys)

Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java?rev=1387950&view=auto
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java (added)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java Thu Sep 20 10:35:57 2012
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.logging;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * Redirects logging to two loggers. This is used only when more than one {@link LoggerFactory}
+ * is found on the classpath. This should never happen, but if it happen anyway we will send the
+ * log records to all registered loggers in order to have a behavior slightly more determinist
+ * than picking an arbitrary logger.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @since   0.3 (derived from geotk-3.20)
+ * @version 0.3
+ * @module
+ */
+final class DualLogger extends LoggerAdapter {
+    /**
+     * The two loggers.
+     */
+    private final Logger first, second;
+
+    /**
+     * Creates a new logger which will redirects the event to the two specified loggers.
+     */
+    DualLogger(final String name, final Logger first, final Logger second) {
+        super(name);
+        this.first  = first;
+        this.second = second;
+    }
+
+    /**
+     * Sets the level for the two loggers.
+     */
+    @Override
+    public void setLevel(final Level level) {
+        first .setLevel(level);
+        second.setLevel(level);
+    }
+
+    /**
+     * Returns the finest level from the two loggers.
+     */
+    @Override
+    public Level getLevel() {
+        final Level v1 = first .getLevel();
+        final Level v2 = second.getLevel();
+        return (v1.intValue() < v2.intValue()) ? v1 : v2;
+    }
+
+    /**
+     * Returns {@code true} if the specified level is loggable by at least one logger.
+     */
+    @Override
+    public boolean isLoggable(final Level level) {
+        return first.isLoggable(level) || second.isLoggable(level);
+    }
+
+    /**
+     * Logs a record at the specified level.
+     */
+    @Override
+    public void log(final Level level, final String message) {
+        first .log(level, message);
+        second.log(level, message);
+    }
+
+    /**
+     * Logs a record at the specified level.
+     */
+    @Override
+    public void log(final Level level, final String message, final Throwable thrown) {
+        first .log(level, message, thrown);
+        second.log(level, message, thrown);
+    }
+
+    @Override public void severe (String message) {first.severe (message); second.severe (message);}
+    @Override public void warning(String message) {first.warning(message); second.warning(message);}
+    @Override public void info   (String message) {first.info   (message); second.info   (message);}
+    @Override public void config (String message) {first.config (message); second.config (message);}
+    @Override public void fine   (String message) {first.fine   (message); second.fine   (message);}
+    @Override public void finer  (String message) {first.finer  (message); second.finer  (message);}
+    @Override public void finest (String message) {first.finest (message); second.finest (message);}
+}

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLogger.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java?rev=1387950&view=auto
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java (added)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java Thu Sep 20 10:35:57 2012
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.logging;
+
+import java.util.logging.Logger;
+
+
+/**
+ * Redirects logging to two loggers. This is used only when more than one {@link LoggerFactory}
+ * is found on the classpath. This should never happen, but if it happen anyway we will send the
+ * log records to all registered loggers in order to have a behavior slightly more determinist
+ * than picking an arbitrary logger.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @since   0.3 (derived from geotk-3.20)
+ * @version 0.3
+ * @module
+ */
+final class DualLoggerFactory extends LoggerFactory<DualLogger> {
+    /**
+     * The factories of loggers on which to delegate logging events.
+     */
+    private final LoggerFactory<?> first, second;
+
+    /**
+     * Creates a new factory which will delegate the logging events to the loggers
+     * created by the two given factories.
+     */
+    DualLoggerFactory(final LoggerFactory<?> first, final LoggerFactory<?> second) {
+        super(DualLogger.class);
+        this.first  = first;
+        this.second = second;
+    }
+
+    /**
+     * Returns the implementation to use for the logger of the specified name,
+     * or {@code null} if the logger would delegates to Java logging anyway.
+     */
+    @Override
+    protected DualLogger getImplementation(final String name) {
+        return new DualLogger(name, first.getLogger(name), second.getLogger(name));
+    }
+
+    /**
+     * Wraps the specified {@linkplain #getImplementation implementation} in a Java logger.
+     */
+    @Override
+    protected Logger wrap(final String name, final DualLogger implementation) {
+        return implementation;
+    }
+
+    /**
+     * Returns the {@linkplain #getImplementation implementation} wrapped by the specified logger,
+     * or {@code null} if none.
+     */
+    @Override
+    protected DualLogger unwrap(final Logger logger) {
+        if (logger instanceof DualLogger) {
+            return (DualLogger) logger;
+        }
+        return null;
+    }
+}

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/DualLoggerFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java?rev=1387950&view=auto
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java (added)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java Thu Sep 20 10:35:57 2012
@@ -0,0 +1,726 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.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;
+
+import org.apache.sis.util.Debug;
+
+
+/**
+ * An adapter that redirect all JDK 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 JDK logging levels to
+ * the backend logging framework.
+ * <p>
+ * All {@link #log(Level,String) log} methods are overridden in order to redirect to one of the
+ * above-cited methods. Note that this is the opposite approach than the JDK logging framework
+ * one, which implements 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} &hellip; {@link #finest(String) finest}.
+ *
+ * {@section Restrictions}
+ * Because the configuration is expected to be fully controlled by the external logging
+ * framework, every configuration methods inherited from {@link Logger} are disabled:
+ * <p>
+ * <ul>
+ *   <li>{@link #addHandler(Handler)}
+ *       since the handling is performed by the external framework.</li>
+ *
+ *   <li>{@link #setUseParentHandlers(boolean)}
+ *       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 JDK loggings.</li>
+ *
+ *   <li>{@link #setParent(Logger)}
+ *       since this adapter should not inherits any configuration from a parent logger using the
+ *       JDK logging framework.</li>
+ *
+ *   <li>{@link #setFilter(Filter)}
+ *       for keeping this {@code LoggerAdapter} simple.</li>
+ * </ul>
+ * <p>
+ * Since {@code LoggerAdapter}s do not hold any configuration by themselves, 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.
+ *
+ * {@section Localization}
+ * 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 sufficient for
+ * SIS 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.
+ *
+ * {@section Logging levels}
+ * 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(Level)} for implementation tips taking advantage
+ * of this rule.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @since   0.3 (derived from geotk-2.4)
+ * @version 0.3
+ * @module
+ *
+ * @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.
+     *
+     * @param level The new value for the log level (may be null).
+     */
+    @Override
+    public abstract void setLevel(Level level);
+
+    /**
+     * Returns the level for this logger. Subclasses shall get this level from the
+     * external logging framework.
+     *
+     * @return The logger's level.
+     */
+    @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 JDK logging framework. Subclasses should override this method if
+     * a different debug level is wanted.
+     *
+     * @return The level to use for debugging informations.
+     */
+    @Debug
+    protected Level getDebugLevel() {
+        return Level.FINER;
+    }
+
+    /**
+     * Returns {@code true} if the specified level is loggable.
+     *
+     * {@section Implementation tip}
+     * 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 <a href="#skip-navbar_top">class javadoc</a>, 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>).
+     *
+     * {@preformat java
+     *     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;
+     *         }
+     *     }
+     * }
+     *
+     * @param  level A message logging level.
+     * @return {@code true} if the given message level is currently being logged.
+     */
+    @Override
+    public abstract boolean isLoggable(Level level);
+
+    /**
+     * Logs a {@link Level#SEVERE SEVERE} message.
+     *
+     * @param message The message to log.
+     */
+    @Override
+    public abstract void severe(String message);
+
+    /**
+     * Logs a {@link Level#WARNING WARNING} message.
+     *
+     * @param message The message to log.
+     */
+    @Override
+    public abstract void warning(String message);
+
+    /**
+     * Logs an {@link Level#INFO INFO} message.
+     *
+     * @param message The message to log.
+     */
+    @Override
+    public abstract void info(String message);
+
+    /**
+     * Logs an {@link Level#CONFIG CONFIG} message.
+     *
+     * @param message The message to log.
+     */
+    @Override
+    public abstract void config(String message);
+
+    /**
+     * Logs a {@link Level#FINE FINE} message.
+     *
+     * @param message The message to log.
+     */
+    @Override
+    public abstract void fine(String message);
+
+    /**
+     * Logs a {@link Level#FINER FINER} message.
+     *
+     * @param message The message to log.
+     */
+    @Override
+    public abstract void finer(String message);
+
+    /**
+     * Logs a {@link Level#FINEST FINEST} message.
+     *
+     * @param message The message to log.
+     */
+    @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.
+     *
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of method that is being entered.
+     */
+    @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.
+     *
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of method that is being entered.
+     * @param param        Parameter to the method being entered.
+     */
+    @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.
+     *
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of method that is being entered.
+     * @param params       Array of parameters to the method being entered.
+     */
+    @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.
+     *
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     */
+    @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.
+     *
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param result       Object that is being returned.
+     */
+    @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.
+     *
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param thrown       The Throwable that is being thrown.
+     */
+    @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}.
+     *
+     * @param record The log record to be published.
+     */
+    @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 overridden 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.
+     *
+     * @param level   One of the message level identifiers.
+     * @param message The message to log.
+     */
+    @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>.
+     *
+     * @param level   One of the message level identifiers.
+     * @param message The message to log.
+     * @param thrown  Throwable associated with log message.
+     */
+    @Override
+    public void log(final Level level, final String message, final Throwable thrown) {
+        log(level, message);
+    }
+
+    /**
+     * Logs a record at the specified level. The default 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.
+     *
+     * @param level   One of the message level identifiers.
+     * @param message The message to log.
+     * @param param   Parameter to the method being entered.
+     */
+    @Override
+    public void log(final Level level, final String message, final Object param) {
+        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>.
+     *
+     * @param level   One of the message level identifiers.
+     * @param message The message to log.
+     * @param params  Array of parameters to the method being entered.
+     */
+    @Override
+    public void log(final Level level, final String message, final Object[] params) {
+        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>.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param message      The message to log.
+     */
+    @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>.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param message      The message to log.
+     * @param thrown       Throwable associated with log message.
+     */
+    @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 discarded unless the
+     * target {@link #logp(Level,String,String,String) logp} method has been overridden.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param message      The message to log.
+     * @param param        Parameter to the method being entered.
+     */
+    @Override
+    public void logp(final Level level, final String sourceClass, final String sourceMethod,
+                     final String message, final Object param)
+    {
+        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 discarded unless the
+     * target {@link #logp(Level,String,String,String) logp} method has been overridden.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param message      The message to log.
+     * @param params       Array of parameters to the method being entered.
+     */
+    @Override
+    public void logp(final Level level, final String sourceClass, final String sourceMethod,
+                     final String message, final Object[] params)
+    {
+        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>.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param bundleName   Name of resource bundle to localize message, or {@code null}.
+     * @param message      The message to log.
+     */
+    @Override
+    public void logrb(final Level level, final String sourceClass, final String sourceMethod,
+                      final String bundleName, final String message)
+    {
+        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>.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param bundleName   Name of resource bundle to localize message, or {@code null}.
+     * @param message      The message to log.
+     * @param thrown       Throwable associated with log message.
+     */
+    @Override
+    public void logrb(final Level level, final String sourceClass, final String sourceMethod,
+                      final String bundleName, final String message, final Throwable thrown)
+    {
+        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>.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param bundleName   Name of resource bundle to localize message, or {@code null}.
+     * @param message      The message to log.
+     * @param param        Parameter to the method being entered.
+     */
+    @Override
+    public void logrb(final Level level, final String sourceClass, final String sourceMethod,
+                      final String bundleName, final String message, final Object param)
+    {
+        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>.
+     *
+     * @param level        One of the message level identifiers.
+     * @param sourceClass  Name of class that issued the logging request.
+     * @param sourceMethod Name of the method.
+     * @param bundleName   Name of resource bundle to localize message, or {@code null}.
+     * @param message      The message to log.
+     * @param params       Array of parameters to the method being entered.
+     */
+    @Override
+    public void logrb(final Level level, final String sourceClass, final String sourceMethod,
+                      final String bundleName, String message, final Object[] params)
+    {
+        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.
+     *
+     * @param handler A logging handler, ignored in default implementation.
+     */
+    @Override
+    public void addHandler(Handler handler) {
+    }
+
+    /**
+     * Do nothing since this logger adapter does not support handlers.
+     *
+     * @param handler A logging handler, ignored in default implementation.
+     */
+    @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 JDK loggings.
+     *
+     * @param useParentHandlers Ignored in default implementation.
+     */
+    @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 JDK logging framework.
+     *
+     * @param parent Ignored in default implementation.
+     */
+    @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).
+     *
+     * @param filter Ignored in default implementation.
+     */
+    @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;
+    }
+
+    /**
+     * Localizes 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;
+    }
+}

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerAdapter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java?rev=1387950&view=auto
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java (added)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java Thu Sep 20 10:35:57 2012
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.logging;
+
+import java.util.logging.Logger;
+import net.jcip.annotations.ThreadSafe;
+
+
+/**
+ * A factory for Java {@link Logger} wrapping an other logging framework. This factory is used
+ * only when wanting to log to an other framework than JDK logging. The {@link #getLogger(String)}
+ * method returns some subclass of {@link Logger} (typically {@link LoggerAdapter}) that forward
+ * directly all log methods to an other framework.
+ *
+ * @param <L> The type of loggers used for the implementation backend.
+ *            This is the type used by external frameworks like Log4J.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @since   0.3 (derived from geotk-2.4)
+ * @version 0.3
+ * @module
+ *
+ * @see Logging
+ * @see LoggerAdapter
+ */
+@ThreadSafe
+public abstract class LoggerFactory<L> {
+    /**
+     * The logger class. We ask for this information right at construction time in order to
+     * force a {@link NoClassDefFoundError} early rather than only the first time a message
+     * is logged.
+     */
+    private final Class<L> loggerClass;
+
+    /**
+     * Creates a new factory.
+     *
+     * @param loggerClass The class of the wrapped logger.
+     */
+    protected LoggerFactory(final Class<L> loggerClass) {
+        this.loggerClass = loggerClass;
+    }
+
+    /**
+     * Returns the logger of the specified name, or {@code null} if the JDK logging framework
+     * should be used.
+     *
+     * @param  name The name of the logger.
+     * @return The logger, or {@code null} if the JDK logging framework should be used.
+     */
+    public Logger getLogger(final String name) {
+        final L target = getImplementation(name);
+        if (target == null) {
+            return null;
+        }
+        return wrap(name, target);
+    }
+
+    /**
+     * Returns the base class of objects to be returned by {@link #getImplementation(String)}.
+     * The class depends on the underlying logging framework (Log4J, SLF4J, <i>etc.</i>).
+     *
+     * @return The type of loggers used for the implementation backend.
+     */
+    public Class<L> getImplementationClass() {
+        return loggerClass;
+    }
+
+    /**
+     * Returns the implementation to use for the logger of the specified name. The object to be
+     * returned depends on the logging framework (Log4J, SLF4J, <i>etc.</i>). If the target
+     * framework redirects logging events to JDK logging, then this method shall return
+     * {@code null} since we should not use wrapper at all.
+     *
+     * @param  name The name of the logger.
+     * @return The logger as an object of the target logging framework (Log4J, SLF4J,
+     *         <i>etc.</i>), or {@code null} if the target framework would redirect
+     *         to the JDK logging framework.
+     */
+    protected abstract L getImplementation(String name);
+
+    /**
+     * Wraps the specified {@linkplain #getImplementation(String) implementation} in a JDK logger.
+     *
+     * @param  name The name of the logger.
+     * @param  implementation An implementation returned by {@link #getImplementation(String)}.
+     * @return A new logger wrapping the specified implementation.
+     */
+    protected abstract Logger wrap(String name, L implementation);
+
+    /**
+     * Returns the {@linkplain #getImplementation(String) implementation} wrapped by the specified
+     * logger, or {@code null} if none. If the specified logger is not an instance of the expected
+     * class, then this method should returns {@code null}.
+     *
+     * @param  logger The logger to test.
+     * @return The implementation wrapped by the specified logger, or {@code null} if none.
+     */
+    protected abstract L unwrap(Logger logger);
+}

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/LoggerFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java?rev=1387950&view=auto
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java (added)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java Thu Sep 20 10:35:57 2012
@@ -0,0 +1,637 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.logging;
+
+import java.util.Comparator;
+import java.util.ServiceLoader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+import org.apache.sis.util.Configuration;
+import org.apache.sis.util.Static;
+import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.Classes;
+
+import static java.util.Arrays.binarySearch;
+import static org.apache.sis.util.Arrays.insert;
+
+
+/**
+ * A set of utilities method for configuring loggings in SIS. Library implementors shall fetch
+ * their logger using the {@link #getLogger(Class)} static method provided in this class rather
+ * than {@link Logger#getLogger(String)}, in order to give SIS a chance to redirect the logging
+ * to an other framework like <A HREF="http://commons.apache.org/logging/">Commons-logging</A> or
+ * <A HREF="http://logging.apache.org/log4j">Log4J</A>.
+ * <p>
+ * This method provides also a {@link #log(Class, String, LogRecord)} convenience static method, which
+ * {@linkplain LogRecord#setLoggerName set the logger name} of the given record before to log it.
+ * An other worthy static method is {@link #unexpectedException(Class, String, Throwable)}, for
+ * reporting an anomalous but nevertheless non-fatal exception.
+ *
+ * {@section Configuration}
+ * The log records can be redirected explicitly to an other logging framework using the
+ * following method call (replace {@link LoggerFactory#COMMONS_LOGGING} by
+ * {@link LoggerFactory#LOG4J} or an other framework if desired):
+ *
+ * {@preformat java
+ *     Logging.SIS.setLoggerFactory(LoggerFactory.COMMONS_LOGGING);
+ * }
+ *
+ * Note however that the above method invocation is performed automatically if the
+ * {@code sis-logging-commons.jar} or the {@code sis-logging-log4j.jar} file is
+ * found on the classpath, so it usually doesn't need to be invoked explicitely.
+ * See the {@link #scanLoggerFactory()} method for more details on automatic logger
+ * factory registration.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-2.4)
+ * @version 0.3
+ * @module
+ */
+public final class Logging extends Static {
+    /**
+     * Compares {@link Logging} or {@link String} objects for alphabetical order.
+     */
+    private static final Comparator<Object> COMPARATOR = new Comparator<Object>() {
+        @Override public int compare(final Object o1, final Object o2) {
+            final String n1 = (o1 instanceof Logging) ? ((Logging) o1).name : o1.toString();
+            final String n2 = (o2 instanceof Logging) ? ((Logging) o2).name : o2.toString();
+            return n1.compareTo(n2);
+        }
+    };
+
+    /**
+     * An empty array of loggings. Also used for locks.
+     */
+    private static final Logging[] EMPTY = new Logging[0];
+
+    /**
+     * Logging configuration that apply to all packages.
+     */
+    public static final Logging ALL = new Logging();
+    static { // Must be run after ALL assignation and before SIS (or any other Logging) creation.
+        ALL.scanLoggerFactory();
+    }
+
+    /**
+     * Logging configuration that apply only to "{@code org.apache.sis}" packages.
+     */
+    public static final Logging SIS = getLogging("org.apache.sis");
+
+    /**
+     * The name of the base package.
+     */
+    public final String name;
+
+    /**
+     * The children {@link Logging} objects.
+     * <p>
+     * The plain array used there is not efficient for adding new items (an {@code ArrayList}
+     * would be more efficient), but we assume that very few new items will be added. Furthermore
+     * a plain array is efficient for reading, and the later is way more common than the former.
+     */
+    private Logging[] children = EMPTY;
+
+    /**
+     * The factory for creating loggers, or {@code null} if none. If {@code null}
+     * (the default), then the standard JDK logging framework will be used.
+     *
+     * @see #setLoggerFactory(LoggerFactory)
+     */
+    private LoggerFactory<?> factory;
+
+    /**
+     * {@code true} if every {@link Logging} instances use the same {@link LoggerFactory}.
+     * This is an optimization for a very common case.
+     */
+    private static boolean sameLoggerFactory = true;
+
+    /**
+     * Creates an instance for the root logger. This constructor should not be used for anything
+     * else than {@link #ALL} construction; use the {@link #getLogging(String)} method instead.
+     */
+    private Logging() {
+        name = "";
+    }
+
+    /**
+     * Creates an instance for the specified base logger. This constructor should
+     * not be public; use the {@link #getLogging(String)} method instead.
+     *
+     * @param parent The parent {@code Logging} instance.
+     * @param name   The logger name for the new instance.
+     */
+    private Logging(final Logging parent, final String name) {
+        this.name = name;
+        factory = parent.factory;
+        assert name.startsWith(parent.name) : name;
+    }
+
+    /**
+     * Logs the given record to the logger associated to the given class.
+     * This convenience method performs the following steps:
+     * <p>
+     * <ul>
+     *   <li>Get the logger using {@link #getLogger(Class)};</li>
+     *   <li>{@linkplain LogRecord#setLoggerName(String) Set the logger name} of the given record,
+     *       if not already set;</li>
+     *   <li>Unconditionally {@linkplain LogRecord#setSourceClassName(String) set the source class
+     *       name} to the {@linkplain Class#getCanonicalName() canonical name} of the given class;</li>
+     *   <li>Unconditionally {@linkplain LogRecord#setSourceMethodName(String) set the source method
+     *       name} to the given value;</li>
+     *   <li>{@linkplain Logger#log(LogRecord) Log} the modified record.</li>
+     * </ul>
+     *
+     * @param classe The class for which to obtain a logger.
+     * @param method The name of the method which is logging a record.
+     * @param record The record to log.
+     */
+    public static void log(final Class<?> classe, final String method, final LogRecord record) {
+        record.setSourceClassName(classe.getCanonicalName());
+        record.setSourceMethodName(method);
+        final Logger logger = getLogger(classe);
+        if (record.getLoggerName() == null) {
+            record.setLoggerName(logger.getName());
+        }
+        logger.log(record);
+    }
+
+    /**
+     * Returns a logger for the specified class. This convenience method invokes
+     * {@link #getLogger(String)} with the package name as the logger name.
+     *
+     * @param  classe The class for which to obtain a logger.
+     * @return A logger for the specified class.
+     */
+    public static Logger getLogger(Class<?> classe) {
+        Class<?> outer;
+        while ((outer = classe.getEnclosingClass()) != null) {
+            classe = outer;
+        }
+        String name = classe.getName();
+        final int separator = name.lastIndexOf('.');
+        name = (separator >= 1) ? name.substring(0, separator) : "";
+        return getLogger(name);
+    }
+
+    /**
+     * Returns a logger for the specified name. If a {@linkplain LoggerFactory logger factory} has
+     * been set, then this method first {@linkplain LoggerFactory#getLogger ask to the factory}.
+     * It gives SIS a chance to redirect logging events to
+     * <A HREF="http://commons.apache.org/logging/">commons-logging</A>
+     * or some equivalent framework.
+     * <p>
+     * If no factory was found or if the factory choose to not redirect the loggings, then this
+     * method returns the usual <code>{@linkplain Logger#getLogger Logger.getLogger}(name)</code>.
+     *
+     * @param  name The logger name.
+     * @return A logger for the specified name.
+     */
+    public static Logger getLogger(final String name) {
+        synchronized (EMPTY) {
+            final Logging logging = sameLoggerFactory ? ALL : getLogging(name, false);
+            if (logging != null) { // Paranoiac check ('getLogging' should not returns null).
+                final LoggerFactory<?> factory = logging.factory;
+                assert getLogging(name, false).factory == factory : name;
+                if (factory != null) {
+                    final Logger logger = factory.getLogger(name);
+                    if (logger != null) {
+                        return logger;
+                    }
+                }
+            }
+        }
+        return Logger.getLogger(name);
+    }
+
+    /**
+     * Returns a {@code Logging} instance for the specified base logger. This instance is
+     * used for controlling logging configuration in SIS. For example methods like
+     * {@link #forceMonolineConsoleOutput(Level)} are invoked on a {@code Logging} instance.
+     * <p>
+     * {@code Logging} instances follow the same hierarchy than {@link Logger}, i.e.
+     * {@code "org.apache.sis"} is the parent of {@code "org.apache.sis.referencing"},
+     * {@code "org.apache.sis.metadata"}, <i>etc</i>.
+     *
+     * @param name The base logger name.
+     * @return The logging instance for the given name.
+     */
+    public static Logging getLogging(final String name) {
+        synchronized (EMPTY) {
+            return getLogging(name, true);
+        }
+    }
+
+    /**
+     * Returns a logging instance for the specified base logger. If no instance is found for
+     * the specified name and {@code create} is {@code true}, then a new instance will be
+     * created. Otherwise the nearest parent is returned.
+     *
+     * @param base The root logger name.
+     * @param create {@code true} if this method is allowed to create new {@code Logging} instance.
+     * @return The logging instance for the given name.
+     */
+    private static Logging getLogging(final String base, final boolean create) {
+        assert Thread.holdsLock(EMPTY);
+        Logging logging = ALL;
+        if (!base.isEmpty()) {
+            int offset = 0;
+            do {
+                Logging[] children = logging.children;
+                offset = base.indexOf('.', offset);
+                final String name = (offset >= 0) ? base.substring(0, offset) : base;
+                int i = binarySearch(children, name, COMPARATOR);
+                if (i < 0) {
+                    // No exact match found.
+                    if (!create) {
+                        // We are not allowed to create new Logging instance.
+                        // 'logging' is the nearest parent, so stop the loop now.
+                        break;
+                    }
+                    i = ~i;
+                    children = insert(children, i, 1);
+                    children[i] = new Logging(logging, name);
+                    logging.children = children;
+                }
+                logging = children[i];
+            } while (++offset != 0);
+        }
+        return logging;
+    }
+
+    /**
+     * Returns the logger factory, or {@code null} if none. This method returns the logger set
+     * by the last call to {@link #setLoggerFactory(LoggerFactory)} on this {@code Logging}
+     * instance or on one of its parent.
+     *
+     * @return The current logger factory.
+     */
+    public LoggerFactory<?> getLoggerFactory() {
+        synchronized (EMPTY) {
+            return factory;
+        }
+    }
+
+    /**
+     * Sets a new logger factory for this {@code Logging} instance and every children. The
+     * specified factory will be used by <code>{@linkplain #getLogger(String) getLogger}(name)</code>
+     * when {@code name} is this {@code Logging} name or one of its children.
+     * <p>
+     * If the factory is set to {@code null} (the default), then the standard Logging framework
+     * will be used.
+     *
+     * @param factory The new logger factory, or {@code null} if none.
+     */
+    @Configuration
+    public void setLoggerFactory(final LoggerFactory<?> factory) {
+        synchronized (EMPTY) {
+            this.factory = factory;
+            for (int i=0; i<children.length; i++) {
+                children[i].setLoggerFactory(factory);
+            }
+            sameLoggerFactory = sameLoggerFactory(ALL.children, ALL.factory); // NOSONAR: really want static field.
+        }
+    }
+
+    /**
+     * Returns {@code true} if all children use the specified factory.
+     * Used in order to detect a possible optimization for this very common case.
+     */
+    private static boolean sameLoggerFactory(final Logging[] children, final LoggerFactory<?> factory) {
+        assert Thread.holdsLock(EMPTY);
+        for (int i=0; i<children.length; i++) {
+            final Logging logging = children[i];
+            if (logging.factory != factory || !sameLoggerFactory(logging.children, factory)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Scans the classpath for logger factories. The fully qualified factory classname shall be
+     * declared in the following file:
+     *
+     * {@preformat text
+     *     META-INF/services/org.apache.sis.util.logging.LoggerFactory
+     * }
+     *
+     * The first factory found on the classpath is given to {@link #setLoggerFactory(String)}.
+     * If it can't be used (for example because of missing dependency), then the second factory
+     * is tried, <i>etc.</i> until an acceptable factory is found.
+     * <p>
+     * This method usually doesn't need to be invoked explicitly, since it is automatically
+     * invoked on {@code Logging} class initialization. However developers may invoke it if
+     * new {@code LoggerFactory}s are added later on the classpath of a running JVM.
+     */
+    @Configuration
+    public void scanLoggerFactory() {
+        LoggerFactory<?> factory = null;
+        for (final LoggerFactory<?> found : ServiceLoader.load(LoggerFactory.class)) {
+            if (factory == null) {
+                factory = found;
+            } else {
+                factory = new DualLoggerFactory(factory, found);
+            }
+        }
+        setLoggerFactory(factory);
+    }
+
+    /**
+     * Flushes all {@linkplain Handler handlers} used by the logger named {@link #name}.
+     * If that logger {@linkplain Logger#getUseParentHandlers() uses parent handlers},
+     * then those handlers will be flushed as well.
+     * <p>
+     * If the log records seem to be interleaved with the content of {@link System#out}
+     * or {@link System#err}, invoking this method before to write to the standard streams
+     * may help.
+     *
+     * @see Handler#flush()
+     */
+    public void flush() {
+        for (Logger logger=getLogger(name); logger!=null; logger=logger.getParent()) {
+            for (final Handler handler : logger.getHandlers()) {
+                handler.flush();
+            }
+            if (!logger.getUseParentHandlers()) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Invoked when an unexpected error occurs. This method logs a message at the
+     * {@link Level#WARNING WARNING} level to the specified logger. The originating
+     * class name and method name are inferred from the error stack trace, using the
+     * first {@linkplain StackTraceElement stack trace element} for which the class
+     * name is inside a package or sub-package of the logger name. For example if
+     * the logger name is {@code "org.apache.sis.image"}, then this method will uses
+     * the first stack trace element where the fully qualified class name starts with
+     * {@code "org.apache.sis.image"} or {@code "org.apache.sis.image.io"}, but not
+     * {@code "org.apache.sis.imageio"}.
+     *
+     * @param  logger Where to log the error.
+     * @param  error  The error that occurred.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the {@link Level#WARNING WARNING} level.
+     */
+    public static boolean unexpectedException(final Logger logger, final Throwable error) {
+        return unexpectedException(logger, null, null, error, Level.WARNING);
+    }
+
+    /**
+     * Invoked when an unexpected error occurs. This method logs a message at the
+     * {@link Level#WARNING WARNING} level to the specified logger. The originating
+     * class name and method name can optionally be specified. If any of them is
+     * {@code null}, then it will be inferred from the error stack trace as in
+     * {@link #unexpectedException(Logger, Throwable)}.
+     * <p>
+     * Explicit value for class and method names are sometime preferred to automatic
+     * inference for the following reasons:
+     * <p>
+     * <ul>
+     *   <li>Automatic inference is not 100% reliable, since the Java Virtual Machine
+     *       is free to omit stack frame in optimized code.</li>
+     *   <li>When an exception occurred in a private method used internally by a public
+     *       method, we sometime want to log the warning for the public method instead,
+     *       since the user is not expected to know anything about the existence of the
+     *       private method. If a developer really want to know about the private method,
+     *       the stack trace is still available anyway.</li>
+     * </ul>
+     *
+     * @param logger  Where to log the error.
+     * @param classe  The class where the error occurred, or {@code null}.
+     * @param method  The method where the error occurred, or {@code null}.
+     * @param error   The error.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the {@link Level#WARNING WARNING} level.
+     *
+     * @see #recoverableException(Logger, Class, String, Throwable)
+     * @see #severeException(Logger, Class, String, Throwable)
+     */
+    public static boolean unexpectedException(final Logger logger, final Class<?> classe,
+                                              final String method, final Throwable error)
+    {
+        final String classname = (classe != null) ? classe.getName() : null;
+        return unexpectedException(logger, classname, method, error, Level.WARNING);
+    }
+
+    /**
+     * Invoked when an unexpected error occurs. This method logs a message at the
+     * {@link Level#WARNING WARNING} level to a logger inferred from the given class.
+     *
+     * @param classe  The class where the error occurred.
+     * @param method  The method where the error occurred, or {@code null}.
+     * @param error   The error.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the {@link Level#WARNING WARNING} level.
+     *
+     * @see #recoverableException(Class, String, Throwable)
+     */
+    public static boolean unexpectedException(Class<?> classe, String method, Throwable error) {
+        return unexpectedException((Logger) null, classe, method, error);
+    }
+
+    /**
+     * Implementation of {@link #unexpectedException(Logger, Class, String, Throwable)}.
+     *
+     * @param logger  Where to log the error, or {@code null}.
+     * @param classe  The fully qualified class name where the error occurred, or {@code null}.
+     * @param method  The method where the error occurred, or {@code null}.
+     * @param error   The error.
+     * @param level   The logging level.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the specified level.
+     */
+    private static boolean unexpectedException(Logger logger, String classe, String method,
+                                               final Throwable error, final Level level)
+    {
+        /*
+         * Checks if loggable, inferring the logger from the classe name if needed.
+         */
+        if (error == null) {
+            return false;
+        }
+        if (logger == null && classe != null) {
+            final int separator = classe.lastIndexOf('.');
+            final String paquet = (separator >= 1) ? classe.substring(0, separator-1) : "";
+            logger = getLogger(paquet);
+        }
+        if (logger != null && !logger.isLoggable(level)) {
+            return false;
+        }
+        /*
+         * Loggeable, so complete the null argument from the stack trace if we can.
+         */
+        if (logger==null || classe==null || method==null) {
+            String paquet = (logger != null) ? logger.getName() : null;
+            final StackTraceElement[] elements = error.getStackTrace();
+            for (int i=0; i<elements.length; i++) {
+                /*
+                 * Searches for the first stack trace element with a classname matching the
+                 * expected one. We compare preferably against the name of the class given
+                 * in argument, or against the logger name (taken as the package name) otherwise.
+                 */
+                final StackTraceElement element = elements[i];
+                final String classname = element.getClassName();
+                if (classe != null) {
+                    if (!classname.equals(classe)) {
+                        continue;
+                    }
+                } else if (paquet != null) {
+                    if (!classname.startsWith(paquet)) {
+                        continue;
+                    }
+                    final int length = paquet.length();
+                    if (classname.length() > length) {
+                        // We expect '.' but we accept also '$' or end of string.
+                        final char separator = classname.charAt(length);
+                        if (Character.isJavaIdentifierPart(separator)) {
+                            continue;
+                        }
+                    }
+                }
+                /*
+                 * Now that we have a stack trace element from the expected class (or any
+                 * element if we don't know the class), make sure that we have the right method.
+                 */
+                final String methodName = element.getMethodName();
+                if (method != null && !methodName.equals(method)) {
+                    continue;
+                }
+                /*
+                 * Now computes every values that are null, and stop the loop.
+                 */
+                if (paquet == null) {
+                    final int separator = classname.lastIndexOf('.');
+                    paquet = (separator >= 1) ? classname.substring(0, separator-1) : "";
+                    logger = getLogger(paquet);
+                    if (!logger.isLoggable(level)) {
+                        return false;
+                    }
+                }
+                if (classe == null) {
+                    classe = classname;
+                }
+                if (method == null) {
+                    method = methodName;
+                }
+                break;
+            }
+            /*
+             * The logger may stay null if we have been unable to find a suitable
+             * stack trace. Fallback on the global logger.
+             */
+            if (logger == null) {
+                logger = getLogger(Logger.GLOBAL_LOGGER_NAME);
+                if (!logger.isLoggable(level)) {
+                    return false;
+                }
+            }
+        }
+        /*
+         * Now prepare the log message. If we have been unable to figure out a source class and
+         * method name, we will fallback on JDK logging default mechanism, which may returns a
+         * less relevant name than our attempt to use the logger name as the package name.
+         */
+        final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(error));
+        String message = error.getLocalizedMessage();
+        if (message != null) {
+            buffer.append(": ").append(message);
+        }
+        message = buffer.toString();
+        message = Exceptions.formatChainedMessages(message, error);
+        final LogRecord record = new LogRecord(level, message);
+        if (classe != null) {
+            record.setSourceClassName(classe);
+        }
+        if (method != null) {
+            record.setSourceMethodName(method);
+        }
+        if (level.intValue() > 500) {
+            record.setThrown(error);
+        }
+        record.setLoggerName(logger.getName());
+        logger.log(record);
+        return true;
+    }
+
+    /**
+     * Invoked when a recoverable error occurs. This method is similar to
+     * {@link #unexpectedException(Class,String,Throwable) unexpectedException}
+     * except that it doesn't log the stack trace and uses a lower logging level.
+     *
+     * @param classe  The class where the error occurred.
+     * @param method  The method name where the error occurred.
+     * @param error   The error.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the {@link Level#FINE FINE} level.
+     *
+     * @see #unexpectedException(Class, String, Throwable)
+     */
+    public static boolean recoverableException(final Class<?> classe, final String method,
+                                               final Throwable error)
+    {
+        return recoverableException(null, classe, method, error);
+    }
+
+    /**
+     * Invoked when a recoverable error occurs. This method is similar to
+     * {@link #unexpectedException(Logger,Class,String,Throwable) unexpectedException}
+     * except that it doesn't log the stack trace and uses a lower logging level.
+     *
+     * @param logger  Where to log the error.
+     * @param classe  The class where the error occurred.
+     * @param method  The method name where the error occurred.
+     * @param error   The error.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the {@link Level#FINE FINE} level.
+     *
+     * @see #unexpectedException(Logger, Class, String, Throwable)
+     * @see #severeException(Logger, Class, String, Throwable)
+     */
+    public static boolean recoverableException(final Logger logger, final Class<?> classe,
+                                               final String method, final Throwable error)
+    {
+        final String classname = (classe != null) ? classe.getName() : null;
+        return unexpectedException(logger, classname, method, error, Level.FINE);
+    }
+
+    /**
+     * Invoked when a severe error occurs. This method is similar to
+     * {@link #unexpectedException(Logger,Class,String,Throwable) unexpectedException}
+     * except that it logs the message at the {@link Level#SEVERE SEVERE} level.
+     *
+     * @param logger  Where to log the error.
+     * @param classe  The class where the error occurred.
+     * @param method  The method name where the error occurred.
+     * @param error   The error.
+     * @return {@code true} if the error has been logged, or {@code false} if the logger
+     *         doesn't log anything at the {@link Level#SEVERE SEVERE} level.
+     *
+     * @see #unexpectedException(Logger, Class, String, Throwable)
+     * @see #recoverableException(Logger, Class, String, Throwable)
+     */
+    public static boolean severeException(final Logger logger, final Class<?> classe,
+                                          final String method, final Throwable error)
+    {
+        final String classname = (classe != null) ? classe.getName() : null;
+        return unexpectedException(logger, classname, method, error, Level.SEVERE);
+    }
+}

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java
URL: http://svn.apache.org/viewvc/incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java?rev=1387950&view=auto
==============================================================================
--- incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java (added)
+++ incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java Thu Sep 20 10:35:57 2012
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.logging;
+
+import java.util.logging.Level;
+import java.util.concurrent.TimeUnit;
+import org.apache.sis.util.Configuration;
+
+import static org.apache.sis.util.ArgumentChecks.ensurePositive;
+
+
+/**
+ * Logging levels for measurements of execution time. Different logging levels - {@link #SLOW},
+ * {@link #SLOWER} and {@link #SLOWEST} - are provided in order to log only the events taking
+ * more than some time duration. For example the console could log only the slowest events,
+ * while a file could log all events considered slow.
+ * <p>
+ * Every levels defined in this class have a {@linkplain #intValue() value} between the
+ * {@link Level#FINE} and {@link Level#CONFIG} values. Consequently performance logging are
+ * disabled by default, and enabling them imply enabling configuration logging too. This is
+ * done that way because the configuration typically have a significant impact on performance.
+ *
+ * {@section Enabling performance logging}
+ * Performance logging can be enabled in various ways. Among others:
+ * <p>
+ * <ul>
+ *   <li>The {@code $JAVA_HOME/lib/logging.properties} file can be edited in order to log
+ *       messages at the {@code FINE} level, at least for the packages of interest.</li>
+ *   <li>The {@link Logging#forceMonolineConsoleOutput(Level)} convenience method
+ *       can be invoked.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.16)
+ * @version 0.3
+ * @module
+ */
+public final class PerformanceLevel extends Level {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 6055684381688936293L;
+
+    /*
+     * IMPLEMENTATION NOTE: The level values used in the constants below are also used
+     * in the 'switch' statements of the 'setMinDuration(...)' method. If those values
+     * are modified, don't forget to update also the switch statements!!
+     */
+
+    /**
+     * The level for logging all time measurements, regardless of their duration.
+     * The {@linkplain #intValue() value} of this level is 600.
+     */
+    public static final PerformanceLevel PERFORMANCE = new PerformanceLevel("PERFORMANCE", 600, 0);
+
+    /**
+     * The level for logging relatively slow events. By default, only events having an execution
+     * time equals or greater than 0.1 second are logged at this level. However this threshold
+     * can be changed by a call to <code>SLOW.{@linkplain #setMinDuration(long, TimeUnit)}</code>.
+     */
+    public static final PerformanceLevel SLOW = new PerformanceLevel("SLOW", 610, 100000000L);
+
+    /**
+     * The level for logging only events slower than the ones logged at the {@link #SLOW} level.
+     * By default, only events having an execution time equals or greater than 1 second are
+     * logged at this level. However this threshold can be changed by a call to
+     * <code>SLOWER.{@linkplain #setMinDuration(long, TimeUnit)}</code>.
+     */
+    public static final PerformanceLevel SLOWER = new PerformanceLevel("SLOWER", 620, 1000000000L);
+
+    /**
+     * The level for logging only slowest events. By default, only events having an execution
+     * time equals or greater than 5 seconds are logged at this level. However this threshold
+     * can be changed by a call to <code>SLOWEST.{@linkplain #setMinDuration(long, TimeUnit)}</code>.
+     */
+    public static final PerformanceLevel SLOWEST = new PerformanceLevel("SLOWEST", 630, 5000000000L);
+
+    /**
+     * The minimal duration (in nanoseconds) for logging the record.
+     */
+    private volatile long minDuration;
+
+    /**
+     * Constructs a new logging level for monitoring performance.
+     *
+     * @param name     The logging level name.
+     * @param value    The level value.
+     * @param duration The minimal duration (in nanoseconds) for logging a record.
+     */
+    private PerformanceLevel(final String name, final int value, final long duration) {
+        super(name, value);
+        minDuration = duration;
+    }
+
+    /**
+     * Returns the level to use for logging an event of the given duration.
+     *
+     * @param  duration The event duration.
+     * @param  unit The unit of the given duration value.
+     * @return The level to use for logging an event of the given duration.
+     */
+    public static PerformanceLevel forDuration(long duration, final TimeUnit unit) {
+        duration = unit.toNanos(duration);
+        if (duration >= SLOWER.minDuration) {
+            return (duration >= SLOWEST.minDuration) ? SLOWEST : SLOWER;
+        } else {
+            return (duration >= SLOW.minDuration) ? SLOW : PERFORMANCE;
+        }
+    }
+
+    /**
+     * Returns the minimal duration for logging an event at this level.
+     *
+     * @param  unit The unit in which to express the minimal duration.
+     * @return The minimal duration in the given unit.
+     */
+    public long getMinDuration(final TimeUnit unit) {
+        return unit.convert(minDuration, TimeUnit.NANOSECONDS);
+    }
+
+    /**
+     * Sets the minimal duration for logging an event at this level. Invoking this method
+     * may have an indirect impact of other performance levels:
+     * <p>
+     * <ul>
+     *   <li>If the given duration is longer than the duration of slower levels, then the later
+     *       are also set to the given duration.</li>
+     *   <li>If the given duration is shorter than the duration of faster levels, then the later
+     *       are also set to the given duration.</li>
+     * </ul>
+     *
+     * {@note The duration of the <code>PERFORMANCE</code> level can not be modified: it is
+     * always zero. However invoking this method on the <code>PERFORMANCE</code> field will
+     * ensure that every <code>SLOW*</code> levels will have at least the given duration.}
+     *
+     * @param  duration The minimal duration.
+     * @param  unit The unit of the given duration value.
+     * @throws IllegalArgumentException If the given duration is negative.
+     */
+    @Configuration
+    @SuppressWarnings("fallthrough")
+    public void setMinDuration(long duration, final TimeUnit unit) throws IllegalArgumentException {
+        ensurePositive("duration", duration);
+        duration = unit.toNanos(duration);
+        final int value = intValue();
+        synchronized (PerformanceLevel.class) {
+            // Check the value of slower levels.
+            switch (value) {
+                default:  throw new AssertionError(this);
+                case 600: if (duration > SLOW   .minDuration) SLOW   .minDuration = duration;
+                case 610: if (duration > SLOWER .minDuration) SLOWER .minDuration = duration;
+                case 620: if (duration > SLOWEST.minDuration) SLOWEST.minDuration = duration;
+                case 630: // Do nothing since there is no level slower than 'SLOWEST'.
+            }
+            // Check the value of faster levels.
+            switch (value) {
+                default:  throw new AssertionError(this);
+                case 630: if (duration < SLOWER .minDuration) SLOWER .minDuration = duration;
+                case 620: if (duration < SLOW   .minDuration) SLOW   .minDuration = duration;
+                case 610: minDuration = duration;
+                case 600: // Do nothing, since we don't allow modification of PERFORMANCE level.
+            }
+        }
+    }
+}

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/logging/PerformanceLevel.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message