SLF4j 311 - Enable swapping of NOPLogger in SubstituteLoggerFactory
diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/SubstitutableLogger.java b/slf4j-api/src/main/java/org/slf4j/helpers/SubstitutableLogger.java
new file mode 100644
index 0000000..468d53b
--- /dev/null
+++ b/slf4j-api/src/main/java/org/slf4j/helpers/SubstitutableLogger.java
@@ -0,0 +1,314 @@
+/**
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+package org.slf4j.helpers;
+
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+/**
+ * A helper class which delegates all calls to different Loggers. This enables
+ * swapping of the Logger implementation later.
+ *
+ * @author Chetan Mehrotra
+ */
+public class SubstitutableLogger implements Logger {
+
+  private final String name;
+
+  private volatile Logger _delegate;
+
+  public SubstitutableLogger(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public boolean isTraceEnabled() {
+    return delegate().isTraceEnabled();
+  }
+
+  public void trace(String msg) {
+    delegate().trace(msg);
+  }
+
+  public void trace(String format, Object arg) {
+    delegate().trace(format, arg);
+  }
+
+  public void trace(String format, Object arg1, Object arg2) {
+    delegate().trace(format, arg1, arg2);
+  }
+
+  public void trace(String format, Object... arguments) {
+    delegate().trace(format, arguments);
+  }
+
+  public void trace(String msg, Throwable t) {
+    delegate().trace(msg, t);
+  }
+
+  public boolean isTraceEnabled(Marker marker) {
+    return delegate().isTraceEnabled(marker);
+  }
+
+  public void trace(Marker marker, String msg) {
+    delegate().trace(marker, msg);
+  }
+
+  public void trace(Marker marker, String format, Object arg) {
+    delegate().trace(marker, format, arg);
+  }
+
+  public void trace(Marker marker, String format, Object arg1, Object arg2) {
+    delegate().trace(marker, format, arg1, arg2);
+  }
+
+  public void trace(Marker marker, String format, Object... arguments) {
+    delegate().trace(marker, format, arguments);
+  }
+
+  public void trace(Marker marker, String msg, Throwable t) {
+    delegate().trace(marker, msg, t);
+  }
+
+  public boolean isDebugEnabled() {
+    return delegate().isDebugEnabled();
+  }
+
+  public void debug(String msg) {
+    delegate().debug(msg);
+  }
+
+  public void debug(String format, Object arg) {
+    delegate().debug(format, arg);
+  }
+
+  public void debug(String format, Object arg1, Object arg2) {
+    delegate().debug(format, arg1, arg2);
+  }
+
+  public void debug(String format, Object... arguments) {
+    delegate().debug(format, arguments);
+  }
+
+  public void debug(String msg, Throwable t) {
+    delegate().debug(msg, t);
+  }
+
+  public boolean isDebugEnabled(Marker marker) {
+    return delegate().isDebugEnabled(marker);
+  }
+
+  public void debug(Marker marker, String msg) {
+    delegate().debug(marker, msg);
+  }
+
+  public void debug(Marker marker, String format, Object arg) {
+    delegate().debug(marker, format, arg);
+  }
+
+  public void debug(Marker marker, String format, Object arg1, Object arg2) {
+    delegate().debug(marker, format, arg1, arg2);
+  }
+
+  public void debug(Marker marker, String format, Object... arguments) {
+    delegate().debug(marker, format, arguments);
+  }
+
+  public void debug(Marker marker, String msg, Throwable t) {
+    delegate().debug(marker, msg, t);
+  }
+
+  public boolean isInfoEnabled() {
+    return delegate().isInfoEnabled();
+  }
+
+  public void info(String msg) {
+    delegate().info(msg);
+  }
+
+  public void info(String format, Object arg) {
+    delegate().info(format, arg);
+  }
+
+  public void info(String format, Object arg1, Object arg2) {
+    delegate().info(format, arg1, arg2);
+  }
+
+  public void info(String format, Object... arguments) {
+    delegate().info(format, arguments);
+  }
+
+  public void info(String msg, Throwable t) {
+    delegate().info(msg, t);
+  }
+
+  public boolean isInfoEnabled(Marker marker) {
+    return delegate().isInfoEnabled(marker);
+  }
+
+  public void info(Marker marker, String msg) {
+    delegate().info(marker, msg);
+  }
+
+  public void info(Marker marker, String format, Object arg) {
+    delegate().info(marker, format, arg);
+  }
+
+  public void info(Marker marker, String format, Object arg1, Object arg2) {
+    delegate().info(marker, format, arg1, arg2);
+  }
+
+  public void info(Marker marker, String format, Object... arguments) {
+    delegate().info(marker, format, arguments);
+  }
+
+  public void info(Marker marker, String msg, Throwable t) {
+    delegate().info(marker, msg, t);
+  }
+
+  public boolean isWarnEnabled() {
+    return delegate().isWarnEnabled();
+  }
+
+  public void warn(String msg) {
+    delegate().warn(msg);
+  }
+
+  public void warn(String format, Object arg) {
+    delegate().warn(format, arg);
+  }
+
+  public void warn(String format, Object arg1, Object arg2) {
+    delegate().warn(format, arg1, arg2);
+  }
+
+  public void warn(String format, Object... arguments) {
+    delegate().warn(format, arguments);
+  }
+
+  public void warn(String msg, Throwable t) {
+    delegate().warn(msg, t);
+  }
+
+  public boolean isWarnEnabled(Marker marker) {
+    return delegate().isWarnEnabled(marker);
+  }
+
+  public void warn(Marker marker, String msg) {
+    delegate().warn(marker, msg);
+  }
+
+  public void warn(Marker marker, String format, Object arg) {
+    delegate().warn(marker, format, arg);
+  }
+
+  public void warn(Marker marker, String format, Object arg1, Object arg2) {
+    delegate().warn(marker, format, arg1, arg2);
+  }
+
+  public void warn(Marker marker, String format, Object... arguments) {
+    delegate().warn(marker, format, arguments);
+  }
+
+  public void warn(Marker marker, String msg, Throwable t) {
+    delegate().warn(marker, msg, t);
+  }
+
+  public boolean isErrorEnabled() {
+    return delegate().isErrorEnabled();
+  }
+
+  public void error(String msg) {
+    delegate().error(msg);
+  }
+
+  public void error(String format, Object arg) {
+    delegate().error(format, arg);
+  }
+
+  public void error(String format, Object arg1, Object arg2) {
+    delegate().error(format, arg1, arg2);
+  }
+
+  public void error(String format, Object... arguments) {
+    delegate().error(format, arguments);
+  }
+
+  public void error(String msg, Throwable t) {
+    delegate().error(msg, t);
+  }
+
+  public boolean isErrorEnabled(Marker marker) {
+    return delegate().isErrorEnabled(marker);
+  }
+
+  public void error(Marker marker, String msg) {
+    delegate().error(marker, msg);
+  }
+
+  public void error(Marker marker, String format, Object arg) {
+    delegate().error(marker, format, arg);
+  }
+
+  public void error(Marker marker, String format, Object arg1, Object arg2) {
+    delegate().error(marker, format, arg1, arg2);
+  }
+
+  public void error(Marker marker, String format, Object... arguments) {
+    delegate().error(marker, format, arguments);
+  }
+
+  public void error(Marker marker, String msg, Throwable t) {
+    delegate().error(marker, msg, t);
+  }
+
+  public void setDelegate(Logger delegate) {
+    this._delegate = delegate;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    SubstitutableLogger that = (SubstitutableLogger) o;
+
+    if (!name.equals(that.name)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+
+  Logger delegate() {
+    return _delegate != null ? _delegate : NOPLogger.NOP_LOGGER;
+  }
+}
diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java
index 7382ed7..223606e 100644
--- a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java
+++ b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java
@@ -26,6 +26,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.slf4j.ILoggerFactory;
 import org.slf4j.Logger;
@@ -33,32 +35,50 @@
 /**
  * SubstituteLoggerFactory is an trivial implementation of
  * {@link ILoggerFactory} which always returns the unique instance of NOPLogger.
- * 
+ * <p/>
  * <p>
  * It used as a temporary substitute for the real ILoggerFactory during its
  * auto-configuration which may re-enter LoggerFactory to obtain logger
  * instances. See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
- * 
+ * <p/>
+ * <p>
+ * Logger implementations can swap out the NOPLogger with actual Logger
+ * implementation once they are properly configured by changing the delegate
+ * in {@link org.slf4j.helpers.SubstitutableLogger}
+ * </p>
+ *
  * @author Ceki G&uuml;lc&uuml;
  */
 public class SubstituteLoggerFactory implements ILoggerFactory {
 
   // keep a record of requested logger names
-  final List loggerNameList = new ArrayList();
+  final ConcurrentMap<String, SubstitutableLogger> loggers = new ConcurrentHashMap<String, SubstitutableLogger>();
 
   public Logger getLogger(String name) {
-    synchronized (loggerNameList) {
-      loggerNameList.add(name);
+    SubstitutableLogger logger;
+    synchronized (loggers) {
+      logger = loggers.get(name);
+      if (logger == null) {
+        logger = new SubstitutableLogger(name);
+        loggers.put(name, logger);
+      }
     }
-    return NOPLogger.NOP_LOGGER;
+    return logger;
   }
 
   public List getLoggerNameList() {
-    List copy = new ArrayList();
-    synchronized (loggerNameList) {
-      copy.addAll(loggerNameList);
+    List<String> copy = new ArrayList<String>();
+    synchronized (loggers) {
+      copy.addAll(loggers.keySet());
     }
     return copy;
   }
 
+  public Iterable<SubstitutableLogger> getLoggers() {
+    return loggers.values();
+  }
+
+  public void clear() {
+    loggers.clear();
+  }
 }
diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java
new file mode 100644
index 0000000..f3f5524
--- /dev/null
+++ b/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+package org.slf4j.helpers;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import org.slf4j.Logger;
+
+/**
+ * @author Chetan Mehrotra
+ */
+public class SubstitutableLoggerTest extends TestCase {
+  private static final Set<String> EXCLUDED_METHODS = new HashSet<String>(Arrays.asList("getName"));
+
+  public void testDelegate() throws Exception {
+    SubstitutableLogger log = new SubstitutableLogger("foo");
+    assertTrue(log.delegate() instanceof NOPLogger);
+
+    Set<String> methodSignatures = determinemethodSignatures(Logger.class);
+    LoggerInvocationHandler ih = new LoggerInvocationHandler();
+    Logger proxyLogger = (Logger) Proxy.newProxyInstance(getClass().getClassLoader(),
+        new Class[]{Logger.class}, ih);
+    log.setDelegate(proxyLogger);
+
+    invokeMethods(log);
+
+    //Assert that all methods are delegated
+    methodSignatures.removeAll(ih.getMethodSignatures());
+    if (!methodSignatures.isEmpty()) {
+      fail("Following methods are not delegated " + methodSignatures.toString());
+    }
+  }
+
+  private void invokeMethods(Logger proxyLogger) throws InvocationTargetException, IllegalAccessException {
+    for (Method m : Logger.class.getDeclaredMethods()) {
+      if (!EXCLUDED_METHODS.contains(m.getName())) {
+        m.invoke(proxyLogger, new Object[m.getParameterTypes().length]);
+      }
+    }
+  }
+
+  private class LoggerInvocationHandler implements InvocationHandler {
+    private final Set<String> methodSignatures = new HashSet<String>();
+
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+      methodSignatures.add(getMethodSignature(method));
+      if (method.getName().startsWith("is")) {
+        return true;
+      }
+      return null;
+    }
+
+    public Set<String> getMethodSignatures() {
+      return methodSignatures;
+    }
+  }
+
+  private static Set<String> determinemethodSignatures(Class<Logger> loggerClass) {
+    Set<String> methodSignatures = new HashSet<String>();
+    for (Method m : loggerClass.getDeclaredMethods()) {
+      if (!EXCLUDED_METHODS.contains(m.getName())) {
+        methodSignatures.add(getMethodSignature(m));
+      }
+    }
+    return methodSignatures;
+  }
+
+  private static String getMethodSignature(Method m) {
+    List<String> result = new ArrayList<String>();
+    result.add(m.getName());
+    for (Class<?> clazz : m.getParameterTypes()) {
+      result.add(clazz.getSimpleName());
+    }
+    return result.toString();
+  }
+}
diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java
new file mode 100644
index 0000000..c6c5a48
--- /dev/null
+++ b/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+package org.slf4j.helpers;
+
+import junit.framework.TestCase;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SubstituteLoggerFactoryTest extends TestCase{
+  private SubstituteLoggerFactory factory = new SubstituteLoggerFactory();
+
+  public void testFactory() {
+    Logger log = factory.getLogger("foo");
+    assertNotNull(log);
+
+    Logger log2 = factory.getLogger("foo");
+    assertTrue("Loggers with same name must be same",log == log2);
+  }
+
+  @SuppressWarnings("unchecked")
+  public void testLoggerNameList() {
+    factory.getLogger("foo1");
+    factory.getLogger("foo2");
+
+    Set<String> expectedNames = new HashSet<String>(Arrays.asList("foo1","foo2"));
+    Set<String> actualNames = new HashSet<String>(factory.getLoggerNameList());
+
+    assertEquals(expectedNames, actualNames);
+  }
+
+  public void testLoggers() {
+    factory.getLogger("foo1");
+    factory.getLogger("foo2");
+
+    Set<String> expectedNames = new HashSet<String>(Arrays.asList("foo1","foo2"));
+
+    Set<String> actualNames = new HashSet<String>();
+    for(SubstitutableLogger slog : factory.getLoggers()){
+      actualNames.add(slog.getName());
+    }
+
+    assertEquals(expectedNames, actualNames);
+  }
+
+}