Bug 42042: Copy org.apache.log4j.helpers to component project

git-svn-id: https://svn.apache.org/repos/asf/logging/sandbox/log4j/component@527667 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java b/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java
new file mode 100644
index 0000000..bfd1b09
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+
+/**
+   Formats a {@link Date} in the format "HH:mm:ss,SSS" for example,
+   "15:49:37,459".
+
+   @author Ceki Gülcü
+   @author Andrew Vajoczki
+
+   @since 0.7.5
+   @deprecated use java.text.SimpleDateFormat to perform date conversion
+       or use org.apache.log4j.helpers.CachedDateFormat to optimize
+       high-frequency date formatting.
+*/
+public class AbsoluteTimeDateFormat extends DateFormat {
+  /**
+     String constant used to specify the Absolute time and date format.
+  */
+  public static final String ABS_TIME_DATE_FORMAT = "ABSOLUTE";
+
+  /**
+     String constant used to specify the "Date and Time" date format.
+  */
+  public static final String DATE_AND_TIME_DATE_FORMAT = "DATE";
+
+  /**
+     String constant used to specify the ISO8601 format.
+  */
+  public static final String ISO8601_DATE_FORMAT = "ISO8601";
+
+  /**
+     Equivalent SimpleDateFormat pattern.
+   */
+  public static final String PATTERN = "HH:mm:ss,SSS";
+
+  /**
+   * SimpleDateFormat used to perform format requests.
+   */
+  private final SimpleDateFormat format;
+
+  /**
+   *  Create a new instance of AbsoluteTimeDateFormat.
+   */
+  public AbsoluteTimeDateFormat() {
+    format = new SimpleDateFormat(PATTERN);
+  }
+
+  /**
+   *   Create a new instance of AbsoluteTimeDateFormat.
+   *   @param timeZone time zone used in conversion, may not be null.
+   */
+  public AbsoluteTimeDateFormat(final TimeZone timeZone) {
+    format = new SimpleDateFormat(PATTERN);
+    format.setTimeZone(timeZone);
+  }
+
+    /**
+     *   Create a new instance of AbsoluteTimeDateFormat.
+     *   @param pattern SimpleDateFormat pattern.
+     */
+   protected AbsoluteTimeDateFormat(final String pattern) {
+      format = new SimpleDateFormat(pattern);
+    }
+
+    /**
+     *   Create a new instance of AbsoluteTimeDateFormat.
+     *   @param pattern SimpleDateFormat pattern.
+     *   @param timeZone time zone used in conversion, may not be null.
+     */
+   protected AbsoluteTimeDateFormat(final String pattern,
+                                    final TimeZone timeZone) {
+      format = new SimpleDateFormat(pattern);
+      format.setTimeZone(timeZone);
+    }
+
+  /**
+   * {@inheritDoc}
+    */
+  public StringBuffer format(
+    Date date, StringBuffer sbuf, FieldPosition fieldPosition) {
+    return format.format(date, sbuf, fieldPosition);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Date parse(String s, ParsePosition pos) {
+    return format.parse(s, pos);
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java b/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java
new file mode 100644
index 0000000..7d40478
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+   A straightforward implementation of the {@link AppenderAttachable}
+   interface.
+
+   @author Ceki Gülcü
+   @since version 0.9.1 */
+public class AppenderAttachableImpl implements AppenderAttachable {
+  /** Array of appenders. */
+  protected Vector appenderList;
+
+  /**
+     Attach an appender. If the appender is already in the list in
+     won't be added again.
+  */
+  public void addAppender(Appender newAppender) {
+    // Null values for newAppender parameter are strictly forbidden.
+    if (newAppender == null) {
+      return;
+    }
+
+    if (appenderList == null) {
+      appenderList = new Vector(1);
+    }
+
+    if (!appenderList.contains(newAppender)) {
+      appenderList.addElement(newAppender);
+    }
+  }
+
+  /**
+     Call the <code>doAppend</code> method on all attached appenders.  */
+  public int appendLoopOnAppenders(LoggingEvent event) {
+    int size = 0;
+    Appender appender;
+
+    if (appenderList != null) {
+      size = appenderList.size();
+
+      for (int i = 0; i < size; i++) {
+        appender = (Appender) appenderList.elementAt(i);
+        appender.doAppend(event);
+      }
+    }
+
+    return size;
+  }
+
+  /**
+     Get all attached appenders as an Enumeration. If there are no
+     attached appenders <code>null</code> is returned.
+
+     @return Enumeration An enumeration of attached appenders.
+   */
+  public Enumeration getAllAppenders() {
+    if (appenderList == null) {
+      return null;
+    } else {
+      return appenderList.elements();
+    }
+  }
+
+  /**
+     Look for an attached appender named as <code>name</code>.
+
+     <p>Return the appender with that name if in the list. Return null
+     otherwise.
+
+   */
+  public Appender getAppender(String name) {
+    if ((appenderList == null) || (name == null)) {
+      return null;
+    }
+
+    int size = appenderList.size();
+    Appender appender;
+
+    for (int i = 0; i < size; i++) {
+      appender = (Appender) appenderList.elementAt(i);
+
+      if (name.equals(appender.getName())) {
+        return appender;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+     Returns <code>true</code> if the specified appender is in the
+     list of attached appenders, <code>false</code> otherwise.
+
+     @since 1.2 */
+  public boolean isAttached(Appender appender) {
+    if ((appenderList == null) || (appender == null)) {
+      return false;
+    }
+
+    int size = appenderList.size();
+    Appender a;
+
+    for (int i = 0; i < size; i++) {
+      a = (Appender) appenderList.elementAt(i);
+
+      if (a == appender) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Remove and close all previously attached appenders.
+   * */
+  public void removeAllAppenders() {
+    if (appenderList != null) {
+      int len = appenderList.size();
+
+      for (int i = 0; i < len; i++) {
+        Appender a = (Appender) appenderList.elementAt(i);
+        a.close();
+      }
+
+      appenderList.removeAllElements();
+      appenderList = null;
+    }
+  }
+
+  /**
+     Remove the appender passed as parameter form the list of attached
+     appenders.  */
+  public void removeAppender(Appender appender) {
+    if ((appender == null) || (appenderList == null)) {
+      return;
+    }
+
+    appenderList.removeElement(appender);
+  }
+
+  /**
+     Remove the appender with the name passed as parameter form the
+     list of appenders.
+   */
+  public void removeAppender(String name) {
+    if ((name == null) || (appenderList == null)) {
+      return;
+    }
+
+    int size = appenderList.size();
+
+    for (int i = 0; i < size; i++) {
+      if (name.equals(((Appender) appenderList.elementAt(i)).getName())) {
+        appenderList.removeElementAt(i);
+
+        break;
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java b/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java
new file mode 100644
index 0000000..479593d
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.
+ */
+
+// Contributors:     Mathias Bogaert
+//                   joelr@viair.com
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   <code>BoundedFIFO</code> serves as the bounded first-in-first-out
+   buffer previously used by the {@link org.apache.log4j.AsyncAppender}.
+   
+   @author Ceki G&uuml;lc&uuml; 
+   @since version 0.9.1
+
+   @deprecated Since 1.3.
+
+ */
+public class BoundedFIFO {
+  
+  LoggingEvent[] buf;
+  int numElements = 0;
+  int first = 0;
+  int next = 0;
+  int maxSize;
+
+  /**
+     Instantiate a new BoundedFIFO with a maximum size passed as argument.
+   */
+  public
+  BoundedFIFO(int maxSize) {
+   if(maxSize < 1) {
+      throw new IllegalArgumentException("The maxSize argument ("+maxSize+
+			    ") is not a positive integer.");
+    }
+    this.maxSize = maxSize;
+    buf = new LoggingEvent[maxSize];
+  }
+  
+  /**
+     Get the first element in the buffer. Returns <code>null</code> if
+     there are no elements in the buffer.  */
+  public
+  LoggingEvent get() {
+    if(numElements == 0) 
+      return null;
+    
+    LoggingEvent r = buf[first];
+    buf[first] = null; // help garbage collection
+
+    if(++first == maxSize) {
+	first = 0;
+    }
+    numElements--;    
+    return r;    
+  }
+
+  /**
+     Place a {@link LoggingEvent} in the buffer. If the buffer is full
+     then the event is <b>silently dropped</b>. It is the caller's
+     responsability to make sure that the buffer has free space.  */
+  public 
+  void put(LoggingEvent o) {
+    if(numElements != maxSize) {      
+      buf[next] = o;    
+      if(++next == maxSize) {
+	next = 0;
+      }
+      numElements++;
+    }
+  }
+
+  /**
+     Get the maximum size of the buffer.
+   */
+  public 
+  int getMaxSize() {
+    return maxSize;
+  }
+
+  /**
+     Return <code>true</code> if the buffer is full, that is the
+     number of elements in the buffer equals the buffer size. */
+  public 
+  boolean isFull() {
+    return numElements == maxSize;
+  }
+
+  /**
+     Get the number of elements in the buffer. This number is
+     guaranteed to be in the range 0 to <code>maxSize</code>
+     (inclusive).
+  */
+  public
+  int length() {
+    return numElements;
+  } 
+
+
+  int min(int a, int b) {
+    return a < b ? a : b;
+  }
+
+
+  /**
+     Resize the buffer to a new size. If the new size is smaller than
+     the old size events might be lost.
+     
+     @since 1.1
+   */
+  synchronized
+  public 
+  void resize(int newSize) {
+    if(newSize == maxSize) 
+      return;
+
+
+   LoggingEvent[] tmp = new LoggingEvent[newSize];
+
+   // we should not copy beyond the buf array
+   int len1 = maxSize - first;
+
+   // we should not copy beyond the tmp array
+   len1 = min(len1, newSize);
+
+   // er.. how much do we actually need to copy?
+   // We should not copy more than the actual number of elements.
+   len1 = min(len1, numElements);
+
+   // Copy from buf starting a first, to tmp, starting at position 0, len1 elements.
+   System.arraycopy(buf, first, tmp, 0, len1);
+   
+   // Are there any uncopied elements and is there still space in the new array?
+   int len2 = 0;
+   if((len1 < numElements) && (len1 < newSize)) {
+     len2 = numElements - len1;
+     len2 = min(len2, newSize - len1);
+     System.arraycopy(buf, 0, tmp, len1, len2);
+   }
+   
+   this.buf = tmp;
+   this.maxSize = newSize;    
+   this.first=0;   
+   this.numElements = len1+len2;
+   this.next = this.numElements;
+   if(this.next == this.maxSize) // this should never happen, but again, it just might.
+     this.next = 0;
+  }
+
+  
+  /**
+     Returns <code>true</code> if there is just one element in the
+     buffer. In other words, if there were no elements before the last
+     {@link #put} operation completed.  */
+  public
+  boolean wasEmpty() {
+    return numElements == 1;
+  }
+
+  /**
+      Returns <code>true</code> if the number of elements in the
+      buffer plus 1 equals the maximum buffer size, returns
+      <code>false</code> otherwise. */
+  public
+  boolean wasFull() {
+    return (numElements+1 == maxSize);
+  }
+
+}
diff --git a/src/main/java/org/apache/log4j/helpers/Constants.java b/src/main/java/org/apache/log4j/helpers/Constants.java
new file mode 100644
index 0000000..bff79fd
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/Constants.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+
+/**
+ * Constants used internally throughout log4j.
+ * 
+ * @since 1.3
+ */
+public interface Constants {
+  
+  static final String LOG4J_PACKAGE_NAME = "org.apache.log4j";
+  
+  /**
+   *  The name of the default repository is "default" (without the quotes).
+   */
+  static final String DEFAULT_REPOSITORY_NAME  = "default";
+  
+  
+  static final String APPLICATION_KEY = "application";
+  static final String HOSTNAME_KEY = "hostname";
+  static final String RECEIVER_NAME_KEY = "receiver";
+  static final String LOG4J_ID_KEY = "log4jid";
+  public static final String TIMESTAMP_RULE_FORMAT = "yyyy/MM/dd HH:mm:ss";
+
+  /*
+   * The default property file name for automatic configuration.
+   */
+  static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
+  /*
+   * The default XML configuration file name for automatic configuration.
+   */
+  static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
+  static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
+  static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
+  
+  static final String JNDI_CONTEXT_NAME = "java:comp/env/log4j/context-name";
+  
+  static final String TEMP_LIST_APPENDER_NAME = "TEMP_LIST_APPENDER";
+  static final String TEMP_CONSOLE_APPENDER_NAME = "TEMP_CONSOLE_APPENDER";
+  static final String CODES_HREF = "http://logging.apache.org/log4j/docs/codes.html";
+  
+  
+  public static final String ABSOLUTE_FORMAT = "ABSOLUTE";
+  public static final String ABSOLUTE_TIME_PATTERN = "HH:mm:ss,SSS";
+
+
+  public static final String DATE_AND_TIME_FORMAT = "DATE";
+  public static final String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS";
+  
+  public static final String ISO8601_FORMAT = "ISO8601";
+  public static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
+}
diff --git a/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.java b/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.java
new file mode 100644
index 0000000..6d30fcb
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.io.Writer;
+import java.io.IOException;
+
+import org.apache.log4j.spi.ErrorCode;
+
+/**
+   Counts the number of bytes written.
+
+   @author Heinz Richter, heinz.richter@frogdot.com
+   @since 0.8.1
+   @deprecated
+
+   */
+public class CountingQuietWriter extends QuietWriter {
+
+  protected long count;
+
+    /**
+     * @deprecated
+     * @param writer
+     * @param eh
+     */
+  public
+  CountingQuietWriter(Writer writer, org.apache.log4j.spi.ErrorHandler eh) {
+    super(writer, eh);
+  }
+
+  public
+  void write(String string) {
+    try {
+      out.write(string);
+      count += string.length();
+    }
+    catch(IOException e) {
+      errorHandler.error("Write failure.", e, ErrorCode.WRITE_FAILURE);
+    }
+  }
+
+  public
+  long getCount() {
+    return count;
+  }
+
+  public
+  void setCount(long count) {
+    this.count = count;
+  }
+
+}
diff --git a/src/main/java/org/apache/log4j/helpers/CyclicBuffer.java b/src/main/java/org/apache/log4j/helpers/CyclicBuffer.java
new file mode 100644
index 0000000..216d7f9
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/CyclicBuffer.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+
+   CyclicBuffer is used by other appenders to hold {@link LoggingEvent
+   LoggingEvents} for immediate or differed display.
+   
+   <p>This buffer gives read access to any element in the buffer not
+   just the first or last element.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.0
+
+ */
+public class CyclicBuffer {
+  
+  LoggingEvent[] ea;
+  int first; 
+  int last; 
+  int numElems;
+  int maxSize;
+
+  /**
+     Instantiate a new CyclicBuffer of at most <code>maxSize</code> events.
+
+     The <code>maxSize</code> argument must a positive integer.
+
+     @param maxSize The maximum number of elements in the buffer.
+  */
+  public CyclicBuffer(int maxSize) throws IllegalArgumentException {
+    if(maxSize < 1) {
+      throw new IllegalArgumentException("The maxSize argument ("+maxSize+
+			    ") is not a positive integer.");
+    }
+    this.maxSize = maxSize;
+    ea = new LoggingEvent[maxSize];
+    first = 0;
+    last = 0;
+    numElems = 0;
+  }
+    
+  /**
+     Add an <code>event</code> as the last event in the buffer.
+
+   */
+  public
+  void add(LoggingEvent event) {    
+    ea[last] = event;    
+    if(++last == maxSize)
+      last = 0;
+
+    if(numElems < maxSize)
+      numElems++;
+    else if(++first == maxSize)
+      first = 0;
+  }
+
+
+  /**
+     Get the <i>i</i>th oldest event currently in the buffer. If
+     <em>i</em> is outside the range 0 to the number of elements
+     currently in the buffer, then <code>null</code> is returned.
+
+
+  */
+  public
+  LoggingEvent get(int i) {
+    if(i < 0 || i >= numElems)
+      return null;
+
+    return ea[(first + i) % maxSize];
+  }
+
+  public 
+  int getMaxSize() {
+    return maxSize;
+  }
+
+  /**
+     Get the oldest (first) element in the buffer. The oldest element
+     is removed from the buffer.
+  */
+  public
+  LoggingEvent get() {
+    LoggingEvent r = null;
+    if(numElems > 0) {
+      numElems--;
+      r = ea[first];
+      ea[first] = null;
+      if(++first == maxSize)
+	first = 0;
+    } 
+    return r;
+  }
+  
+  /**
+     Get the number of elements in the buffer. This number is
+     guaranteed to be in the range 0 to <code>maxSize</code>
+     (inclusive).
+  */
+  public
+  int length() {
+    return numElems;
+  } 
+
+  /**
+     Resize the cyclic buffer to <code>newSize</code>.
+
+     @throws IllegalArgumentException if <code>newSize</code> is negative.
+   */
+  public 
+  void resize(int newSize) {
+    if(newSize < 0) {
+      throw new IllegalArgumentException("Negative array size ["+newSize+
+					 "] not allowed.");
+    }
+    if(newSize == numElems)
+      return; // nothing to do
+    
+    LoggingEvent[] temp = new  LoggingEvent[newSize];
+
+    int loopLen = newSize < numElems ? newSize : numElems;
+    
+    for(int i = 0; i < loopLen; i++) {
+      temp[i] = ea[first];
+      ea[first] = null;
+      if(++first == numElems) 
+	first = 0;
+    }
+    ea = temp;
+    first = 0;
+    numElems = loopLen;
+    maxSize = newSize;
+    if (loopLen == newSize) {
+      last = 0;
+    } else {
+      last = loopLen;
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/DateLayout.java b/src/main/java/org/apache/log4j/helpers/DateLayout.java
new file mode 100644
index 0000000..c358215
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/DateLayout.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+// Contributors: Christopher Williams
+//               Mathias Bogaert
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+
+/**
+   This abstract layout takes care of all the date related options and
+   formatting work.
+
+
+   @author Ceki G&uuml;lc&uuml;
+   @deprecated since 1.3
+ */
+abstract public class DateLayout extends Layout {
+
+    /**
+       String constant designating no time information. Current value of
+       this constant is <b>NULL</b>.
+
+    */
+    public final static String NULL_DATE_FORMAT = "NULL";
+
+    /**
+       String constant designating relative time. Current value of
+       this constant is <b>RELATIVE</b>.
+     */
+    public final static String RELATIVE_TIME_DATE_FORMAT = "RELATIVE";
+
+    protected FieldPosition pos = new FieldPosition(0);
+
+    /**
+       @deprecated Options are now handled using the JavaBeans paradigm.
+       This constant is not longer needed and will be removed in the
+       <em>near</em> term.
+    */
+    final static public String DATE_FORMAT_OPTION = "DateFormat";
+
+    /**
+       @deprecated Options are now handled using the JavaBeans paradigm.
+       This constant is not longer needed and will be removed in the
+       <em>near</em> term.
+    */
+    final static public String TIMEZONE_OPTION = "TimeZone";
+
+    private String timeZoneID;
+    private String dateFormatOption;
+
+    protected DateFormat dateFormat;
+    protected Date date = new Date();
+
+
+    /**
+     * Instantiate a DateLayout object with in the ISO8601 format as the date
+     * formatter.
+     * */
+    public DateLayout() {
+    }
+
+    /**
+       Instantiate a DateLayout object using the local time zone. The
+       DateFormat used will depend on the <code>dateFormatType</code>.
+
+       <p>This constructor just calls the {@link #setDateFormat} method.
+    */
+    public DateLayout(final String dateFormatType) {
+      this.setDateFormat(dateFormatType);
+    }
+
+
+    /**
+       @deprecated Use the setter method for the option directly instead
+       of the generic <code>setOption</code> method.
+    */
+    public
+    String[] getOptionStrings() {
+      return new String[] {DATE_FORMAT_OPTION, TIMEZONE_OPTION};
+    }
+
+    /**
+       @deprecated Use the setter method for the option directly instead
+       of the generic <code>setOption</code> method.
+    */
+    public
+    void setOption(final String option, final String value) {
+      if(option.equalsIgnoreCase(DATE_FORMAT_OPTION)) {
+        dateFormatOption = value.toUpperCase();
+      } else if(option.equalsIgnoreCase(TIMEZONE_OPTION)) {
+        timeZoneID = value;
+      }
+    }
+
+    /**
+      The value of the <b>DateFormat</b> option should be either an
+      argument to the constructor of {@link SimpleDateFormat} or one of
+      the srings "NULL", "RELATIVE", "ABSOLUTE", "DATE" or "ISO8601.
+     */
+    public
+    void setDateFormat(String dateFormat) {
+      if (dateFormat != null) {
+          dateFormatOption = dateFormat;
+      }
+      setDateFormat(dateFormatOption, TimeZone.getDefault());
+    }
+
+
+
+    /**
+       Returns value of the <b>DateFormat</b> option.
+     */
+    public
+    String getDateFormat() {
+      return dateFormatOption;
+    }
+
+    /**
+      The <b>TimeZoneID</b> option is a time zone ID string in the format
+      expected by the {@link TimeZone#getTimeZone} method.
+     */
+    public
+    void setTimeZone(String timeZone) {
+      this.timeZoneID = timeZone;
+    }
+
+    /**
+       Returns value of the <b>TimeZone</b> option.
+     */
+    public
+    String getTimeZone() {
+      return timeZoneID;
+    }
+
+
+  public void activateOptions() {
+  }
+
+    /**
+       Sets the {@link DateFormat} used to format time and date in the
+       zone determined by <code>timeZone</code>.
+     */
+    public
+    void setDateFormat(DateFormat dateFormat, TimeZone timeZone) {
+      this.dateFormat = dateFormat;
+      this.dateFormat.setTimeZone(timeZone);
+    }
+
+
+  public void setDateFormat(final String dateFormatStr, final TimeZone timeZone) {
+    if (dateFormatStr == null) {
+      this.dateFormat = null;
+      return;
+    }
+
+    if (!dateFormatStr.equalsIgnoreCase("NULL")) {
+       if (dateFormatStr.equalsIgnoreCase(RELATIVE_TIME_DATE_FORMAT)) {
+          this.dateFormat =  new RelativeTimeDateFormat();
+       } else {
+           if (dateFormatStr.equalsIgnoreCase(Constants.ABSOLUTE_FORMAT)) {
+              dateFormat = new SimpleDateFormat(Constants.ABSOLUTE_TIME_PATTERN);
+           } else if (dateFormatStr.equalsIgnoreCase(Constants.DATE_AND_TIME_FORMAT)) {
+              dateFormat = new SimpleDateFormat(Constants.DATE_AND_TIME_PATTERN);
+           } else if (dateFormatStr.equalsIgnoreCase(Constants.ISO8601_FORMAT)) {
+              dateFormat = new SimpleDateFormat(Constants.ISO8601_PATTERN);
+           } else {
+              dateFormat = new SimpleDateFormat(dateFormatStr);
+           }
+           if (timeZone != null) {
+              dateFormat.setTimeZone(timeZone);
+           }
+       }
+    }
+  }
+
+
+    public
+    void dateFormat(StringBuffer buf, LoggingEvent event) {
+      if(dateFormat != null) {
+        date.setTime(event.timeStamp);
+        dateFormat.format(date, buf, this.pos);
+        buf.append(' ');
+      }
+    }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.java b/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.java
new file mode 100644
index 0000000..4eda99f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.util.TimeZone;
+import java.util.Date;
+import java.text.DateFormatSymbols;
+
+/**
+ * Formats a {@link Date} in the format "dd MMM yyyy HH:mm:ss,SSS" for example,
+ * "06 Nov 1994 15:49:37,459".
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @since 0.7.5
+ * @deprecated
+ */
+public class DateTimeDateFormat extends AbsoluteTimeDateFormat {
+    /**
+     * Equivalent format string for SimpleDateFormat.
+     */
+    private final static String PATTERN = "dd MMM yyyy HH:mm:ss,SSS";
+    /** Short names for the months. */
+    String[] shortMonths = new DateFormatSymbols().getShortMonths();
+
+    /**
+     * Create a new instance of DateTimeDateFormat.
+     */
+    public DateTimeDateFormat() {
+        super(PATTERN);
+    }
+
+
+    /**
+     * Create a new instance of DateTimeDateFormat.
+     *
+     * @param timeZone time zone used in conversion, may not be null.
+     */
+    public DateTimeDateFormat(final TimeZone timeZone) {
+        super(PATTERN, timeZone);
+    }
+
+}
diff --git a/src/main/java/org/apache/log4j/helpers/FileWatchdog.java b/src/main/java/org/apache/log4j/helpers/FileWatchdog.java
new file mode 100644
index 0000000..0d45212
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/FileWatchdog.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+// Contributors:  Mathias Bogaert
+package org.apache.log4j.helpers;
+
+import java.io.File;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+
+/**
+   Check every now and then that a certain file has not changed. If it
+   has, then call the {@link #doOnChange} method.
+
+   This class has been deprecated and is no longer used by either
+   PropertyConfigurator or DOMConfigurator.
+   
+   @author Ceki G&uuml;lc&uuml;
+   @since version 0.9.1
+   @deprecated Use org.apache.log4j.watchdog.FileWatchdog instead.
+*/
+public abstract class FileWatchdog extends Thread {
+  /**
+     The default delay between every file modification check, set to 60
+     seconds.  */
+  public static final long DEFAULT_DELAY = 60000;
+  /**
+     The name of the file to observe  for changes.
+   */
+  protected String filename;
+
+  private Logger logger = LogManager.getLogger(SyslogWriter.class);
+  
+  /**
+     The delay to observe between every check. By default set {@link
+     #DEFAULT_DELAY}. */
+  protected long delay = DEFAULT_DELAY;
+  File file;
+  long lastModif = 0;
+  boolean warnedAlready = false;
+  boolean interrupted = false;
+
+  protected FileWatchdog(String filename) {
+    this.filename = filename;
+    file = new File(filename);
+    setDaemon(true);
+    checkAndConfigure();
+  }
+
+  /**
+     Set the delay to observe between each check of the file changes.
+   */
+  public void setDelay(long delay) {
+    this.delay = delay;
+  }
+
+  protected abstract void doOnChange();
+
+  protected void checkAndConfigure() {
+    boolean fileExists;
+    try {
+      fileExists = file.exists();
+    } catch (SecurityException e) {
+      logger.warn(
+        "Was not allowed to read check file existance, file:[" + filename
+        + "].");
+      interrupted = true; // there is no point in continuing
+      return;
+    }
+
+    if (fileExists) {
+      long l = file.lastModified(); // this can also throw a SecurityException
+      if (l > lastModif) { // however, if we reached this point this
+        lastModif = l; // is very unlikely.
+        doOnChange();
+        warnedAlready = false;
+      }
+    } else {
+      if (!warnedAlready) {
+        logger.debug("[" + filename + "] does not exist.");
+        warnedAlready = true;
+      }
+    }
+  }
+
+  public void run() {
+    while (!interrupted) {
+      try {
+        Thread.sleep(delay);
+      } catch (InterruptedException e) {
+        // no interruption expected
+      }
+      checkAndConfigure();
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/FormattingInfo.java b/src/main/java/org/apache/log4j/helpers/FormattingInfo.java
new file mode 100644
index 0000000..5f27c3c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/FormattingInfo.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+
+/**
+   FormattingInfo instances contain the information obtained when parsing
+   formatting modifiers in conversion modifiers.
+
+   @author <a href=mailto:jim_cakalic@na.biomerieux.com>Jim Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.8.2
+   @deprecated Since 1.3
+ */
+public class FormattingInfo {
+  int min = -1;
+  int max = Integer.MAX_VALUE;
+  boolean leftAlign = false;
+
+  void reset() {
+    min = -1;
+    max = Integer.MAX_VALUE;
+    leftAlign = false;      
+  }
+
+  void dump() {
+    LogLog.debug("min="+min+", max="+max+", leftAlign="+leftAlign);
+  }
+}
+ 
diff --git a/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.java b/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.java
new file mode 100644
index 0000000..cf0411c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.util.TimeZone;
+
+// Contributors: Arndt Schoenewald <arndt@ibm23093i821.mc.schoenewald.de>
+
+/**
+ * Formats a {@link java.util.Date} in the format "yyyy-MM-dd HH:mm:ss,SSS" for example
+ * "1999-11-27 15:49:37,459".
+ * <p/>
+ * <p>Refer to the <a
+ * href=http://www.cl.cam.ac.uk/~mgk25/iso-time.html>summary of the
+ * International Standard Date and Time Notation</a> for more
+ * information on this format.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Andrew Vajoczki
+ * @since 0.7.5
+ * @deprecated
+ */
+public class ISO8601DateFormat extends AbsoluteTimeDateFormat {
+    /**
+     * Equivalent format string for SimpleDateFormat.
+     */
+    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss,SSS";
+
+    /**
+     * Create a new instance of ISO8601DateFormat.
+     */
+    public ISO8601DateFormat() {
+        super(FORMAT);
+    }
+
+    /**
+     * Create a new instance of ISO8601DateFormat.
+     *
+     * @param timeZone time zone used in conversion, may not be null.
+     */
+    public ISO8601DateFormat(final TimeZone timeZone) {
+        super(FORMAT, timeZone);
+    }
+
+}
+
diff --git a/src/main/java/org/apache/log4j/helpers/IntializationUtil.java b/src/main/java/org/apache/log4j/helpers/IntializationUtil.java
new file mode 100644
index 0000000..497a0b8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/IntializationUtil.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+/**
+ * This class groups certain internally used methods.
+ *
+ * @author Ceki Gulcu
+ * @since 1.3
+ */
+public class IntializationUtil {
+
+
+  public static void log4jInternalConfiguration(LoggerRepository repository) {
+    // This method does not do anoything currently. It might become useful
+    // when sub-domains are added to log4j.
+    
+//    Logger logger = repository.getLogger("LOG4J");
+//    logger.setAdditivity(false);
+//    logger.addAppender(
+//      new ConsoleAppender(
+//        new PatternLayout("log4j-internal: %r %-22c{2} - %m%n")));
+  }
+
+  /**
+   * Configure <code>repository</code> using <code>configuratonResourceStr</code> 
+   * and <code>configuratorClassNameStr</code>.  
+   * 
+   * If <code>configuratonResourceStr</code>  is not a URL it will be searched
+   * as a resource from the classpath. 
+   * 
+   * @param repository The repository to configre
+   * @param configuratonResourceStr URL to the configuration resourc
+   * @param configuratorClassNameStr The name of the class to use as
+   * the configrator. This parameter can be null.
+   * */
+  public static void initialConfiguration(LoggerRepository repository, 
+                                          String configuratonResourceStr,
+                                          String configuratorClassNameStr) {
+                             
+    if(configuratonResourceStr == null) {
+      return;
+    }
+    URL url = null;
+
+    try {
+      url = new URL(configuratonResourceStr);
+    } catch (MalformedURLException ex) {
+      // so, resource is not a URL:
+      // attempt to get the resource from the class loader path
+      // Please refer to Loader.getResource documentation.
+      url = Loader.getResource(configuratonResourceStr);
+    }
+
+    // If we have a non-null url, then delegate the rest of the
+    // configuration to the OptionConverter.selectAndConfigure
+    // method.
+    if (url != null) {
+      if (repository instanceof LoggerRepositoryEx) {
+        LogLog.info(
+            "Using URL [" + url
+            + "] for automatic log4j configuration of repository named ["+
+            ((LoggerRepositoryEx) repository).getName()+"].");
+      } else {
+          LogLog.info(
+              "Using URL [" + url
+              + "] for automatic log4j configuration of unnamed repository.");
+      }
+
+      OptionConverter.selectAndConfigure(url, configuratorClassNameStr, repository);
+    }
+  }
+
+  /*
+  public static void initialConfiguration(LoggerRepository repository) {
+    String configurationOptionStr =
+      OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
+
+    String configuratorClassName =
+      OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);
+
+    URL url = null;
+
+    // if the user has not specified the log4j.configuration
+    // property, we search first for the file "log4j.xml" and then
+    // "log4j.properties"
+    if (configurationOptionStr == null) {
+      url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
+
+      if (url == null) {
+        url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
+      }
+    } else {
+      try {
+        url = new URL(configurationOptionStr);
+      } catch (MalformedURLException ex) {
+        // so, resource is not a URL:
+        // attempt to get the resource from the class path
+        url = Loader.getResource(configurationOptionStr);
+      }
+    }
+
+    // If we have a non-null url, then delegate the rest of the
+    // configuration to the OptionConverter.selectAndConfigure
+    // method.
+    if (url != null) {
+      LogLog.debug(
+        "Using URL [" + url + "] for automatic log4j configuration.");
+      OptionConverter.selectAndConfigure(
+        url, configuratorClassName, repository);
+    } else {
+      LogLog.debug(
+        "Could not find resources to perform automatic configuration.");
+    }
+  }
+  */
+}
diff --git a/src/main/java/org/apache/log4j/helpers/JNDIUtil.java b/src/main/java/org/apache/log4j/helpers/JNDIUtil.java
new file mode 100644
index 0000000..a40355f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/JNDIUtil.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+
+/**
+ *
+ * @author Ceki Gulcu
+ */
+public class JNDIUtil {
+  public static Context getInitialContext() throws NamingException {
+    return new InitialContext();
+  }
+
+  public static String lookup(Context ctx, String name) {
+    if (ctx == null) {
+      return null;
+    }
+    try {
+      return (String) ctx.lookup(name);
+    } catch (NamingException e) {
+      //LogLog.warn("Failed to get "+name);
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/Loader.java b/src/main/java/org/apache/log4j/helpers/Loader.java
new file mode 100644
index 0000000..ab0504a
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/Loader.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+import java.net.URL;
+
+
+
+/**
+   Load resources (or images) from various sources.
+
+  @author Ceki G&uuml;lc&uuml;
+ */
+public class Loader {
+  static final String TSTR =
+    "Caught Exception while in Loader.getResource. This may be innocuous.";
+
+  // We conservatively assume that we are running under Java 1.x
+  private static boolean java1 = true;
+  private static boolean ignoreTCL = false;
+
+  static {
+    String prop = OptionConverter.getSystemProperty("java.version", null);
+
+    if (prop != null) {
+      int i = prop.indexOf('.');
+
+      if (i != -1) {
+        if (prop.charAt(i + 1) != '1') {
+          java1 = false;
+        }
+      }
+    }
+
+    String ignoreTCLProp =
+      OptionConverter.getSystemProperty("log4j.ignoreTCL", null);
+
+    if (ignoreTCLProp != null) {
+      ignoreTCL = OptionConverter.toBoolean(ignoreTCLProp, true);
+    }
+  }
+
+  /**
+   *  Get a resource by delegating to getResource(String).
+   *  @param resource resource name
+   *  @param clazz class, ignored.
+   *  @return URL to resource or null.
+   *  @deprecated as of 1.2.
+   */
+  public static URL getResource(String resource, Class clazz) {
+      return getResource(resource);
+  }
+
+  /**
+     This method will search for <code>resource</code> in different
+     places. The search order is as follows:
+
+     <ol>
+
+     <p><li>Search for <code>resource</code> using the thread context
+     class loader under Java2. This step is performed only if the <code>
+     skipTCL</code> parameter is false.</li>
+
+     <p><li>If the previous step failed, search for <code>resource</code> using
+     the class loader that loaded this class (<code>Loader</code>).</li>
+
+     <p><li>Try one last time with
+     <code>ClassLoader.getSystemResource(resource)</code>, that is is
+     using the system class loader in JDK 1.2 and virtual machine's
+     built-in class loader in JDK 1.1.
+
+     </ol>
+  */
+  public static URL getResource(String resource) {
+    ClassLoader classLoader = null;
+    URL url = null;
+
+    try {
+      classLoader = getTCL();
+
+      if (classLoader != null) {
+//        LogLog.debug(
+//          "Trying to find [" + resource + "] using context classloader "
+//          + classLoader + ".");
+        url = classLoader.getResource(resource);
+
+        if (url != null) {
+          return url;
+        }
+      }
+
+      // We could not find resource. Ler us now try with the
+      // classloader that loaded this class.
+      classLoader = Loader.class.getClassLoader();
+
+      if (classLoader != null) {
+//        LogLog.debug(
+//          "Trying to find [" + resource + "] using " + classLoader
+//          + " class loader.");
+        url = classLoader.getResource(resource);
+
+        if (url != null) {
+          return url;
+        }
+      }
+    } catch (Throwable t) {
+      // LogLog.warn(TSTR, t);
+    }
+
+    // Last ditch attempt: get the resource from the class path. It
+    // may be the case that clazz was loaded by the Extentsion class
+    // loader which the parent of the system class loader. Hence the
+    // code below.
+//    LogLog.debug(
+//      "Trying to find [" + resource
+//      + "] using ClassLoader.getSystemResource().");
+
+    return ClassLoader.getSystemResource(resource);
+  }
+
+  /**
+     Are we running under JDK 1.x?
+  */
+  public static boolean isJava1() {
+    return java1;
+  }
+
+  /**
+    * Get the Thread Context Loader which is a JDK 1.2 feature. If we
+    * are running under JDK 1.1 or anything else goes wrong the method
+    * returns <code>null<code>.
+    *
+    *  */
+  private static ClassLoader getTCL() {
+    return Thread.currentThread().getContextClassLoader();
+  }
+
+  /**
+   * If running under JDK 1.2 load the specified class using the
+   *  <code>Thread</code> <code>contextClassLoader</code> if that
+   *  fails try Class.forname. Under JDK 1.1 only Class.forName is
+   *  used.
+   *
+   */
+  public static Class loadClass(String clazz) throws ClassNotFoundException {
+    // Just call Class.forName(clazz) if we are running under JDK 1.1
+    // or if we are instructed to ignore the TCL.
+    if (java1 || ignoreTCL) {
+      return Class.forName(clazz);
+    } else {
+      try {
+        return getTCL().loadClass(clazz);
+      } catch (Throwable e) {
+        // we reached here because tcl was null or because of a
+        // security exception, or because clazz could not be loaded...
+        // In any case we now try one more time
+        return Class.forName(clazz);
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/LogLog.java b/src/main/java/org/apache/log4j/helpers/LogLog.java
new file mode 100644
index 0000000..3c83a29
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/LogLog.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+
+/**This class used to output log statements from within the log4j package.
+
+   <p>Log4j components cannot make log4j logging calls. However, it is
+   sometimes useful for the user to learn about what log4j is
+   doing. You can enable log4j internal logging by defining the
+   <b>log4j.configDebug</b> variable.
+
+   <p>All log4j internal debug calls go to <code>System.out</code>
+   where as internal error messages are sent to
+   <code>System.err</code>. All internal messages are prepended with
+   the string "log4j: ".
+
+   @since 0.8.2
+   @author Ceki G&uuml;lc&uuml;
+*/
+public class LogLog {
+  /**
+     Defining this value makes log4j print log4j-internal debug
+     statements to <code>System.out</code>.
+     
+    <p> The value of this string is <b>log4j.debug</b>.
+    
+    <p>Note that the search for all option names is case sensitive.  */
+  public static final String DEBUG_KEY="log4j.debug";
+
+ 
+  /**
+     Defining this value makes log4j components print log4j-internal
+     debug statements to <code>System.out</code>.
+     
+    <p> The value of this string is <b>log4j.configDebug</b>.
+    
+    <p>Note that the search for all option names is case sensitive.  
+
+    @deprecated Use {@link #DEBUG_KEY} instead.
+  */
+  public static final String CONFIG_DEBUG_KEY="log4j.configDebug";
+
+  /**
+     Defining this value makes log4j print log4j-internal debug
+     statements to <code>System.out</code>.
+
+    <p> The value of this string is <b>log4j.debug</b>.
+
+    <p>Note that the search for all option names is case sensitive.  */
+  public static final String CORE_DEBUG_KEY = "log4j.coreDebug";
+
+  protected static boolean debugEnabled = false;
+
+  private static final String PREFIX = "log4j: ";
+  private static final String ERR_PREFIX = "log4j:ERROR ";
+  private static final String INFO_PREFIX = "log4j:INFO ";
+  private static final String WARN_PREFIX = "log4j:WARN ";
+
+  static {
+    String key = OptionConverter.getSystemProperty(CORE_DEBUG_KEY, null);
+
+    if (key != null) {
+      debugEnabled = OptionConverter.toBoolean(key, true);
+    }
+  }
+
+  /**
+     Allows to enable/disable log4j internal logging.
+   */
+  public static void setInternalDebugging(boolean enabled) {
+    debugEnabled = enabled;
+  }
+
+  /**
+     This method is used to output log4j internal debug
+     statements. Output goes to <code>System.out</code>.
+  */
+  public static void debug(String msg) {
+    if (debugEnabled) {
+      System.out.println(PREFIX + msg);
+    }
+  }
+
+  public static void info(String msg) {
+    System.out.println(INFO_PREFIX + msg);
+  }
+  
+  /**
+     This method is used to output log4j internal debug
+     statements. Output goes to <code>System.out</code>.
+  */
+  public static void debug(String msg, Throwable t) {
+    if (debugEnabled) {
+      System.out.println(PREFIX + msg);
+
+      if (t != null) {
+        t.printStackTrace(System.out);
+      }
+    }
+  }
+
+  /**
+   * This method is used to output log4j internal error statements. There is no 
+   * way to disable error statements. Output goes to <code>System.err</code>.
+   * @deprecated Use {@link org.apache.log4j.Logger} instead.
+  */
+  public static void error(String msg) {
+    System.err.println(ERR_PREFIX + msg);
+  }
+
+  /**
+   * This method is used to output log4j internal error statements. There is no 
+   * way to disable error statements. Output goes to <code>System.err</code>.
+   * @deprecated Use {@link org.apache.log4j.Logger} instead.
+  **/
+  public static void error(String msg, Throwable t) {
+    System.err.println(ERR_PREFIX + msg);
+
+    if (t != null) {
+      t.printStackTrace();
+    }
+  }
+
+  /**
+   * In quite mode no LogLog generates strictly no output, not even 
+   * for errors.
+   * @param quietMode A true for not
+   * @deprecated with no replacement
+  */
+  public static void setQuietMode(boolean quietMode) {
+    // nothing to do
+  }
+
+  /**
+   * This method is used to output log4j internal warning statements. There is 
+   * no way to disable warning statements. Output goes to 
+   * <code>System.err</code>.  
+   * 
+   * @deprecated Use {@link org.apache.log4j.Logger} instead.
+   * */
+  public static void warn(String msg) {
+    System.err.println(WARN_PREFIX + msg);
+  }
+
+  /**
+   * This method is used to output log4j internal warnings. There is no way to 
+   * disable warning statements.  Output goes to <code>System.err</code>. 
+   * 
+   * @deprecated Use {@link org.apache.log4j.Logger} instead.
+   *  */
+  public static void warn(String msg, Throwable t) {
+    System.err.println(WARN_PREFIX + msg);
+
+    if (t != null) {
+      t.printStackTrace();
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/LoggerTraverse.java b/src/main/java/org/apache/log4j/helpers/LoggerTraverse.java
new file mode 100644
index 0000000..6661401
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/LoggerTraverse.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggerRepository;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+  This helper class can be used to extract/traverse logger information
+  for a given LoggerRepository.  It is a work in progress and focus to
+  date has been functionality, not performance or efficiency.
+
+  The set of loggers can be retrieved in one of two ways:
+
+  1) getLoggerNames() - A complete list of the loggers
+  2) getLoggerPackageNames() - A list of package names, starting at a
+     given package name pattern.
+
+  If the second retrieval method is used, the caller can iteratively call
+  the LoggerTraverse to retrieve sub-packages and children.
+
+  This class is dependent on logger names that match Java fully qualified
+  class names.
+
+  It also provides methods for querying the current level of a given
+  logger and
+
+  NOTE: This class does not cause any side effects in the LoggerRepository.
+  It does not inadvertantly create new Loggers in the process of parsing the
+  package names or accessing information.
+
+  NOTE: This class does not automatically keep track of changes in the given
+  LoggerRepository.  The caller must call the update() method to get the
+  current set of loggers.
+
+  @author Mark Womack <mwomack@apache.org>
+*/
+public class LoggerTraverse {
+  /** A map of all the loggers in the LoggerRepository. */
+  private Map loggerMap = new TreeMap();
+  
+  /** A reference to the root logger of the LoggerRepository. */
+  private Logger rootLogger;
+
+  /**
+    Empty constructor. */
+  public LoggerTraverse() {
+  }
+
+  /**
+    @param repository The LoggerRepository to traverse. */
+  public LoggerTraverse(LoggerRepository repository) {
+    update(repository);
+  }
+
+  /**
+    Updates the LoggerTraverse to the current information in the given
+    LoggerRepository.
+
+    @param repository LoggerRepository to use for Logger information. */
+  public void update(LoggerRepository repository) {
+    // clear any old information
+    loggerMap.clear();
+
+    // set the root logger
+    rootLogger = repository.getRootLogger();
+
+    // enumerate through the current set of loggers
+    // the key is the logger name, the value is the logger
+    Enumeration loggerEnum = repository.getCurrentLoggers();
+
+    while (loggerEnum.hasMoreElements()) {
+      Logger logger = (Logger) loggerEnum.nextElement();
+      loggerMap.put(logger.getName(), logger);
+    }
+  }
+
+  /**
+    Returns the list of all loggers, sorted by name.
+
+    @return List List of the current loggers. */
+  public List getLoggerNames() {
+    List loggerList = new ArrayList(loggerMap.size());
+    Iterator loggerIter = loggerMap.keySet().iterator();
+
+    while (loggerIter.hasNext()) {
+      loggerList.add((String) loggerIter.next());
+    }
+
+    return loggerList;
+  }
+
+  /**
+    Using a starting name pattern, returns the next level of package names
+    that start with that pattern.  Returns a list, as there can be more than
+    one return value.  Passing in an empty string for the starting pattern
+    will return a list of the top level package names.
+
+    For example, if the following logger names were defined: org.apache.log4j
+    and org.apache.log4j-extensions, then passing in an empty string would
+    return one item in the list with a value of "org".  If the pattern
+    "org.apache" were passed in, then the list would contain two items,
+    "log4j" and "log4j-extensions".
+
+    @param startPattern The name pattern to match for Logger name.
+    @return List List of matching Logger names that start with the pattern. */
+  public List getLoggerPackageNames(String startPattern) {
+    String name = "";
+    List packageList = new ArrayList(1);
+
+    // iterate through the loggerMap, checking the name of each logger
+    // against the starting pattern.  If name starts with pattern, then
+    // add the next part of the package name to the return list.
+    Iterator loggerIter = loggerMap.keySet().iterator();
+
+    while (loggerIter.hasNext()) {
+      String loggerName = (String) loggerIter.next();
+
+      // does the logger name start with the startPattern
+      if (loggerName.startsWith(startPattern)) {
+        loggerName = loggerName.substring(startPattern.length());
+
+        // is there part of the name left after the start pattern is removed?
+        if (loggerName.length() > 0) {
+          // if the left over string starts with '.'. remove it
+          if (loggerName.startsWith(".")) {
+            loggerName = loggerName.substring(1);
+          } else if (startPattern.length() > 0) {
+            break;
+          }
+
+          // find the next index of '.' and grab the part of the name before it
+          int index = loggerName.indexOf('.');
+
+          if (index != -1) {
+            //System.out.println("found . at " + index);
+            loggerName = loggerName.substring(0, index);
+          }
+
+          // if this is not a name we have previously encountered,
+          // put it in the return list.
+          if (!loggerName.equals(name)) {
+            packageList.add(loggerName);
+            name = loggerName;
+          }
+        }
+      }
+    }
+
+    return packageList;
+  }
+
+  /**
+    Returns true if the given package name appears to have sub-package.
+
+    @param startPattern The name pattern to match for Logger name.
+    @return boolean True if there are existing loggers that match. */
+  public boolean loggerHasSubPackages(String startPattern) {
+    int len = startPattern.length();
+
+    // iterate through logger names and first one that starts with
+    // pattern and the length is greater, return true.
+    Iterator loggerIter = loggerMap.keySet().iterator();
+
+    while (loggerIter.hasNext()) {
+      String loggerName = (String) loggerIter.next();
+
+      if (loggerName.startsWith(startPattern) && (loggerName.length() > len)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+    Returns the level for the root logger.
+
+    @return Level The current Level for the root logger. */
+  public Level getLevelForRootLogger() {
+    return rootLogger.getEffectiveLevel();
+  }
+
+  /**
+    Returns the effective level for the given package name. If no level is
+    set for the given package, then search back through the package names
+    until one is found that is set or return the level of the root logger.
+
+    @param packageName The name of the logger to return the level for.
+    @return Level The level of the logger. */
+  public Level getLevelForPackage(String packageName) {
+    String name = packageName;
+    Logger logger = (Logger) loggerMap.get(packageName);
+
+    while ((logger == null) && (name != null)) {
+      int index = name.lastIndexOf('.');
+
+      if (index != -1) {
+        name = name.substring(0, index - 1);
+        logger = (Logger) loggerMap.get(packageName);
+      } else {
+        name = null;
+      }
+    }
+
+    if (logger != null) {
+      return logger.getEffectiveLevel();
+    } else {
+      return rootLogger.getEffectiveLevel();
+    }
+  }
+
+  /**
+    Returns true of the package has had its level set directly or
+    false if the level is inherited.
+
+    @param packageName The name of the logger to return the level for.
+    @return boolean True if the level has been explicitly configured. */
+  public boolean getLevelIsSetForPackage(String packageName) {
+    String name = packageName;
+    Logger logger = (Logger) loggerMap.get(packageName);
+
+    while ((logger == null) && (name != null)) {
+      int index = name.lastIndexOf('.');
+
+      if (index != -1) {
+        name = name.substring(0, index - 1);
+        logger = (Logger) loggerMap.get(packageName);
+      } else {
+        name = null;
+      }
+    }
+
+    if (logger != null) {
+      if (logger == rootLogger) {
+        return true;
+      } else {
+        return (logger.getLevel() != null);
+      }
+    } else {
+      return false;
+    }
+  }
+
+  /**
+    here is an example of using the hierarchical version, iterating
+    through all the package names, all the loggers.
+    
+    @param args Parameters for main execution. */
+  public static void main(String[] args) {
+    // set the root to level warn
+    Logger.getRootLogger().setLevel(Level.ERROR);
+
+    // create some loggers
+    Logger.getLogger("org.womacknet.wgntool.Researcher");
+    Logger.getLogger("org.womacknet.wgntool.ResearcherList");
+    Logger.getLogger("org.womacknet.wgntool").setLevel(Level.WARN);
+    Logger.getLogger("org.womacknet.util.NameUtil");
+    Logger.getLogger("com.widgets_r_us.util.StringUtil").setLevel(Level.DEBUG);
+
+    LoggerTraverse info = new LoggerTraverse(LogManager.getLoggerRepository());
+    System.out.println("NOTE: '*' indicates the level has not been "
+      + "explicitly configured for that logger");
+    System.out.println("root - " + info.getLevelForRootLogger());
+    iteratePackages("", 1, info);
+  }
+
+  /**
+    Starting with a package name, iterate through all subpackages and loggers.
+    
+    @param startPackageName The logger name to start iterating from.
+    @param level The indentation value for display of logger names.
+    @param info The TraverseInfo instance to iterate. */
+  static void iteratePackages(
+    String startPackageName, int level, LoggerTraverse info) {
+    List packageInfo = info.getLoggerPackageNames(startPackageName);
+    Iterator iter = packageInfo.iterator();
+
+    while (iter.hasNext()) {
+      String packageName = (String) iter.next();
+
+      for (int x = 0; x < level; x++) {
+        System.out.print(" ");
+      }
+
+      System.out.print(packageName);
+
+      String subpackageName;
+
+      if (startPackageName.length() > 0) {
+        subpackageName = startPackageName + "." + packageName;
+      } else {
+        subpackageName = packageName;
+      }
+
+      System.out.print(" - " + info.getLevelForPackage(subpackageName));
+      System.out.println(
+        (info.getLevelIsSetForPackage(subpackageName) ? "" : "*"));
+
+      if (info.loggerHasSubPackages(subpackageName)) {
+        iteratePackages(subpackageName, level + 1, info);
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/MessageFormatter.java b/src/main/java/org/apache/log4j/helpers/MessageFormatter.java
new file mode 100644
index 0000000..88e4013
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/MessageFormatter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 1999,2005 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+
+/**
+ * Formats messages according to very simple rules. 
+ * See {@link #format(String, Object)} and 
+ * {@link #format(String, Object, Object)} for more details.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class MessageFormatter {
+  static final char DELIM_START = '{';
+  static final char DELIM_STOP = '}';
+
+  /**
+   * Performs single argument substitution for the 'messagePattern' passed as
+   * parameter.
+   * <p>
+   * For example, <code>MessageFormatter.format("Hi {}.", "there");</code> will
+   * return the string "Hi there.".
+   * <p>
+   * The {} pair is called the formatting element. It serves to designate the
+   * location where the argument needs to be inserted within the pattern.
+   * 
+   * @param messagePattern The message pattern which will be parsed and formatted
+   * @param argument The argument to be inserted instead of the formatting element
+   * @return The formatted message
+   */
+  public static String format(String messagePattern, Object argument) {
+    int j = messagePattern.indexOf(DELIM_START);
+    int len = messagePattern.length();
+    char escape = 'x';
+
+    // if there are no { characters or { is the last character of the messsage
+    // then we just return messagePattern
+    if (j == -1 || (j+1 == len)) {
+      return messagePattern;
+    } else {
+      if(j+1 == len) {
+      }
+      
+      char delimStop = messagePattern.charAt(j + 1);
+      if (j > 0) {
+        escape = messagePattern.charAt(j - 1);
+      }
+      if ((delimStop != DELIM_STOP) || (escape == '\\')) {
+        // invalid DELIM_START/DELIM_STOP pair or espace character is
+        // present
+        return messagePattern;
+      } else {
+        StringBuffer sbuf = new StringBuffer(len + 20);
+        sbuf.append(messagePattern.substring(0, j));
+        sbuf.append(argument);
+        sbuf.append(messagePattern.substring(j + 2));
+        return sbuf.toString();
+      }
+    }
+  }
+
+  /**
+   * /**
+   * Performs a two argument substitution for the 'messagePattern' passed as
+   * parameter.
+   * <p>
+   * For example, <code>MessageFormatter.format("Hi {}. My name is {}.", 
+   * "there", "David");</code> will return the string "Hi there. My name is David.".
+   * <p>
+   * The '{}' pair is called a formatting element. It serves to designate the
+   * location where the arguments need to be inserted within the message pattern.
+   * 
+   * @param messagePattern The message pattern which will be parsed and formatted
+   * @param arg1 The first argument to replace the first formatting element
+   * @param arg2 The second argument to replace the second formatting element
+   * @return The formatted message
+   */
+  public static String format(String messagePattern, Object arg1, Object arg2) {
+    int i = 0;
+    int len = messagePattern.length();
+    int j = messagePattern.indexOf(DELIM_START);
+
+    StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
+
+    for (int L = 0; L < 2; L++) {
+      j = messagePattern.indexOf(DELIM_START, i);
+
+      if (j == -1 || (j+1 == len)) {
+        // no more variables
+        if (i == 0) { // this is a simple string
+          return messagePattern;
+        } else { // add the tail string which contains no variables and return the result.
+          sbuf.append(messagePattern.substring(i, messagePattern.length()));
+          return sbuf.toString();
+        }
+      } else {
+        char delimStop = messagePattern.charAt(j + 1);
+        if ((delimStop != DELIM_STOP)) {
+          // invalid DELIM_START/DELIM_STOP pair
+          sbuf.append(messagePattern.substring(i, messagePattern.length()));
+          return sbuf.toString();
+        }
+        sbuf.append(messagePattern.substring(i, j));
+        sbuf.append((L == 0) ? arg1 : arg2);
+        i = j + 2;
+      }
+    }
+    // append the characters following the second {} pair.
+    sbuf.append(messagePattern.substring(i, messagePattern.length()));
+    return sbuf.toString();
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/NullEnumeration.java b/src/main/java/org/apache/log4j/helpers/NullEnumeration.java
new file mode 100644
index 0000000..3daf8c2
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/NullEnumeration.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+   
+  An always-empty Enumerator.
+
+  @author Anders Kristensen
+  @since version 1.0
+ */
+public class NullEnumeration implements Enumeration {
+  private static final NullEnumeration instance = new NullEnumeration();
+  
+  private
+  NullEnumeration() {
+  }
+  
+  public static NullEnumeration getInstance() {
+    return instance;
+  }
+  
+  public
+  boolean hasMoreElements() {
+    return false;
+  }
+  
+  public
+  Object nextElement() {
+    throw new NoSuchElementException();
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java b/src/main/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java
new file mode 100644
index 0000000..51567ca
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+   <code>ErrorHandler</code> and its implementations are no longer
+   utilized by Log4j.  The <code>ErrorHandler</code> interface and any
+   implementations of it are only here to provide binary runtime
+   compatibility with versions previous to 1.3, most specifically
+   1.2.xx versions.  All methods are NOP's.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.9.0
+   @deprecated As of 1.3
+ */
+public class OnlyOnceErrorHandler implements org.apache.log4j.spi.ErrorHandler {
+  public void setLogger(Logger logger) {}
+  public void activateOptions() {}
+  public void error(String message, Exception e, int errorCode) {}
+  public void error(String message, Exception e, int errorCode, LoggingEvent event) {}
+  public void error(String message) {}
+  public void setAppender(Appender appender) {}
+  public void setBackupAppender(Appender appender) {}
+}
diff --git a/src/main/java/org/apache/log4j/helpers/Option.java b/src/main/java/org/apache/log4j/helpers/Option.java
new file mode 100644
index 0000000..d53fbc5
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/Option.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+/**
+ *
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public class Option {
+  static final String EMPTY_STR = "";
+
+  public static boolean isEmpty(String val) {
+    return ((val == null) || EMPTY_STR.equals(val));
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/OptionConverter.java b/src/main/java/org/apache/log4j/helpers/OptionConverter.java
new file mode 100644
index 0000000..499696e
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/OptionConverter.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.config.ConfiguratorBase;
+import org.apache.log4j.joran.JoranConfigurator;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.LoggerRepository;
+
+import java.net.URL;
+
+import java.util.Properties;
+
+
+// Contributors:   Avy Sharell 
+//                 Matthieu Verbert
+//                 Colin Sampaleanu
+
+// Contributors:   Avy Sharell 
+//                 Matthieu Verbert
+//                 Colin Sampaleanu
+
+/**
+ * A convenience class to convert property values to specific types.
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Simon Kitching;
+ * @author Anders Kristensen
+ * @author Avy Sharell
+*/
+public class OptionConverter  {
+  static String DELIM_START = "${";
+  static char DELIM_STOP = '}';
+  static int DELIM_START_LEN = 2;
+  static int DELIM_STOP_LEN = 1;
+
+  // TODO: this method should be removed if OptionConverter becomes a static
+  static Logger getLogger() {
+    return LogManager.getLogger(OptionConverter.class);
+  }
+
+  // TODO: this method should be removed if OptionConverter becomes totally static
+  public static void setLoggerRepository(LoggerRepository lr) {
+    
+  }
+
+  
+  public static String[] concatanateArrays(String[] l, String[] r) {
+    int len = l.length + r.length;
+    String[] a = new String[len];
+
+    System.arraycopy(l, 0, a, 0, l.length);
+    System.arraycopy(r, 0, a, l.length, r.length);
+
+    return a;
+  }
+
+  public static String convertSpecialChars(String s) {
+    char c;
+    int len = s.length();
+    StringBuffer sbuf = new StringBuffer(len);
+
+    int i = 0;
+
+    while (i < len) {
+      c = s.charAt(i++);
+
+      if (c == '\\') {
+        c = s.charAt(i++);
+
+        if (c == 'n') {
+          c = '\n';
+        } else if (c == 'r') {
+          c = '\r';
+        } else if (c == 't') {
+          c = '\t';
+        } else if (c == 'f') {
+          c = '\f';
+        } else if (c == '\b') {
+          c = '\b';
+        } else if (c == '\"') {
+          c = '\"';
+        } else if (c == '\'') {
+          c = '\'';
+        } else if (c == '\\') {
+          c = '\\';
+        }
+      }
+
+      sbuf.append(c);
+    }
+
+    return sbuf.toString();
+  }
+
+  /**
+     Very similar to <code>System.getProperty</code> except
+     that the {@link SecurityException} is hidden.
+
+     @param key The key to search for.
+     @param def The default value to return.
+     @return the string value of the system property, or the default
+     value if there is no property with that key.
+
+     @since 1.1 */
+  public static String getSystemProperty(String key, String def) {
+    try {
+      return System.getProperty(key, def);
+    } catch (Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
+      return def;
+    }
+  }
+
+  public static Object instantiateByKey(
+    Properties props, String key, Class superClass, Object defaultValue) {
+    // Get the value of the property in string form
+    String className = findAndSubst(key, props);
+
+    if (className == null) {
+      getLogger().error("Could not find value for key {}", key);
+
+      return defaultValue;
+    }
+
+    // Trim className to avoid trailing spaces that cause problems.
+    return instantiateByClassName(className.trim(), superClass, defaultValue);
+  }
+
+  /**
+     If <code>value</code> is "true", then <code>true</code> is
+     returned. If <code>value</code> is "false", then
+     <code>true</code> is returned. Otherwise, <code>default</code> is
+     returned.
+
+     <p>Case of value is unimportant.  */
+  public static boolean toBoolean(String value, boolean dEfault) {
+    if (value == null) {
+      return dEfault;
+    }
+
+    String trimmedVal = value.trim();
+
+    if ("true".equalsIgnoreCase(trimmedVal)) {
+      return true;
+    }
+
+    if ("false".equalsIgnoreCase(trimmedVal)) {
+      return false;
+    }
+
+    return dEfault;
+  }
+
+  public static int toInt(String value, int dEfault) {
+    if (value != null) {
+      String s = value.trim();
+
+      try {
+        return Integer.valueOf(s).intValue();
+      } catch (NumberFormatException e) {
+        getLogger().error("[{}] is not in proper int form.", s);
+      }
+    }
+
+    return dEfault;
+  }
+
+  /**
+     Converts a standard or custom priority level to a Level
+     object.  <p> If <code>value</code> is of form
+     "level#classname", then the specified class' toLevel method
+     is called to process the specified level string; if no '#'
+     character is present, then the default {@link org.apache.log4j.Level}
+     class is used to process the level value.
+
+     <p>As a special case, if the <code>value</code> parameter is
+     equal to the string "NULL", then the value <code>null</code> will
+     be returned.
+
+     <p> If any error occurs while converting the value to a level,
+     the <code>defaultValue</code> parameter, which may be
+     <code>null</code>, is returned.
+
+     <p> Case of <code>value</code> is insignificant for the level level, but is
+     significant for the class name part, if present.
+
+     @since 1.1 */
+  public static Level toLevel(String value, Level defaultValue) {
+    if (value == null) {
+      return defaultValue;
+    }
+    
+    value = value.trim();
+    int hashIndex = value.indexOf('#');
+
+    if (hashIndex == -1) {
+      if ("NULL".equalsIgnoreCase(value)) {
+        return null;
+      } else {
+        // no class name specified : use standard Level class
+        return (Level) Level.toLevel(value, defaultValue);
+      }
+    }
+
+    Level result = defaultValue;
+
+    String clazz = value.substring(hashIndex + 1);
+    String levelName = value.substring(0, hashIndex);
+
+    // This is degenerate case but you never know.
+    if ("NULL".equalsIgnoreCase(levelName)) {
+      return null;
+    }
+
+    try {
+      Class customLevel = Loader.loadClass(clazz);
+
+      // get a ref to the specified class' static method
+      // toLevel(String, org.apache.log4j.Level)
+      Class[] paramTypes =
+        new Class[] { String.class, org.apache.log4j.Level.class };
+      java.lang.reflect.Method toLevelMethod =
+        customLevel.getMethod("toLevel", paramTypes);
+
+      // now call the toLevel method, passing level string + default
+      Object[] params = new Object[] { levelName, defaultValue };
+      Object o = toLevelMethod.invoke(null, params);
+
+      result = (Level) o;
+    } catch (ClassNotFoundException e) {
+      getLogger().warn("custom level class [" + clazz + "] not found.");
+    } catch (NoSuchMethodException e) {
+      getLogger().warn(
+        "custom level class [" + clazz + "]"
+        + " does not have a class function toLevel(String, Level)", e);
+    } catch (java.lang.reflect.InvocationTargetException e) {
+      getLogger().warn(
+        "custom level class [" + clazz + "]" + " could not be instantiated", e);
+    } catch (ClassCastException e) {
+      getLogger().warn(
+        "class [" + clazz + "] is not a subclass of org.apache.log4j.Level", e);
+    } catch (IllegalAccessException e) {
+      getLogger().warn(
+        "class [" + clazz
+        + "] cannot be instantiated due to access restrictions", e);
+    } catch (Exception e) {
+      getLogger().warn(
+        "class [" + clazz + "], level [" + levelName + "] conversion failed.",
+        e);
+    }
+
+    return result;
+  }
+
+  public static long toFileSize(String value, long dEfault) {
+    if (value == null) {
+      return dEfault;
+    }
+
+    String s = value.trim().toUpperCase();
+    long multiplier = 1;
+    int index;
+
+    if ((index = s.indexOf("KB")) != -1) {
+      multiplier = 1024;
+      s = s.substring(0, index);
+    } else if ((index = s.indexOf("MB")) != -1) {
+      multiplier = 1024 * 1024;
+      s = s.substring(0, index);
+    } else if ((index = s.indexOf("GB")) != -1) {
+      multiplier = 1024 * 1024 * 1024;
+      s = s.substring(0, index);
+    }
+
+    if (s != null) {
+      try {
+        return Long.valueOf(s).longValue() * multiplier;
+      } catch (NumberFormatException e) {
+        getLogger().error("[{}] is not in proper int form.", s);
+        getLogger().error("[" + value + "] not in expected format.", e);
+      }
+    }
+
+    return dEfault;
+  }
+
+  /**
+     Find the value corresponding to <code>key</code> in
+     <code>props</code>. Then perform variable substitution on the
+     found value.
+
+  */
+  public static String findAndSubst(String key, Properties props) {
+    String value = props.getProperty(key);
+
+    if (value == null) {
+      return null;
+    }
+
+    try {
+      return substVars(value, props);
+    } catch (IllegalArgumentException e) {
+      getLogger().error("Bad option value [" + value + "].", e);
+
+      return value;
+    }
+  }
+
+  /**
+     Instantiate an object given a class name. Check that the
+     <code>className</code> is a subclass of
+     <code>superClass</code>. If that test fails or the object could
+     not be instantiated, then <code>defaultValue</code> is returned.
+  
+     @param className The fully qualified class name of the object to instantiate.
+     @param superClass The class to which the new object should belong.
+     @param defaultValue The object to return in case of non-fulfillment
+   */
+  public static Object instantiateByClassName(
+    String className, Class superClass, Object defaultValue) {
+    if (className != null) {
+      try {
+        Class classObj = Loader.loadClass(className);
+  
+        if (!superClass.isAssignableFrom(classObj)) {
+          getLogger().error(
+            "A \"" + className + "\" object is not assignable to a \""
+            + superClass.getName() + "\" variable.");
+          getLogger().error(
+            "The class \"" + superClass.getName() + "\" was loaded by ");
+          getLogger().error(
+            "[" + superClass.getClassLoader() + "] whereas object of type ");
+          getLogger().error(
+            "\"" + classObj.getName() + "\" was loaded by ["
+            + classObj.getClassLoader() + "].");
+  
+          return defaultValue;
+        }
+
+        //System.out.println("About to call classObj.newInstance(), "+classObj.getName());
+        
+        return classObj.newInstance();
+      } catch(NoClassDefFoundError ncfe) {
+        getLogger().error("Could not instantiate object of class [" + className + "].", ncfe);
+      } catch (Throwable e) {
+        getLogger().error("Could not instantiate object of class [" + className + "].", e);
+      }
+    }
+  
+    return defaultValue;
+  }
+
+  /**
+   * Perform variable substitution in string <code>val</code> from the values of
+   * keys found the properties passed as parameter or in the system propeties.
+   * 
+   * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
+   *
+   * <p>For example, if the properties parameter contains a property "key1" set
+   * as "value1", then the call <pre>
+   *  String s = OptionConverter.substituteVars("Value of key is ${key1}.");</pre>
+   * will set the variable <code>s</code> to "Value of key is value1.".
+   * 
+   * <p>If no value could be found for the specified key, then the 
+   * system properties are searched, if the value could not be found 
+   * there, then substitution defaults to the empty string.
+   * 
+   * <p>For example, if system propeties contains no value for the key 
+   * "inexistentKey", then the call
+   * <pre>
+   * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]"); </pre>
+   * will set <code>s</code> to "Value of inexistentKey is []".
+   * 
+   * <p>Nevertheless, it is possible to specify a default substitution value 
+   * using the ":-" operator. For example, the call
+   * <pre>
+   * String s = OptionConverter.subsVars("Value of key is [${key2:-val2}]");</pre>
+   *  will set <code>s</code> to "Value of key is [val2]" even if the "key2"
+   * property is unset.
+   * 
+   * <p>An {@link java.lang.IllegalArgumentException} is thrown if 
+   * <code>val</code> contains a start delimeter "${" which is not 
+   * balanced by a stop delimeter "}". </p>
+   * 
+   * @param val The string on which variable substitution is performed.
+   * @throws IllegalArgumentException if <code>val</code> is malformed.
+  */
+  public static String substVars(String val, Properties props) {
+    
+    StringBuffer sbuf = new StringBuffer();
+
+    int i = 0;
+    int j;
+    int k;
+
+    while (true) {
+      j = val.indexOf(DELIM_START, i);
+
+      if (j == -1) {
+        // no more variables
+        if (i == 0) { // this is a simple string
+
+          return val;
+        } else { // add the tail string which contails no variables and return the result.
+          sbuf.append(val.substring(i, val.length()));
+
+          return sbuf.toString();
+        }
+      } else {
+        sbuf.append(val.substring(i, j));
+        k = val.indexOf(DELIM_STOP, j);
+
+        if (k == -1) {
+          throw new IllegalArgumentException(
+            '"' + val + "\" has no closing brace. Opening brace at position "
+            + j + '.');
+        } else {
+          j += DELIM_START_LEN;
+          
+          String rawKey = val.substring(j, k);
+          
+          // Massage the key to extract a default replacement if there is one
+          String[] extracted = extractDefaultReplacement(rawKey);
+          String key = extracted[0];
+          String defaultReplacement = extracted[1]; // can be null
+          
+					String replacement = null;
+				  
+					// first try the props passed as parameter					
+				  if(props != null) {
+						replacement = props.getProperty(key);				   
+				  }
+				  
+          // then try in System properties
+          if (replacement == null) {
+						replacement = getSystemProperty(key, null);
+          }
+
+          // if replacement is still null, use the defaultReplacement which
+          // still be null
+          if(replacement == null) {
+            replacement = defaultReplacement;
+          }
+          
+          if (replacement != null) {
+            // Do variable substitution on the replacement string
+            // such that we can solve "Hello ${x2}" as "Hello p1" 
+            // where the properties are
+            // x1=p1
+            // x2=${x1}
+            String recursiveReplacement = substVars(replacement, props);
+            sbuf.append(recursiveReplacement);
+          }
+
+          i = k + DELIM_STOP_LEN;
+        }
+      }
+    }
+  }
+
+  static public String[] extractDefaultReplacement(String key) {
+    String[] result = new String[2];
+    result[0] = key;
+    int d = key.indexOf(":-");
+    if(d != -1) {
+      result[0] = key.substring(0, d);
+      result[1] = key.substring(d+2);
+    }
+    return result;
+  }
+  
+  /**
+   * Replaces double backslashes (except the leading doubles in UNC's)
+   * with single backslashes for compatibility with existing path specifications
+   * that were working around use of OptionConverter.convertSpecialChars
+   * in XML configuration files.
+   * 
+   * @param src source string
+   * @return source string with double backslashes replaced
+   * 
+   * @since 1.3
+   */
+  public static String stripDuplicateBackslashes(final String src) {
+    int i = src.lastIndexOf('\\');
+    if (i > 0) {
+      StringBuffer buf = new StringBuffer(src);
+      for(; i > 0; i = src.lastIndexOf('\\', i - 1)) {
+        //
+        //  if the preceding character is a slash then
+        //     remove the preceding character
+        //     and continue processing with the earlier part of the string
+        if(src.charAt(i - 1) == '\\') {
+          buf.deleteCharAt(i);
+          i--;
+          if (i == 0) break;
+        } else {
+          //
+          //  if there was a single slash then
+          //    the string was not trying to work around
+          //    convertSpecialChars
+          //
+          return src;
+        }
+      }
+      return buf.toString();
+    }
+    return src;
+  }
+  
+  /**
+     Configure log4j given a URL.
+
+     <p>The url must point to a file or resource which will be interpreted by
+     a new instance of a log4j configurator.
+
+     <p>All configurations steps are taken on the
+     <code>hierarchy</code> passed as a parameter.
+
+     <p>
+     @param url The location of the configuration file or resource.
+     @param clazz The classname, of the log4j configurator which will parse
+     the file or resource at <code>url</code>. This must be a subclass of
+     {@link Configurator}, or null. If this value is null then a default
+     configurator of {@link PropertyConfigurator} is used, unless the
+     filename pointed to by <code>url</code> ends in '.xml', in which case
+     {@link org.apache.log4j.xml.DOMConfigurator} is used.
+     @param repository The {@link LoggerRepository} to act on.
+
+     @since 1.1.4 */
+  public static void selectAndConfigure(
+    URL url, String clazz, LoggerRepository repository) {
+    Configurator configurator = null;
+    String filename = url.getFile();
+
+    if ((clazz == null) && (filename != null) && filename.endsWith(".xml")) {
+      clazz = JoranConfigurator.class.getName();
+    }
+
+    if (clazz != null) {
+      Logger logger = repository.getLogger(OptionConverter.class.getName());
+      logger.info("Preferred configurator class: " + clazz);
+
+      configurator =
+        (Configurator) instantiateByClassName(clazz, Configurator.class, null);
+
+      if (configurator == null) {
+        logger.error("Could not instantiate configurator [" + clazz + "].");
+        
+        return;
+      }
+    } else {
+      configurator = new PropertyConfigurator();
+    }
+    
+    configurator.doConfigure(url, repository);
+    if(configurator instanceof ConfiguratorBase) {
+      ((ConfiguratorBase)configurator).dumpErrors();
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/PatternConverter.java b/src/main/java/org/apache/log4j/helpers/PatternConverter.java
new file mode 100644
index 0000000..e2ffd44
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/PatternConverter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+
+   <p>PatternConverter is an abtract class that provides the
+   formatting functionality that derived classes need.
+
+   <p>Conversion specifiers in a conversion patterns are parsed to
+   individual PatternConverters. Each of which is responsible for
+   converting a logging event in a converter specific manner.
+
+   @author <a href="mailto:cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.8.2
+   @deprecated Since 1.3
+ */
+public abstract class PatternConverter {
+  public PatternConverter next;
+  int min = -1;
+  int max = Integer.MAX_VALUE;
+  boolean leftAlign = false;
+
+  protected
+  PatternConverter() {  }
+  
+  protected
+  PatternConverter(FormattingInfo fi) {
+    min = fi.min;
+    max = fi.max;
+    leftAlign = fi.leftAlign;
+  }
+
+  /**
+     Derived pattern converters must override this method in order to
+     convert conversion specifiers in the correct way.
+  */
+  abstract
+  protected
+  String convert(LoggingEvent event);
+
+  /**
+     A template method for formatting in a converter specific way.
+   */
+  public
+  void format(StringBuffer sbuf, LoggingEvent e) {
+    String s = convert(e);
+
+    if(s == null) {
+      if(0 < min)
+	spacePad(sbuf, min);
+      return;
+    }
+
+    int len = s.length();
+
+    if(len > max)
+      sbuf.append(s.substring(len-max));
+    else if(len < min) {
+      if(leftAlign) {	
+	sbuf.append(s);
+	spacePad(sbuf, min-len);
+      }
+      else {
+	spacePad(sbuf, min-len);
+	sbuf.append(s);
+      }
+    }
+    else
+      sbuf.append(s);
+  }	
+
+  static String[] SPACES = {" ", "  ", "    ", "        ", //1,2,4,8 spaces
+			    "                ", // 16 spaces
+			    "                                " }; // 32 spaces
+
+  /**
+     Fast space padding method.
+  */
+  public
+  void spacePad(StringBuffer sbuf, int length) {
+    while(length >= 32) {
+      sbuf.append(SPACES[5]);
+      length -= 32;
+    }
+    
+    for(int i = 4; i >= 0; i--) {	
+      if((length & (1<<i)) != 0) {
+	sbuf.append(SPACES[i]);
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/PatternParser.java b/src/main/java/org/apache/log4j/helpers/PatternParser.java
new file mode 100644
index 0000000..734b4f7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/PatternParser.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+// Contributors:   Nelson Minar <(nelson@monkey.org>
+//                 Igor E. Poteryaev <jah@mail.ru>
+//                 Reinhard Deschler <reinhard.deschler@web.de>
+
+/**
+   Most of the work of the {@link org.apache.log4j.PatternLayout} class
+   is delegated to the PatternParser class.
+
+   <p>It is this class that parses conversion patterns and creates
+   a chained list of {@link OptionConverter OptionConverters}.
+
+   @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
+   @author Ceki G&uuml;lc&uuml;
+   @author Anders Kristensen
+
+   @since 0.8.2
+   @deprecated Since 1.3
+*/
+public class PatternParser {
+
+  private static final char ESCAPE_CHAR = '%';
+
+  private static final int LITERAL_STATE = 0;
+  private static final int CONVERTER_STATE = 1;
+  private static final int MINUS_STATE = 2;
+  private static final int DOT_STATE = 3;
+  private static final int MIN_STATE = 4;
+  private static final int MAX_STATE = 5;
+
+  static final int FULL_LOCATION_CONVERTER = 1000;
+  static final int METHOD_LOCATION_CONVERTER = 1001;
+  static final int CLASS_LOCATION_CONVERTER = 1002;
+  static final int LINE_LOCATION_CONVERTER = 1003;
+  static final int FILE_LOCATION_CONVERTER = 1004;
+
+  static final int RELATIVE_TIME_CONVERTER = 2000;
+  static final int THREAD_CONVERTER = 2001;
+  static final int LEVEL_CONVERTER = 2002;
+  static final int NDC_CONVERTER = 2003;
+  static final int MESSAGE_CONVERTER = 2004;
+
+  int state;
+  protected StringBuffer currentLiteral = new StringBuffer(32);
+  protected int patternLength;
+  protected int i;
+  PatternConverter head;
+  PatternConverter tail;
+  protected FormattingInfo formattingInfo = new FormattingInfo();
+  protected String pattern;
+
+  public
+  PatternParser(String pattern) {
+    this.pattern = pattern;
+    patternLength =  pattern.length();
+    state = LITERAL_STATE;
+  }
+
+  private
+  void  addToList(PatternConverter pc) {
+    if(head == null) {
+      head = tail = pc;
+    } else {
+      tail.next = pc;
+      tail = pc;
+    }
+  }
+
+  protected
+  String extractOption() {
+    if((i < patternLength) && (pattern.charAt(i) == '{')) {
+      int end = pattern.indexOf('}', i);
+      if (end > i) {
+	String r = pattern.substring(i + 1, end);
+	i = end+1;
+	return r;
+      }
+    }
+    return null;
+  }
+
+
+  /**
+     The option is expected to be in decimal and positive. In case of
+     error, zero is returned.  */
+  protected
+  int extractPrecisionOption() {
+    String opt = extractOption();
+    int r = 0;
+    if(opt != null) {
+      try {
+	r = Integer.parseInt(opt);
+	if(r <= 0) {
+	    LogLog.error(
+	        "Precision option (" + opt + ") isn't a positive integer.");
+	    r = 0;
+	}
+      }
+      catch (NumberFormatException e) {
+	LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
+      }
+    }
+    return r;
+  }
+
+  public
+  PatternConverter parse() {
+    char c;
+    i = 0;
+    while(i < patternLength) {
+      c = pattern.charAt(i++);
+      switch(state) {
+      case LITERAL_STATE:
+        // In literal state, the last char is always a literal.
+        if(i == patternLength) {
+          currentLiteral.append(c);
+          continue;
+        }
+        if(c == ESCAPE_CHAR) {
+          // peek at the next char.
+          switch(pattern.charAt(i)) {
+          case ESCAPE_CHAR:
+            currentLiteral.append(c);
+            i++; // move pointer
+            break;
+          case 'n':
+            currentLiteral.append(Layout.LINE_SEP);
+            i++; // move pointer
+            break;
+          default:
+            if(currentLiteral.length() != 0) {
+              addToList(new LiteralPatternConverter(
+                                                  currentLiteral.toString()));
+              //LogLog.debug("Parsed LITERAL converter: \""
+              //           +currentLiteral+"\".");
+            }
+            currentLiteral.setLength(0);
+            currentLiteral.append(c); // append %
+            state = CONVERTER_STATE;
+            formattingInfo.reset();
+          }
+        }
+        else {
+          currentLiteral.append(c);
+        }
+        break;
+      case CONVERTER_STATE:
+	currentLiteral.append(c);
+	switch(c) {
+	case '-':
+	  formattingInfo.leftAlign = true;
+	  break;
+	case '.':
+	  state = DOT_STATE;
+	  break;
+	default:
+	  if(c >= '0' && c <= '9') {
+	    formattingInfo.min = c - '0';
+	    state = MIN_STATE;
+	  }
+	  else
+	    finalizeConverter(c);
+	} // switch
+	break;
+      case MIN_STATE:
+	currentLiteral.append(c);
+	if(c >= '0' && c <= '9')
+	  formattingInfo.min = formattingInfo.min*10 + (c - '0');
+	else if(c == '.')
+	  state = DOT_STATE;
+	else {
+	  finalizeConverter(c);
+	}
+	break;
+      case DOT_STATE:
+	currentLiteral.append(c);
+	if(c >= '0' && c <= '9') {
+	  formattingInfo.max = c - '0';
+	   state = MAX_STATE;
+	}
+	else {
+	  LogLog.error("Error occured in position "+i
+		     +".\n Was expecting digit, instead got char \""+c+"\".");
+	  state = LITERAL_STATE;
+	}
+	break;
+      case MAX_STATE:
+	currentLiteral.append(c);
+	if(c >= '0' && c <= '9')
+	  formattingInfo.max = formattingInfo.max*10 + (c - '0');
+	else {
+	  finalizeConverter(c);
+	  state = LITERAL_STATE;
+	}
+	break;
+      } // switch
+    } // while
+    if(currentLiteral.length() != 0) {
+      addToList(new LiteralPatternConverter(currentLiteral.toString()));
+      //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
+    }
+    return head;
+  }
+
+  protected
+  void finalizeConverter(char c) {
+    PatternConverter pc = null;
+    switch(c) {
+    case 'c':
+      pc = new CategoryPatternConverter(formattingInfo,
+					extractPrecisionOption());
+      //LogLog.debug("CATEGORY converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'C':
+      pc = new ClassNamePatternConverter(formattingInfo,
+					 extractPrecisionOption());
+      //LogLog.debug("CLASS_NAME converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'd':
+      String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
+      DateFormat df;
+      String dOpt = extractOption();
+      if(dOpt != null)
+	dateFormatStr = dOpt;
+
+      if(dateFormatStr.equalsIgnoreCase(
+                                    AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
+	df = new  ISO8601DateFormat();
+      else if(dateFormatStr.equalsIgnoreCase(
+                                   AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
+	df = new AbsoluteTimeDateFormat();
+      else if(dateFormatStr.equalsIgnoreCase(
+                              AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
+	df = new DateTimeDateFormat();
+      else {
+	try {
+	  df = new SimpleDateFormat(dateFormatStr);
+	}
+	catch (IllegalArgumentException e) {
+	  LogLog.error("Could not instantiate SimpleDateFormat with " +
+		       dateFormatStr, e);
+	  df = (DateFormat) OptionConverter.instantiateByClassName(
+			           "org.apache.log4j.helpers.ISO8601DateFormat",
+				   DateFormat.class, null);
+	}
+      }
+      pc = new DatePatternConverter(formattingInfo, df);
+      //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'F':
+      pc = new LocationPatternConverter(formattingInfo,
+					FILE_LOCATION_CONVERTER);
+      //LogLog.debug("File name converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'l':
+      pc = new LocationPatternConverter(formattingInfo,
+					FULL_LOCATION_CONVERTER);
+      //LogLog.debug("Location converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'L':
+      pc = new LocationPatternConverter(formattingInfo,
+					LINE_LOCATION_CONVERTER);
+      //LogLog.debug("LINE NUMBER converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'm':
+      pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
+      //LogLog.debug("MESSAGE converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'M':
+      pc = new LocationPatternConverter(formattingInfo,
+					METHOD_LOCATION_CONVERTER);
+      //LogLog.debug("METHOD converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'p':
+      pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
+      //LogLog.debug("LEVEL converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 'r':
+      pc = new BasicPatternConverter(formattingInfo,
+					 RELATIVE_TIME_CONVERTER);
+      //LogLog.debug("RELATIVE time converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+    case 't':
+      pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
+      //LogLog.debug("THREAD converter.");
+      //formattingInfo.dump();
+      currentLiteral.setLength(0);
+      break;
+      /*case 'u':
+      if(i < patternLength) {
+	char cNext = pattern.charAt(i);
+	if(cNext >= '0' && cNext <= '9') {
+	  pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
+	  LogLog.debug("USER converter ["+cNext+"].");
+	  formattingInfo.dump();
+	  currentLiteral.setLength(0);
+	  i++;
+	}
+	else
+	  LogLog.error("Unexpected char" +cNext+" at position "+i);
+      }
+      break;*/
+    case 'x':
+      pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
+      //LogLog.debug("NDC converter.");
+      currentLiteral.setLength(0);
+      break;
+    case 'X':
+      String xOpt = extractOption();
+      pc = new MDCPatternConverter(formattingInfo, xOpt);
+      currentLiteral.setLength(0);
+      break;
+    default:
+      LogLog.error("Unexpected char [" +c+"] at position "+i
+		   +" in conversion patterrn.");
+      pc = new LiteralPatternConverter(currentLiteral.toString());
+      currentLiteral.setLength(0);
+    }
+
+    addConverter(pc);
+  }
+
+  protected
+  void addConverter(PatternConverter pc) {
+    currentLiteral.setLength(0);
+    // Add the pattern converter to the list.
+    addToList(pc);
+    // Next pattern is assumed to be a literal.
+    state = LITERAL_STATE;
+    // Reset formatting info
+    formattingInfo.reset();
+  }
+
+  // ---------------------------------------------------------------------
+  //                      PatternConverters
+  // ---------------------------------------------------------------------
+
+  private static class BasicPatternConverter extends PatternConverter {
+    int type;
+
+    BasicPatternConverter(FormattingInfo formattingInfo, int type) {
+      super(formattingInfo);
+      this.type = type;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      switch(type) {
+      case RELATIVE_TIME_CONVERTER:
+	return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
+      case THREAD_CONVERTER:
+	return event.getThreadName();
+      case LEVEL_CONVERTER:
+	return event.getLevel().toString();
+      case NDC_CONVERTER:
+	return event.getNDC();
+      case MESSAGE_CONVERTER: {
+	return event.getRenderedMessage();
+      }
+      default: return null;
+      }
+    }
+  }
+
+  private static class LiteralPatternConverter extends PatternConverter {
+    private String literal;
+
+    LiteralPatternConverter(String value) {
+      literal = value;
+    }
+
+    public
+    final
+    void format(StringBuffer sbuf, LoggingEvent event) {
+      sbuf.append(literal);
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      return literal;
+    }
+  }
+
+  private static class DatePatternConverter extends PatternConverter {
+    private DateFormat df;
+    private Date date;
+
+    DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
+      super(formattingInfo);
+      date = new Date();
+      this.df = df;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      date.setTime(event.timeStamp);
+      String converted = null;
+      try {
+        converted = df.format(date);
+      }
+      catch (Exception ex) {
+        LogLog.error("Error occured while converting date.", ex);
+      }
+      return converted;
+    }
+  }
+
+  private static class MDCPatternConverter extends PatternConverter {
+    private String key;
+
+    MDCPatternConverter(FormattingInfo formattingInfo, String key) {
+      super(formattingInfo);
+      this.key = key;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      Object val = event.getMDC(key);
+      if(val == null) {
+	return null;
+      } else {
+	return val.toString();
+      }
+    }
+  }
+
+
+  private static class LocationPatternConverter extends PatternConverter {
+    int type;
+
+    LocationPatternConverter(FormattingInfo formattingInfo, int type) {
+      super(formattingInfo);
+      this.type = type;
+    }
+
+    public
+    String convert(LoggingEvent event) {
+      LocationInfo locationInfo = event.getLocationInformation();
+      switch(type) {
+      case FULL_LOCATION_CONVERTER:
+	return locationInfo.fullInfo;
+      case METHOD_LOCATION_CONVERTER:
+	return locationInfo.getMethodName();
+      case LINE_LOCATION_CONVERTER:
+	return locationInfo.getLineNumber();
+      case FILE_LOCATION_CONVERTER:
+	return locationInfo.getFileName();
+      default: return null;
+      }
+    }
+  }
+
+  private static abstract class NamedPatternConverter extends PatternConverter {
+    int precision;
+
+    NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo);
+      this.precision =  precision;
+    }
+
+    abstract
+    String getFullyQualifiedName(LoggingEvent event);
+
+    public
+    String convert(LoggingEvent event) {
+      String n = getFullyQualifiedName(event);
+      if(precision <= 0)
+	return n;
+      else {
+	int len = n.length();
+
+	// We substract 1 from 'len' when assigning to 'end' to avoid out of
+	// bounds exception in return r.substring(end+1, len). This can happen if
+	// precision is 1 and the category name ends with a dot.
+	int end = len -1 ;
+	for(int i = precision; i > 0; i--) {
+	  end = n.lastIndexOf('.', end-1);
+	  if(end == -1)
+	    return n;
+	}
+	return n.substring(end+1, len);
+      }
+    }
+  }
+
+  private class ClassNamePatternConverter extends NamedPatternConverter {
+
+    ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo, precision);
+    }
+
+    String getFullyQualifiedName(LoggingEvent event) {
+      return event.getLocationInformation().getClassName();
+    }
+  }
+
+  private class CategoryPatternConverter extends NamedPatternConverter {
+
+    CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
+      super(formattingInfo, precision);
+    }
+
+    String getFullyQualifiedName(LoggingEvent event) {
+      return event.getLoggerName();
+    }
+  }
+}
+
diff --git a/src/main/java/org/apache/log4j/helpers/PlatformInfo.java b/src/main/java/org/apache/log4j/helpers/PlatformInfo.java
new file mode 100644
index 0000000..1933bf7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/PlatformInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+import java.util.Properties;
+
+
+/**
+ * This class provides information about the runtime platform.
+ *
+ * @author Ceki Gulcu
+ * @since 1.3
+ * */
+public class PlatformInfo {
+  private static final int UNINITIALIZED = -1;
+
+  // Check if we are running in IBM's visual age.
+  private static int inVisualAge = UNINITIALIZED;
+  private static int onAS400 = UNINITIALIZED;
+  private static int hasStackTraceElement = UNINITIALIZED;
+
+  public static boolean isInVisualAge() {
+    if (inVisualAge == UNINITIALIZED) {
+      try {
+        Class dummy = Class.forName("com.ibm.uvm.tools.DebugSupport");
+        inVisualAge = 1;
+      } catch (Throwable e) {
+        inVisualAge = 0;
+      }
+    }
+    return (inVisualAge == 1);
+  }
+
+  /**
+   * Are we running on AS400?
+   */
+  public static boolean isOnAS400() {
+    if (onAS400 == UNINITIALIZED) {
+      try {
+        Properties p = System.getProperties();
+        String osname = p.getProperty("os.name");
+        if ((osname != null) && (osname.equals("OS/400"))) {
+          onAS400 = 1;
+        } else {
+          onAS400 = 0;
+        }
+      } catch (Throwable e) {
+        // This should not happen, but if it does, assume we are not on
+        // AS400.
+        onAS400 = 0;
+      }
+    }
+    return (onAS400 == 1);
+  }
+
+  public static boolean hasStackTraceElement() {
+    if (hasStackTraceElement == UNINITIALIZED) {
+      try {
+        Class.forName("java.lang.StackTraceElement");
+        hasStackTraceElement = 1;
+      } catch (Throwable e) {
+        // we are running on a JDK prior to 1.4
+        hasStackTraceElement = 0;
+      }
+    }
+    return (hasStackTraceElement == 1);
+  }
+  
+  public static boolean isJDK14OrLater() {
+    return hasStackTraceElement();
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/QuietWriter.java b/src/main/java/org/apache/log4j/helpers/QuietWriter.java
new file mode 100644
index 0000000..66ef7fa
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/QuietWriter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.io.Writer;
+import java.io.FilterWriter;
+import java.io.IOException;
+import org.apache.log4j.spi.ErrorCode;
+
+
+/**
+   QuietWriter does not throw exceptions when things go
+   wrong. Instead, it delegates error handling to its {@link
+      org.apache.log4j.spi.ErrorHandler}.
+
+   @author Ceki G&uuml;lc&uuml;
+
+   @since 0.7.3
+   @deprecated
+*/
+public class QuietWriter extends FilterWriter {
+
+    /**
+     * @deprecated
+     */
+  protected org.apache.log4j.spi.ErrorHandler errorHandler;
+
+    /**
+     * @deprecated
+     * @param writer
+     * @param errorHandler
+     */
+  public
+  QuietWriter(Writer writer, org.apache.log4j.spi.ErrorHandler errorHandler) {
+    super(writer);
+    setErrorHandler(errorHandler);
+  }
+
+  public
+  void write(String string) {
+    try {
+      out.write(string);
+    } catch(IOException e) {
+      errorHandler.error("Failed to write ["+string+"].", e, 
+			 ErrorCode.WRITE_FAILURE);
+    }
+  }
+
+  public
+  void flush() {
+    try {
+      out.flush();
+    } catch(IOException e) {
+      errorHandler.error("Failed to flush writer,", e, 
+			 ErrorCode.FLUSH_FAILURE);
+    }	
+  }
+
+
+  public
+  void setErrorHandler(org.apache.log4j.spi.ErrorHandler eh) {
+    if(eh == null) {
+      // This is a programming error on the part of the enclosing appender.
+      throw new IllegalArgumentException("Attempted to set null ErrorHandler.");
+    } else { 
+      this.errorHandler = eh;
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/ReaderWriterLock.java b/src/main/java/org/apache/log4j/helpers/ReaderWriterLock.java
new file mode 100644
index 0000000..3412774
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/ReaderWriterLock.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.io.PrintWriter;
+
+
+/**
+ *
+ * A RederWriterLock allows multiple readers to obtain the lock at the same time
+ * but allows only one writer at a time.
+ *
+ * When both readers and writers wait to obtain the lock, priority is given to 
+ * waiting writers.
+ *
+ * This lock is not reentrant. It is possible for a writer in possession of a writer
+ * lock to fail to obtain a reader lock. The same goes for reader in possession of a 
+ * reader lock. It can fail to obtain a writer lock.
+ * 
+ * THIS LOCK IS NOT RENTRANT.
+ * 
+ * It is the developer's responsability to retstrict the use of this lock to small segments
+ * of code where reentrancy can be avoided.
+ * 
+ * Note that the RederWriterLock is only useful in cases where a resource:
+ * 
+ * 1) Has many frequent read operations performed on it
+ * 2) Only rarely is the resource modified (written)
+ * 3) Read operations are invoked by many different threads
+ * 
+ * If any of the above conditions are not met, it is better to avoid this fancy lock.
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ *
+ */
+public class ReaderWriterLock {
+  int readers = 0;
+  int writers = 0;
+  int waitingWriters = 0;
+  PrintWriter printWriter;
+
+  public ReaderWriterLock() {
+  }
+
+  public ReaderWriterLock(PrintWriter pw) {
+    printWriter = pw;
+  }
+
+  public synchronized void getReadLock() {
+    if (printWriter != null) {
+      printMessage("Asking for read lock.");
+    }
+
+    while ((writers > 0) || (waitingWriters > 0)) {
+      try {
+        wait();
+      } catch (InterruptedException ie) {
+      }
+    }
+
+    if (printWriter != null) {
+      printMessage("Got read lock.");
+    }
+
+    readers++;
+  }
+
+  public synchronized void releaseReadLock() {
+    if (printWriter != null) {
+      printMessage("About to release read lock.");
+    }
+
+    readers--;
+
+    if (waitingWriters > 0) {
+      notifyAll();
+    }
+  }
+
+  public synchronized void getWriteLock() {
+    if (printWriter != null) {
+      printMessage("Asking for write lock.");
+    }
+
+    waitingWriters++;
+
+    while ((readers > 0) || (writers > 0)) {
+      try {
+        wait();
+      } catch (InterruptedException ie) {
+      }
+    }
+
+    if (printWriter != null) {
+      printMessage("Got write lock.");
+    }
+
+    waitingWriters--;
+    writers++;
+  }
+
+  public synchronized void releaseWriteLock() {
+    if (printWriter != null) {
+      printMessage("About to release write lock.");
+    }
+
+    writers--;
+    notifyAll();
+  }
+
+  void printMessage(String msg) {
+    //printWriter.print("[");      
+    printWriter.println(Thread.currentThread().getName() + " " + msg);
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.java b/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.java
new file mode 100644
index 0000000..38b8d19
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.util.Date;
+import java.util.TimeZone;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.DateFormat;
+
+/**
+   Formats a {@link Date} by printing the number of milliseconds
+   elapsed since the construction of the format.  This is the fastest
+   printing DateFormat in the package.
+   
+   @author Ceki G&uuml;lc&uuml;
+   @since 0.7.5
+*/
+public class RelativeTimeDateFormat extends DateFormat {
+
+  protected final long startTime;
+
+  public
+  RelativeTimeDateFormat() {
+    this.startTime = System.currentTimeMillis();
+  }
+  
+  /**
+     Appends to <code>sbuf</code> the number of milliseconds elapsed
+     since the start of the application. 
+     
+     @since 0.7.5
+  */
+  public
+  StringBuffer format(Date date, StringBuffer sbuf,
+		      FieldPosition fieldPosition) {
+    //System.err.println(":"+ date.getTime() + " - " + startTime);
+    return sbuf.append((date.getTime() - startTime));
+  }
+
+  /**
+     This method does not do anything but return <code>null</code>.
+   */
+  public
+  Date parse(java.lang.String s, ParsePosition pos) {
+    return null;
+  }  
+
+  /**
+   * Sets the timezone.
+   * Ignored by this formatter, but intercepted to prevent
+   * NullPointerException in superclass.
+   * @param tz TimeZone timezone
+   */
+  public void setTimeZone(final TimeZone tz) {
+  }
+  
+}
diff --git a/src/main/java/org/apache/log4j/helpers/SyslogQuietWriter.java b/src/main/java/org/apache/log4j/helpers/SyslogQuietWriter.java
new file mode 100644
index 0000000..817cbc3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/SyslogQuietWriter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+
+
+import java.io.Writer;
+
+/**
+   SyslogQuietWriter extends QuietWriter by prepending the syslog
+   level code before each printed String.
+
+   @since 0.7.3
+   @deprecated
+*/
+public class SyslogQuietWriter extends QuietWriter {
+
+  int syslogFacility;
+  int level;
+
+    /**
+     * @deprecated
+     * @param writer
+     * @param syslogFacility
+     * @param eh
+     */
+  public
+  SyslogQuietWriter(Writer writer, int syslogFacility, org.apache.log4j.spi.ErrorHandler eh) {
+    super(writer, eh);
+    this.syslogFacility = syslogFacility;
+  }
+
+  public
+  void setLevel(int level) {
+    this.level = level;
+  }
+
+  public
+  void setSyslogFacility(int syslogFacility) {
+    this.syslogFacility = syslogFacility;
+  }
+
+  public
+  void write(String string) {
+    super.write("<"+(syslogFacility | level)+">" + string);
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/SyslogWriter.java b/src/main/java/org/apache/log4j/helpers/SyslogWriter.java
new file mode 100644
index 0000000..bfbbcb8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/SyslogWriter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.helpers;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+
+/**
+   SyslogWriter is a wrapper around the java.net.DatagramSocket class
+   so that it behaves like a java.io.Writer.
+
+   @since 0.7.3
+*/
+public class SyslogWriter extends Writer {
+  private static final int SYSLOG_PORT = 514;
+  private InetAddress address;
+  private final int port;
+  private DatagramSocket ds;
+
+  private Logger logger = LogManager.getLogger(SyslogWriter.class);
+  private StringBuffer buf = new StringBuffer();
+  
+  /**
+   *  Constructs a new instance of SyslogWriter.
+   *  @param syslogHost host name, may not be null.  A port
+   *  may be specified by following the name or IPv4 literal address with
+   *  a colon and a decimal port number.  To specify a port with an IPv6
+   *  address, enclose the IPv6 address in square brackets before appending
+   *  the colon and decimal port number.
+   */
+  public SyslogWriter(final String syslogHost) {
+    if (syslogHost == null) {
+        throw new NullPointerException("syslogHost");
+    }
+    
+    String host = syslogHost;
+    int urlPort = -1;
+    
+    //
+    //  If not an unbracketed IPv6 address then
+    //      parse as a URL
+    //
+    if (host.indexOf("[") != -1 || host.indexOf(':') == host.lastIndexOf(':')) {
+        try {
+            URL url = new URL("http://" + host);
+            if (url.getHost() != null) {
+                host = url.getHost();
+                //   if host is a IPv6 literal, strip off the brackets
+                if(host.startsWith("[") && host.charAt(host.length() - 1) == ']') {
+                    host = host.substring(1, host.length() - 1);
+                }
+                urlPort = url.getPort();
+            }
+        } catch(MalformedURLException e) {
+      		logger.warn("Malformed URL: will attempt to interpret as InetAddress.", e);
+        }
+    }
+    
+    if (urlPort == -1) {
+        urlPort = SYSLOG_PORT;
+    }
+    port = urlPort;
+
+    try {
+      this.address = InetAddress.getByName(host);
+    } catch (UnknownHostException e) {
+      logger.error(
+        "Could not find " + host + ". All logging will FAIL.", e);
+    }
+
+    try {
+      this.ds = new DatagramSocket();
+    } catch (SocketException e) {
+      e.printStackTrace();
+      logger.error(
+        "Could not instantiate DatagramSocket to " + host
+        + ". All logging will FAIL.", e);
+    }
+  }
+
+  public void write(char[] charArray, int offset, int len) throws IOException {
+    buf.append(charArray, offset, len);
+  }
+
+  public void write(String str) throws IOException {
+    buf.append(str); 
+  }
+
+  /**
+   * Sends the pending data.
+   */
+  public void flush() throws IOException {
+    if (buf.length() == 0)
+      return;
+    // logging here can be problematic during shutdown when writing the footer
+    // logger.debug("Writing out [{}]", buf);
+    byte[] bytes = buf.toString().getBytes();
+    DatagramPacket packet =
+      new DatagramPacket(bytes, bytes.length, address, port);
+
+    ds.send(packet);
+
+    // clean up for next time
+    buf.setLength(0);
+  }
+
+  /**
+   * Closes the datagram socket.
+   */
+  public void close() {
+    try {
+      flush();
+    } catch (IOException e) {
+      // should throw it ... can't change method sig. though
+    }
+    ds.close();
+  }
+  
+}
diff --git a/src/main/java/org/apache/log4j/helpers/ThreadLocalMap.java b/src/main/java/org/apache/log4j/helpers/ThreadLocalMap.java
new file mode 100644
index 0000000..0736f0f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/ThreadLocalMap.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.util.Hashtable;
+
+/**
+   <code>ThreadLocalMap</code> extends {@link InheritableThreadLocal}
+   to bequeath a copy of the hashtable of the MDC of the parent
+   thread.
+
+   @author Ceki G&uuml;lc&uuml;
+   @since 1.2
+*/
+final public class ThreadLocalMap extends InheritableThreadLocal {
+
+  public final Object childValue(Object parentValue) {
+    Hashtable ht = (Hashtable) parentValue;
+    if(ht != null) {
+      return ht.clone();
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/Transform.java b/src/main/java/org/apache/log4j/helpers/Transform.java
new file mode 100644
index 0000000..f865dd1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/Transform.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.io.IOException;
+import java.io.Writer;
+
+
+/**
+   Utility class for transforming strings.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Michael A. McAngus
+ */
+public class Transform {
+  private static final String CDATA_START = "<![CDATA[";
+  private static final String CDATA_END = "]]>";
+  private static final String CDATA_PSEUDO_END = "]]&gt;";
+  private static final String CDATA_EMBEDED_END =
+    CDATA_END + CDATA_PSEUDO_END + CDATA_START;
+  private static final int CDATA_END_LEN = CDATA_END.length();
+
+  /**
+   * This method takes a string which may contain HTML tags (ie,
+   * &lt;b&gt;, &lt;table&gt;, etc) and replaces any '<' and '>'
+   * characters with respective predefined entity references.
+   *
+   * @param input The text to be converted. 
+   */
+  public static String escapeTags(final String input) {
+    //Check if the string is null or zero length -- if so, return
+    //what was sent in.
+    if ((input == null)
+            || (input.length() == 0)
+            || (input.indexOf("<") == -1 && input.indexOf(">") == -1)) {
+      return input;
+    }
+
+    StringBuffer buf = new StringBuffer(input);
+    for(int i = 0;i < buf.length(); i++) {
+        char ch = buf.charAt(i);
+        if (ch == '<') {
+            buf.replace(i, i + 1, "&lt;");
+        } else if (ch == '>') {
+            buf.replace(i, i + 1, "&gt;");
+        }
+    }
+    return buf.toString();
+  }
+
+  //public static void appendEscapingCDATA(StringBuffer buf, String str) {
+  //	
+  //}
+
+  /**
+  * Ensures that embeded CDEnd strings (]]>) are handled properly
+  * within message, NDC and throwable tag text.
+  *
+  * @param output Writer.  The
+  * initial CDSutart (<![CDATA[) and final CDEnd (]]>) of the CDATA
+  * section are the responsibility of the calling method.
+  * 
+  * @param str The String that is inserted into an existing CDATA Section.
+  * */
+  public static void appendEscapingCDATA(StringBuffer output, String str) {
+    if (str == null) {
+      return;
+    }
+
+    int end = str.indexOf(CDATA_END);
+
+    if (end < 0) {
+      output.append(str);
+
+      return;
+    }
+
+    int start = 0;
+
+    while (end > -1) {
+      output.append(str.substring(start, end));
+      output.append(CDATA_EMBEDED_END);
+      start = end + CDATA_END_LEN;
+
+      if (start < str.length()) {
+        end = str.indexOf(CDATA_END, start);
+      } else {
+        return;
+      }
+    }
+
+    output.append(str.substring(start));
+  }
+}
diff --git a/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java b/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java
new file mode 100644
index 0000000..c46b14f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.log4j.helpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Level;
+
+/**
+ *  An extension of the Level class that provides support for java.util.logging 
+ * Levels.
+ *
+ *  @author Scott Deboy <sdeboy@apache.org>
+ *
+ */
+
+public class UtilLoggingLevel extends Level {
+
+  private static final long serialVersionUID = 909301162611820211L;
+
+  public static final int SEVERE_INT = 17000;
+  public static final int WARNING_INT = 16000;
+  public static final int INFO_INT = 15000;
+  public static final int CONFIG_INT = 14000;
+  public static final int FINE_INT = 13000;
+  public static final int FINER_INT = 12000;
+  public static final int FINEST_INT = 11000;
+  public static final int UNKNOWN_INT = 10000;
+  
+  public static final UtilLoggingLevel SEVERE = new UtilLoggingLevel(SEVERE_INT, "SEVERE", 0);
+  public static final UtilLoggingLevel WARNING = new UtilLoggingLevel(WARNING_INT, "WARNING", 4);
+  public static final UtilLoggingLevel INFO = new UtilLoggingLevel(INFO_INT, "INFO", 5);
+  public static final UtilLoggingLevel CONFIG = new UtilLoggingLevel(CONFIG_INT, "CONFIG", 6);
+  public static final UtilLoggingLevel FINE = new UtilLoggingLevel(FINE_INT, "FINE", 7);
+  public static final UtilLoggingLevel FINER = new UtilLoggingLevel(FINER_INT, "FINER", 8);      
+  public static final UtilLoggingLevel FINEST = new UtilLoggingLevel(FINEST_INT, "FINEST", 9);      
+
+  protected UtilLoggingLevel(int level, String levelStr, int syslogEquivalent) {
+    super(level, levelStr, syslogEquivalent);
+  }
+
+  /**
+    Convert an integer passed as argument to a level. If the
+    conversion fails, then this method returns the specified default.
+  */
+  public static UtilLoggingLevel toLevel(int val, UtilLoggingLevel defaultLevel) {
+    switch (val) {
+    case SEVERE_INT:
+      return SEVERE;
+
+    case WARNING_INT:
+      return WARNING;
+
+    case INFO_INT:
+      return INFO;
+
+    case CONFIG_INT:
+      return CONFIG;
+
+    case FINE_INT:
+      return FINE;
+
+    case FINER_INT:
+      return FINER;
+
+    case FINEST_INT:
+      return FINEST;
+
+    default:
+      return defaultLevel;
+    }
+  }
+
+  public static Level toLevel(int val) {
+    return toLevel(val, FINEST);
+  }
+
+  public static List getAllPossibleLevels() {
+  	ArrayList list=new ArrayList();
+  	list.add(FINE);
+  	list.add(FINER);
+  	list.add(FINEST);
+  	list.add(INFO);
+  	list.add(CONFIG);
+  	list.add(WARNING);
+  	list.add(SEVERE);
+  	return list;
+  }
+
+  public static Level toLevel(String s) {
+  	return toLevel(s, Level.DEBUG);
+  }
+  
+  public static Level toLevel(String sArg, Level defaultLevel) {
+    if (sArg == null) {
+      return defaultLevel;
+    }
+
+    String s = sArg.toUpperCase();
+
+    if (s.equals("SEVERE")) {
+      return SEVERE;
+    }
+
+    //if(s.equals("FINE")) return Level.FINE; 
+    if (s.equals("WARNING")) {
+      return WARNING;
+    }
+
+    if (s.equals("INFO")) {
+      return INFO;
+    }
+
+    if (s.equals("CONFI")) {
+      return CONFIG;
+    }
+
+    if (s.equals("FINE")) {
+      return FINE;
+    }
+
+    if (s.equals("FINER")) {
+      return FINER;
+    }
+
+    if (s.equals("FINEST")) {
+      return FINEST;
+    }
+    return defaultLevel;
+  }
+
+}
diff --git a/src/main/java/org/apache/log4j/helpers/package.html b/src/main/java/org/apache/log4j/helpers/package.html
new file mode 100644
index 0000000..870a8fd
--- /dev/null
+++ b/src/main/java/org/apache/log4j/helpers/package.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title></title>
+</head>
+
+<body>
+
+<p>This package is used internally.
+
+
+<hr>
+<address></address>
+<!-- hhmts start -->
+Last modified: Sat Jul  3 15:12:58 MDT 1999
+<!-- hhmts end -->
+</body> </html>