[logback-dev] [GIT] Logback: the generic, reliable, fast and flexible logging framework. branch, master, updated. v_0.9.25-42-g2742a97

added by portage for gitosis-gentoo git-noreply at pixie.qos.ch
Wed Dec 15 19:07:08 CET 2010


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Logback: the generic, reliable, fast and flexible logging framework.".

The branch, master has been updated
       via  2742a97de478cd3fc270223816eaa1c24d13cd82 (commit)
      from  fc4508616906b773dd9d93d4e0d2c45628d3f7b5 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.qos.ch/gitweb/?p=logback.git;a=commit;h=2742a97de478cd3fc270223816eaa1c24d13cd82
http://github.com/ceki/logback/commit/2742a97de478cd3fc270223816eaa1c24d13cd82

commit 2742a97de478cd3fc270223816eaa1c24d13cd82
Author: Ceki Gulcu <ceki at qos.ch>
Date:   Wed Dec 15 19:07:04 2010 +0100

    adding support for composite keywords

diff --git a/goscp b/goscp
new file mode 100644
index 0000000..406a6ae
--- /dev/null
+++ b/goscp
@@ -0,0 +1,3 @@
+TARGET=cgulcu at hqchnesoa07:/srv/jboss-eap-4.3.0.GA_CP05/jboss-as/server/nesoa-04-esb-01/lib/
+
+scp logback-core/target/logback-core-0.9.27-SNAPSHOT.jar logback-classic/target/logback-classic-0.9.27-SNAPSHOT.jar logback-access/target/logback-access-0.9.27-SNAPSHOT.jar   $TARGET
\ No newline at end of file
diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/pattern/DateConverter.java b/logback-classic/src/main/java/ch/qos/logback/classic/pattern/DateConverter.java
index 84226b5..4f0a449 100644
--- a/logback-classic/src/main/java/ch/qos/logback/classic/pattern/DateConverter.java
+++ b/logback-classic/src/main/java/ch/qos/logback/classic/pattern/DateConverter.java
@@ -29,6 +29,7 @@ public class DateConverter extends ClassicConverter {
 
   public void start() {
 
+
     String datePattern = getFirstOption();
     if (datePattern == null) {
       datePattern = CoreConstants.ISO8601_PATTERN;
diff --git a/logback-classic/src/test/java/ch/qos/logback/classic/turbo/ReconfigureOnChangeTest.java b/logback-classic/src/test/java/ch/qos/logback/classic/turbo/ReconfigureOnChangeTest.java
index 4259f26..546a653 100644
--- a/logback-classic/src/test/java/ch/qos/logback/classic/turbo/ReconfigureOnChangeTest.java
+++ b/logback-classic/src/test/java/ch/qos/logback/classic/turbo/ReconfigureOnChangeTest.java
@@ -179,7 +179,7 @@ public class ReconfigureOnChangeTest {
     // we can't have the test succeed under JDK 1.5, punt and require 1.6+
     if (Env.isJDK6OrHigher()) {
       assertTrue(failMsg,
-          (effectiveResets * 1.3) >= (expectedReconfigurations * 1.0));
+          (effectiveResets * 1.4) >= (expectedReconfigurations * 1.0));
     }
   }
 
diff --git a/logback-core/src/main/java/ch/qos/logback/core/OutputStreamAppender.java b/logback-core/src/main/java/ch/qos/logback/core/OutputStreamAppender.java
index 0ba51b4..05f1a7b 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/OutputStreamAppender.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/OutputStreamAppender.java
@@ -210,8 +210,9 @@ public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {
       if (event instanceof DeferredProcessingAware) {
         ((DeferredProcessingAware) event).prepareForDeferredProcessing();
       }
-      // the synchronized prevents the OutputStream from being closed while we
-      // are writing
+      // the synchronization prevents the OutputStream from being closed while we
+      // are writing. It also prevents multiple thread from entering the same
+      // converter. Converters assume that they are in a synchronized block.
       synchronized (lock) {
         writeOut(event);
       }
diff --git a/logback-core/src/main/java/ch/qos/logback/core/html/HTMLLayoutBase.java b/logback-core/src/main/java/ch/qos/logback/core/html/HTMLLayoutBase.java
index 5ee84d0..af16edf 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/html/HTMLLayoutBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/html/HTMLLayoutBase.java
@@ -83,7 +83,7 @@ public abstract class HTMLLayoutBase<E> extends LayoutBase<E> {
       Parser<E> p = new Parser<E>(pattern);
       p.setContext(getContext());
       Node t = p.parse();
-      this.head = p.compile(t, getEffectiveConverterMap());
+      this.head = p.compile(t, getEffectiveConverterMap(), Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
       ConverterUtil.startConverters(this.head);
     } catch (ScanException ex) {
       addError("Incorrect pattern found", ex);
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/CompositeConverter.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/CompositeConverter.java
index 07e352e..bad3c8c 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/CompositeConverter.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/CompositeConverter.java
@@ -1,6 +1,6 @@
 /**
  * Logback: the reliable, generic, fast and flexible logging framework.
- * Copyright (C) 1999-2009, QOS.ch. All rights reserved.
+ * Copyright (C) 1999-2010, QOS.ch. All rights reserved.
  *
  * This program and the accompanying materials are dual-licensed under
  * either the terms of the Eclipse Public License v1.0 as published by
@@ -13,24 +13,22 @@
  */
 package ch.qos.logback.core.pattern;
 
-public class CompositeConverter<E> extends FormattingConverter<E> {
+abstract public class CompositeConverter<E> extends DynamicConverter<E> {
 
-  StringBuilder buf = new StringBuilder();
   Converter<E> childConverter;
 
   public String convert(E event) {
-    if (buf.capacity() > MAX_CAPACITY) {
-      buf = new StringBuilder(INITIAL_BUF_SIZE);
-    } else {
-      buf.setLength(0);
-    }
+    StringBuilder buf = new StringBuilder();
 
     for (Converter<E> c = childConverter; c != null; c = c.next) {
       c.write(buf, event);
     }
-    return buf.toString();
+    String intermediary = buf.toString();
+    return transform(intermediary);
   }
 
+  abstract String transform(String in);
+
   public void setChildConverter(Converter<E> child) {
     childConverter = child;
   }
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/ConverterUtil.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/ConverterUtil.java
index 03f65e8..1dc12b2 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/ConverterUtil.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/ConverterUtil.java
@@ -17,25 +17,28 @@ public class ConverterUtil {
 
   /**
    * Start converters in the chain of converters.
+   *
    * @param head
    */
   public static void startConverters(Converter head) {
     Converter c = head;
     while (c != null) {
-      if (c instanceof DynamicConverter) {
-        DynamicConverter dc = (DynamicConverter) c;
-        dc.start();
-      } else if(c instanceof CompositeConverter){
+      // CompositeConverter is a subclass of  DynamicConverter
+      if (c instanceof CompositeConverter) {
         CompositeConverter cc = (CompositeConverter) c;
         Converter childConverter = cc.childConverter;
         startConverters(childConverter);
+        cc.start();
+      } else if (c instanceof DynamicConverter) {
+        DynamicConverter dc = (DynamicConverter) c;
+        dc.start();
       }
       c = c.getNext();
     }
   }
 
-  
-  public static<E> Converter<E> findTail(Converter<E> head) {
+
+  public static <E> Converter<E> findTail(Converter<E> head) {
     Converter<E> p = head;
     while (p != null) {
       Converter<E> next = p.getNext();
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/DynamicConverter.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/DynamicConverter.java
index 53eb3b8..cdb0958 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/DynamicConverter.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/DynamicConverter.java
@@ -21,7 +21,7 @@ abstract public class DynamicConverter<E> extends FormattingConverter<E>
     implements LifeCycle {
 
   // Contains a list of option Strings.
-  private List optionList;
+  private List<String> optionList;
 
   /**
    * Is this component active?
@@ -46,7 +46,7 @@ abstract public class DynamicConverter<E> extends FormattingConverter<E>
     return started;
   }
 
-  public void setOptionList(List optionList) {
+  public void setOptionList(List<String> optionList) {
     this.optionList = optionList;
   }
 
@@ -60,11 +60,11 @@ abstract public class DynamicConverter<E> extends FormattingConverter<E>
     if (optionList == null || optionList.size() == 0) {
       return null;
     } else {
-      return (String) optionList.get(0);
+      return optionList.get(0);
     }
   }
 
-  protected List getOptionList() {
+  protected List<String> getOptionList() {
     return optionList;
   }
 }
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/IdentityCompositeConverter.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/IdentityCompositeConverter.java
new file mode 100644
index 0000000..473ff6c
--- /dev/null
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/IdentityCompositeConverter.java
@@ -0,0 +1,22 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * Copyright (C) 1999-2010, QOS.ch. All rights reserved.
+ *
+ * This program and the accompanying materials are dual-licensed under
+ * either the terms of the Eclipse Public License v1.0 as published by
+ * the Eclipse Foundation
+ *
+ *   or (per the licensee's choosing)
+ *
+ * under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation.
+ */
+package ch.qos.logback.core.pattern;
+
+public class IdentityCompositeConverter<E> extends CompositeConverter<E> {
+
+  @Override
+  String transform(String in) {
+    return in;
+  }
+}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/PatternLayoutBase.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/PatternLayoutBase.java
index d507887..e60ee6f 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/PatternLayoutBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/PatternLayoutBase.java
@@ -81,7 +81,7 @@ abstract public class PatternLayoutBase<E> extends LayoutBase<E> {
         p.setContext(getContext());
       }
       Node t = p.parse();
-      this.head = p.compile(t, getEffectiveConverterMap());
+      this.head = p.compile(t, getEffectiveConverterMap(), Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
       if (postCompileProcessor != null) {
         postCompileProcessor.process(head);
       }
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/ReplacingCompositeConverter.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/ReplacingCompositeConverter.java
new file mode 100644
index 0000000..c5e29ef
--- /dev/null
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/ReplacingCompositeConverter.java
@@ -0,0 +1,31 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * Copyright (C) 1999-2010, QOS.ch. All rights reserved.
+ *
+ * This program and the accompanying materials are dual-licensed under
+ * either the terms of the Eclipse Public License v1.0 as published by
+ * the Eclipse Foundation
+ *
+ *   or (per the licensee's choosing)
+ *
+ * under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation.
+ */
+package ch.qos.logback.core.pattern;
+
+import java.util.List;
+
+public class ReplacingCompositeConverter<E> extends CompositeConverter<E> {
+
+  String regex;
+  String by;
+
+  public void start() {
+     final List<String> optionList = getOptionList();
+     regex = optionList.get(0);
+  }
+  @Override
+  String transform(String in) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Compiler.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Compiler.java
index bc94153..9bcb1a6 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Compiler.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Compiler.java
@@ -15,10 +15,7 @@ package ch.qos.logback.core.pattern.parser;
 
 import java.util.Map;
 
-import ch.qos.logback.core.pattern.CompositeConverter;
-import ch.qos.logback.core.pattern.Converter;
-import ch.qos.logback.core.pattern.DynamicConverter;
-import ch.qos.logback.core.pattern.LiteralConverter;
+import ch.qos.logback.core.pattern.*;
 import ch.qos.logback.core.spi.ContextAwareBase;
 import ch.qos.logback.core.status.ErrorStatus;
 import ch.qos.logback.core.util.OptionHelper;
@@ -29,46 +26,49 @@ class Compiler<E> extends ContextAwareBase {
   Converter<E> tail;
   final Node top;
   final Map converterMap;
+  final Map compositeConverterMap;
 
-  Compiler(final Node top, final Map converterMap) {
+  Compiler(final Node top, final Map converterMap, Map compositeConverterMap) {
     this.top = top;
     this.converterMap = converterMap;
+    this.compositeConverterMap = compositeConverterMap;
   }
 
   Converter<E> compile() {
     head = tail = null;
     for (Node n = top; n != null; n = n.next) {
       switch (n.type) {
-      case Node.LITERAL:
-        addToList(new LiteralConverter<E>((String) n.getValue()));
-        break;
-      case Node.COMPOSITE:
-        CompositeNode cn = (CompositeNode) n;
-        CompositeConverter<E> compositeConverter = new CompositeConverter<E>();
-        compositeConverter.setFormattingInfo(cn.getFormatInfo());
-        Compiler<E> childCompiler = new Compiler<E>(cn.getChildNode(),
-            converterMap);
-        childCompiler.setContext(context);
-        Converter<E> childConverter = childCompiler.compile();
-        compositeConverter.setChildConverter(childConverter);
-        addToList(compositeConverter);
-        break;
-      case Node.KEYWORD:
-        KeywordNode kn = (KeywordNode) n;
-        DynamicConverter<E> dynaConverter = createConverter(kn);
-        if (dynaConverter != null) {
-          dynaConverter.setFormattingInfo(kn.getFormatInfo());
-          dynaConverter.setOptionList(kn.getOptions());
-          addToList(dynaConverter);
-        } else {
-          // if the appropriate dynaconverter cannot be found, then replace
-          // it with a dummy LiteralConverter indicating an error.
-          Converter<E> errConveter = new LiteralConverter<E>("%PARSER_ERROR["
-              + kn.getValue()+"]");
-          addStatus(new ErrorStatus("[" + kn.getValue()
-              + "] is not a valid conversion word", this));
-          addToList(errConveter);
-        }
+        case Node.LITERAL:
+          addToList(new LiteralConverter<E>((String) n.getValue()));
+          break;
+        case Node.COMPOSITE_KEYWORD:
+          CompositeNode cn = (CompositeNode) n;
+          CompositeConverter<E> compositeConverter = createCompiteConverter(cn);
+          compositeConverter.setFormattingInfo(cn.getFormatInfo());
+          compositeConverter.setOptionList(cn.getOptions());
+          Compiler<E> childCompiler = new Compiler<E>(cn.getChildNode(),
+                  converterMap, compositeConverterMap);
+          childCompiler.setContext(context);
+          Converter<E> childConverter = childCompiler.compile();
+          compositeConverter.setChildConverter(childConverter);
+          addToList(compositeConverter);
+          break;
+        case Node.SIMPLE_KEYWORD:
+          SimpleKeywordNode kn = (SimpleKeywordNode) n;
+          DynamicConverter<E> dynaConverter = createConverter(kn);
+          if (dynaConverter != null) {
+            dynaConverter.setFormattingInfo(kn.getFormatInfo());
+            dynaConverter.setOptionList(kn.getOptions());
+            addToList(dynaConverter);
+          } else {
+            // if the appropriate dynaconverter cannot be found, then replace
+            // it with a dummy LiteralConverter indicating an error.
+            Converter<E> errConveter = new LiteralConverter<E>("%PARSER_ERROR["
+                    + kn.getValue() + "]");
+            addStatus(new ErrorStatus("[" + kn.getValue()
+                    + "] is not a valid conversion word", this));
+            addToList(errConveter);
+          }
 
       }
     }
@@ -87,31 +87,60 @@ class Compiler<E> extends ContextAwareBase {
   /**
    * Attempt to create a converter using the information found in
    * 'converterMap'.
-   * 
+   *
    * @param kn
    * @return
    */
   @SuppressWarnings("unchecked")
-  DynamicConverter<E> createConverter(KeywordNode kn) {
+  DynamicConverter<E> createConverter(SimpleKeywordNode kn) {
     String keyword = (String) kn.getValue();
     String converterClassStr = (String) converterMap.get(keyword);
 
     if (converterClassStr != null) {
       try {
         return (DynamicConverter) OptionHelper.instantiateByClassName(
-            converterClassStr, DynamicConverter.class, context);
+                converterClassStr, DynamicConverter.class, context);
       } catch (Exception e) {
         addError("Failed to instantiate converter class [" + converterClassStr
-            + "]", e);
+                + "]", e);
         return null;
       }
     } else {
       addError("There is no conversion class registered for conversion word ["
-          + keyword + "]");
+              + keyword + "]");
       return null;
     }
   }
 
+  /**
+   * Attempt to create a converter using the information found in
+   * 'compositeConverterMap'.
+   *
+   * @param cn
+   * @return
+   */
+  @SuppressWarnings("unchecked")
+  CompositeConverter<E> createCompiteConverter(CompositeNode cn) {
+    String keyword = (String) cn.getValue();
+    String converterClassStr = (String) compositeConverterMap.get(keyword);
+
+    if (converterClassStr != null) {
+      try {
+        return (CompositeConverter) OptionHelper.instantiateByClassName(
+                converterClassStr, CompositeConverter.class, context);
+      } catch (Exception e) {
+        addError("Failed to instantiate converter class [" + converterClassStr
+                + "]", e);
+        return null;
+      }
+    } else {
+      addError("There is no conversion class registered for composite conversion word ["
+              + keyword + "]");
+      return null;
+    }
+  }
+
+
   // public void setStatusManager(StatusManager statusManager) {
   // this.statusManager = statusManager;
   // }
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/CompositeNode.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/CompositeNode.java
index 8fd2029..cf3c402 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/CompositeNode.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/CompositeNode.java
@@ -13,11 +13,12 @@
  */
 package ch.qos.logback.core.pattern.parser;
 
-public class CompositeNode extends FormattingNode {
+public class CompositeNode extends SimpleKeywordNode {
 	Node childNode;
 
-	CompositeNode() {
-		super(Node.COMPOSITE);
+	CompositeNode(String keyword) {
+		super(Node.COMPOSITE_KEYWORD, keyword);
+
 	}
 
 	public Node getChildNode() {
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Node.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Node.java
index 615e2e9..e9bee8b 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Node.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Node.java
@@ -15,8 +15,8 @@ package ch.qos.logback.core.pattern.parser;
 
 public class Node {
 	static final int LITERAL = 0;
-	static final int KEYWORD = 1;
-	static final int COMPOSITE = 2;
+	static final int SIMPLE_KEYWORD = 1;
+	static final int COMPOSITE_KEYWORD = 2;
 
 	final int type;
 	final Object value;
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/OptionTokenizer.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/OptionTokenizer.java
index 65308db..ad4c762 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/OptionTokenizer.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/OptionTokenizer.java
@@ -52,7 +52,7 @@ public class OptionTokenizer {
     this.escapeUtil = escapeUtil;
   }
 
-  List tokenize() throws ScanException {
+  List<String> tokenize() throws ScanException {
     List<String> tokenList = new ArrayList<String>();
     StringBuffer buf = new StringBuffer();
 
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Parser.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Parser.java
index 268edea..510aa7a 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Parser.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Parser.java
@@ -13,18 +13,46 @@
  */
 package ch.qos.logback.core.pattern.parser;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import ch.qos.logback.core.pattern.Converter;
 import ch.qos.logback.core.pattern.FormatInfo;
+import ch.qos.logback.core.pattern.IdentityCompositeConverter;
+import ch.qos.logback.core.pattern.ReplacingCompositeConverter;
 import ch.qos.logback.core.pattern.util.IEscapeUtil;
 import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
 import ch.qos.logback.core.spi.ContextAwareBase;
 
+// ~=lamda
+// E = TE|T
+
+// Left factorization
+// E = T(E|~)
+// Eopt = E|~
+// replace E|~ with Eopt in E
+// E = TEopt
+
+// T = LITERAL | '%' C | '%' FORMAT_MODIFIER C
+// C = SIMPLE_KEYWORD OPTION | COMPOSITE_KEYWORD COMPOSITE
+// OPTION = {...} | ~
+// COMPOSITE = E ')' OPTION
+
+
 
 public class Parser<E> extends ContextAwareBase {
 
+  public final static Map<String, String> DEFAULT_COMPOSITE_CONVERTER_MAP = new HashMap<String, String>();
+  public final static String REPLACER_CONVERTER_WORD = "replace";
+  static {
+    DEFAULT_COMPOSITE_CONVERTER_MAP.put(Token.BARE_COMPOSITE_KEYWORD_TOKEN.getValue().toString(),
+            IdentityCompositeConverter.class.getName());
+    DEFAULT_COMPOSITE_CONVERTER_MAP.put(REPLACER_CONVERTER_WORD,
+             ReplacingCompositeConverter.class.getName());
+
+  }
+
   final List tokenList;
   int pointer = 0;
   
@@ -32,7 +60,6 @@ public class Parser<E> extends ContextAwareBase {
     this.tokenList = ts.tokenize();
   }
 
-  // this variant should be used for testing purposes only
   public Parser(String pattern) throws ScanException {
     this(pattern, new RegularEscapeUtil());
   }
@@ -46,40 +73,51 @@ public class Parser<E> extends ContextAwareBase {
     }
   }
 
-  public Node parse() throws ScanException {
-    return E();
-  }
-
   /**
    * When the parsing step is done, the Node list can be transformed into a
    * converter chain.
-   * 
+   *
    * @param top
    * @param converterMap
    * @return
    * @throws ScanException
    */
-  public Converter<E> compile(final Node top, Map converterMap) {
-    Compiler<E> compiler = new Compiler<E>(top, converterMap);
+  public Converter<E> compile(final Node top, Map converterMap, Map compositeConverterMap) {
+    Compiler<E> compiler = new Compiler<E>(top, converterMap, compositeConverterMap);
     compiler.setContext(context);
     //compiler.setStatusManager(statusManager);
     return compiler.compile();
   }
 
+  public Node parse() throws ScanException {
+    return E();
+  }
+
+  // E = TEopt
   Node E() throws ScanException {
-    // System.out.println("in E()");
     Node t = T();
     if (t == null) {
       return null;
     }
     Node eOpt = Eopt();
     if (eOpt != null) {
-      // System.out.println("setting next node to " + eOpt);
       t.setNext(eOpt);
     }
     return t;
   }
 
+  // Eopt = E|~
+  Node Eopt() throws ScanException {
+    // System.out.println("in Eopt()");
+    Token next = getCurentToken();
+    // System.out.println("Current token is " + next);
+    if (next == null) {
+      return null;
+    } else {
+      return E();
+    }
+  }
+
   Node T() throws ScanException {
     // System.out.println("in T()");
     Token t = getCurentToken();
@@ -117,17 +155,6 @@ public class Parser<E> extends ContextAwareBase {
 
   }
 
-  Node Eopt() throws ScanException {
-    // System.out.println("in Eopt()");
-    Token next = getCurentToken();
-    // System.out.println("Current token is " + next);
-    if (next == null) {
-      return null;
-    } else {
-      return E();
-    }
-  }
-
   FormattingNode C() throws ScanException {
     Token t = getCurentToken();
     // System.out.println("in C()");
@@ -135,11 +162,11 @@ public class Parser<E> extends ContextAwareBase {
     expectNotNull(t, "a LEFT_PARENTHESIS or KEYWORD");
     int type = t.getType();
     switch (type) {
-    case Token.KEYWORD:
+    case Token.SIMPLE_KEYWORD:
       return SINGLE();
-    case Token.LEFT_PARENTHESIS:
+    case Token.COMPOSITE_KEYWORD:
       advanceTokenPointer();
-      return COMPOSITE();
+      return COMPOSITE(t.getValue().toString());
     default:
       throw new IllegalStateException("Unexpected token " + t);
     }
@@ -149,19 +176,19 @@ public class Parser<E> extends ContextAwareBase {
     // System.out.println("in SINGLE()");
     Token t = getNextToken();
     // System.out.println("==" + t);
-    KeywordNode keywordNode = new KeywordNode(t.getValue());
+    SimpleKeywordNode keywordNode = new SimpleKeywordNode(t.getValue());
 
     Token ot = getCurentToken();
     if (ot != null && ot.getType() == Token.OPTION) {
-      List optionList = new OptionTokenizer((String) ot.getValue()).tokenize();
+      List<String> optionList = new OptionTokenizer((String) ot.getValue()).tokenize();
       keywordNode.setOptions(optionList);
       advanceTokenPointer();
     }
     return keywordNode;
   }
 
-  FormattingNode COMPOSITE() throws ScanException {
-    CompositeNode compositeNode = new CompositeNode();
+  FormattingNode COMPOSITE(String keyword) throws ScanException {
+    CompositeNode compositeNode = new CompositeNode(keyword);
 
     Node childNode = E();
     // System.out.println("Child node: " + childNode);
@@ -177,6 +204,12 @@ public class Parser<E> extends ContextAwareBase {
     } else {
       // System.out.println("got expected ')'");
     }
+    Token ot = getCurentToken();
+    if (ot != null && ot.getType() == Token.OPTION) {
+      List<String> optionList = new OptionTokenizer((String) ot.getValue()).tokenize();
+      compositeNode.setOptions(optionList);
+      advanceTokenPointer();
+    }
     return compositeNode;
   }
 
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/KeywordNode.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/SimpleKeywordNode.java
similarity index 71%
rename from logback-core/src/main/java/ch/qos/logback/core/pattern/parser/KeywordNode.java
rename to logback-core/src/main/java/ch/qos/logback/core/pattern/parser/SimpleKeywordNode.java
index 3d2879c..3eda998 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/KeywordNode.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/SimpleKeywordNode.java
@@ -1,60 +1,64 @@
-/**
- * Logback: the reliable, generic, fast and flexible logging framework.
- * Copyright (C) 1999-2009, QOS.ch. All rights reserved.
- *
- * This program and the accompanying materials are dual-licensed under
- * either the terms of the Eclipse Public License v1.0 as published by
- * the Eclipse Foundation
- *
- *   or (per the licensee's choosing)
- *
- * under the terms of the GNU Lesser General Public License version 2.1
- * as published by the Free Software Foundation.
- */
-package ch.qos.logback.core.pattern.parser;
-
-import java.util.List;
-
-public class KeywordNode extends FormattingNode {
-
-  List optionList;
-
-  KeywordNode(Object value) {
-    super(Node.KEYWORD, value);
-  }
-
-  public List getOptions() {
-    return optionList;
-  }
-
-  public void setOptions(List optionList) {
-    this.optionList = optionList;
-  }
-
-  public boolean equals(Object o) {
-    // System.out.println("Keyword.equals()");
-    if (!super.equals(o)) {
-      return false;
-    }
-
-    if (!(o instanceof KeywordNode)) {
-      return false;
-    }
-    KeywordNode r = (KeywordNode) o;
-
-    return (optionList != null ? optionList.equals(r.optionList)
-        : r.optionList == null);
-  }
-
-  public String toString() {
-    StringBuffer buf = new StringBuffer();
-    if (optionList == null) {
-      buf.append("KeyWord(" + value + "," + formatInfo + ")");
-    } else {
-      buf.append("KeyWord(" + value + ", " + formatInfo + "," + optionList
-          + ")");
-    }
-    buf.append(printNext());
-    return buf.toString();
-  }
-}
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * Copyright (C) 1999-2009, QOS.ch. All rights reserved.
+ *
+ * This program and the accompanying materials are dual-licensed under
+ * either the terms of the Eclipse Public License v1.0 as published by
+ * the Eclipse Foundation
+ *
+ *   or (per the licensee's choosing)
+ *
+ * under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation.
+ */
+package ch.qos.logback.core.pattern.parser;
+
+import java.util.List;
+
+public class SimpleKeywordNode extends FormattingNode {
+
+  List<String> optionList;
+
+  SimpleKeywordNode(Object value) {
+    super(Node.SIMPLE_KEYWORD, value);
+  }
+
+  protected  SimpleKeywordNode(int type, Object value) {
+    super(type, value);
+  }
+
+  public List<String> getOptions() {
+    return optionList;
+  }
+
+  public void setOptions(List<String> optionList) {
+    this.optionList = optionList;
+  }
+
+  public boolean equals(Object o) {
+    // System.out.println("Keyword.equals()");
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    if (!(o instanceof SimpleKeywordNode)) {
+      return false;
+    }
+    SimpleKeywordNode r = (SimpleKeywordNode) o;
+
+    return (optionList != null ? optionList.equals(r.optionList)
+        : r.optionList == null);
+  }
+
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    if (optionList == null) {
+      buf.append("KeyWord(" + value + "," + formatInfo + ")");
+    } else {
+      buf.append("KeyWord(" + value + ", " + formatInfo + "," + optionList
+          + ")");
+    }
+    buf.append(printNext());
+    return buf.toString();
+  }
+}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Token.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Token.java
index 2965a1c..1e8a385 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Token.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/Token.java
@@ -16,7 +16,7 @@ package ch.qos.logback.core.pattern.parser;
 class Token {
 
   static final int PERCENT = 37;
-  static final int LEFT_PARENTHESIS = 40;
+  //static final int LEFT_PARENTHESIS = 40;
   static final int RIGHT_PARENTHESIS = 41;
   static final int MINUS = 45;
   static final int DOT = 46;
@@ -24,14 +24,15 @@ class Token {
   static final int CURLY_RIGHT = 125;
   static final int LITERAL = 1000;
   static final int FORMAT_MODIFIER = 1002;
-  static final int KEYWORD = 1004;
+  static final int SIMPLE_KEYWORD = 1004;
+  static final int COMPOSITE_KEYWORD = 1005;
   static final int OPTION = 1006;
 
   static final int EOF = Integer.MAX_VALUE;
 
   static Token EOF_TOKEN = new Token(EOF, "EOF");
   static Token RIGHT_PARENTHESIS_TOKEN = new Token(RIGHT_PARENTHESIS);
-  static Token LEFT_PARENTHESIS_TOKEN = new Token(LEFT_PARENTHESIS);
+  static Token BARE_COMPOSITE_KEYWORD_TOKEN = new Token(COMPOSITE_KEYWORD, "BARE");
   static Token PERCENT_TOKEN = new Token(PERCENT);
 
   private final int type;
@@ -72,15 +73,15 @@ class Token {
       case OPTION:
         typeStr = "OPTION";
         break;
-      case KEYWORD:
-        typeStr = "KEYWORD";
+      case SIMPLE_KEYWORD:
+        typeStr = "SIMPLE_KEYWORD";
         break;
+      case COMPOSITE_KEYWORD:
+          typeStr = "COMPOSITE_KEYWORD";
+          break;
       case RIGHT_PARENTHESIS:
         typeStr = "RIGHT_PARENTHESIS";
         break;
-      case LEFT_PARENTHESIS:
-        typeStr = "LEFT_PARENTHESIS";
-        break;
      default:
         typeStr = "UNKNOWN";
     }
diff --git a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/TokenStream.java b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/TokenStream.java
index b0dc881..19d8311 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/TokenStream.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/pattern/parser/TokenStream.java
@@ -24,17 +24,17 @@ import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
  * <p>
  * Return a steady stream of tokens.
  * <p/>
- * 
+ * <p/>
  * <p>
- * The returned tokens are one of: LITERAL, '%', FORMAT_MODIFIER, KEYWWORD,
+ * The returned tokens are one of: LITERAL, '%', FORMAT_MODIFIER, SIMPLE_KEYWORD, COMPOSITE_KEYWORD
  * OPTION, LEFT_PARENTHESIS, and RIGHT_PARENTHESIS.
  * </p>
- * 
+ * <p/>
  * <p>
  * The '\' character is used as escape. It can be used to escape '_', '%', '('
  * and '('.
  * <p>
- * 
+ * <p/>
  * <p>
  * Note that there is no EOS token returned.
  * </p>
@@ -42,7 +42,6 @@ import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
 class TokenStream {
 
   private static final char ESCAPE_CHAR = '\\';
-  private static final char PERCENT_CHAR = CoreConstants.PERCENT_CHAR;
   private static final char CURLY_LEFT = '{';
   private static final char CURLY_RIGHT = '}';
 
@@ -66,7 +65,7 @@ class TokenStream {
   TokenStream(String pattern, IEscapeUtil escapeUtil) {
     if (pattern == null || pattern.length() == 0) {
       throw new IllegalArgumentException(
-          "null or empty pattern string not allowed");
+              "null or empty pattern string not allowed");
     }
     this.pattern = pattern;
     patternLength = pattern.length();
@@ -83,105 +82,121 @@ class TokenStream {
 
       switch (state) {
 
-      case LITERAL_STATE:
-        switch (c) {
-        case ESCAPE_CHAR:
-          escape("%()", buf);
+        case LITERAL_STATE:
+          handleLiteralState(c, tokenList, buf);
           break;
-        case PERCENT_CHAR:
-          addValuedToken(Token.LITERAL, buf, tokenList);
-          tokenList.add(Token.PERCENT_TOKEN);
-          state = FORMAT_MODIFIER_STATE;
+        //
+        case FORMAT_MODIFIER_STATE:
+          handleFormatModifierState(c, tokenList, buf);
           break;
-
-        case CoreConstants.RIGHT_PARENTHESIS_CHAR:
-          if (buf.length() >= 1 && buf.charAt(buf.length() - 1) == '\\') {
-            buf.deleteCharAt(buf.length() - 1);
-            buf.append(CoreConstants.RIGHT_PARENTHESIS_CHAR);
-          } else {
-            addValuedToken(Token.LITERAL, buf, tokenList);
-            tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
+        case OPTION_STATE:
+          switch (c) {
+            case CURLY_RIGHT:
+              addValuedToken(Token.OPTION, buf, tokenList);
+              state = LITERAL_STATE;
+              break;
+            case ESCAPE_CHAR:
+              escape("%{}", buf);
+              break;
+            default:
+              buf.append(c);
           }
           break;
+        case KEYWORD_STATE:
+          handleKeywordState(c, tokenList, buf);
+          break;
 
         default:
-          buf.append(c);
-        }
+      }
+    }
+
+    // EOS
+    switch (state) {
+      case LITERAL_STATE:
+        addValuedToken(Token.LITERAL, buf, tokenList);
         break;
-      //
-      case FORMAT_MODIFIER_STATE:
-        if (c == CoreConstants.LEFT_PARENTHESIS_CHAR) {
-          addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
-          tokenList.add(Token.LEFT_PARENTHESIS_TOKEN);
-          state = LITERAL_STATE;
-        } else if (Character.isJavaIdentifierStart(c)) {
-          addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
-          state = KEYWORD_STATE;
-          buf.append(c);
-        } else {
-          buf.append(c);
-        }
+      case KEYWORD_STATE:
+        tokenList.add(new Token(Token.SIMPLE_KEYWORD, buf.toString()));
+        buf.setLength(0);
         break;
+
+      case FORMAT_MODIFIER_STATE:
       case OPTION_STATE:
-        switch (c) {
-        case CURLY_RIGHT:
-          addValuedToken(Token.OPTION, buf, tokenList);
-          state = LITERAL_STATE;
-          break;
-        case ESCAPE_CHAR:
-          escape("%{}", buf);
-          break;
-        default:
-          buf.append(c);
-        }
+        throw new ScanException("Unexpected end of pattern string");
+    }
+
+    return tokenList;
+  }
+
+  private void handleFormatModifierState(char c, List<Token> tokenList, StringBuffer buf) {
+    if (c == CoreConstants.LEFT_PARENTHESIS_CHAR) {
+      addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
+      tokenList.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
+      state = LITERAL_STATE;
+    } else if (Character.isJavaIdentifierStart(c)) {
+      addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
+      state = KEYWORD_STATE;
+      buf.append(c);
+    } else {
+      buf.append(c);
+    }
+  }
+
+  private void handleLiteralState(char c, List<Token> tokenList, StringBuffer buf) {
+    switch (c) {
+      case ESCAPE_CHAR:
+        escape("%()", buf);
         break;
-      case KEYWORD_STATE:
-        if (c == CURLY_LEFT) {
-          addValuedToken(Token.KEYWORD, buf, tokenList);
-          state = OPTION_STATE;
-        } else if (Character.isJavaIdentifierPart(c)) {
-          buf.append(c);
-        } else if (c == PERCENT_CHAR) {
-          addValuedToken(Token.KEYWORD, buf, tokenList);
-          tokenList.add(Token.PERCENT_TOKEN);
-          state = FORMAT_MODIFIER_STATE;
+
+      case CoreConstants.PERCENT_CHAR:
+        addValuedToken(Token.LITERAL, buf, tokenList);
+        tokenList.add(Token.PERCENT_TOKEN);
+        state = FORMAT_MODIFIER_STATE;
+        break;
+
+      case CoreConstants.RIGHT_PARENTHESIS_CHAR:
+        if (buf.length() >= 1 && buf.charAt(buf.length() - 1) == '\\') {
+          buf.deleteCharAt(buf.length() - 1);
+          buf.append(CoreConstants.RIGHT_PARENTHESIS_CHAR);
         } else {
-          addValuedToken(Token.KEYWORD, buf, tokenList);
-          if (c == CoreConstants.RIGHT_PARENTHESIS_CHAR) {
-            // if c is a right parenthesis, then add it as such
-            tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
-          } else if (c == ESCAPE_CHAR) {
-            if ((pointer < patternLength)) {
-              char next = pattern.charAt(pointer++);
-              escapeUtil.escape("%()", buf, next, pointer);
-            }
-          } else {
-            buf.append(c);
-          }
-          state = LITERAL_STATE;
+          addValuedToken(Token.LITERAL, buf, tokenList);
+          tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
         }
         break;
 
       default:
-      }
+        buf.append(c);
     }
+  }
 
-    // EOS
-    switch (state) {
-    case LITERAL_STATE:
-      addValuedToken(Token.LITERAL, buf, tokenList);
-      break;
-    case KEYWORD_STATE:
-      tokenList.add(new Token(Token.KEYWORD, buf.toString()));
-      buf.setLength(0);
-      break;
-
-    case FORMAT_MODIFIER_STATE:
-    case OPTION_STATE:
-      throw new ScanException("Unexpected end of pattern string");
+  private void handleKeywordState(char c, List<Token> tokenList, StringBuffer buf) {
+    if (c == CURLY_LEFT) {
+      addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
+      state = OPTION_STATE;
+    } else if (Character.isJavaIdentifierPart(c)) {
+      buf.append(c);
+    } else if (c == CoreConstants.LEFT_PARENTHESIS_CHAR) {
+      addValuedToken(Token.COMPOSITE_KEYWORD, buf, tokenList);
+      state = LITERAL_STATE;
+    } else if (c == CoreConstants.PERCENT_CHAR) {
+      addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
+      tokenList.add(Token.PERCENT_TOKEN);
+      state = FORMAT_MODIFIER_STATE;
+    } else {
+      addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
+      if (c == CoreConstants.RIGHT_PARENTHESIS_CHAR) {
+        // if c is a right parenthesis, then add it as such
+        tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
+      } else if (c == ESCAPE_CHAR) {
+        if ((pointer < patternLength)) {
+          char next = pattern.charAt(pointer++);
+          escapeUtil.escape("%()", buf, next, pointer);
+        }
+      } else {
+        buf.append(c);
+      }
+      state = LITERAL_STATE;
     }
-
-    return tokenList;
   }
 
   void escape(String escapeChars, StringBuffer buf) {
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileNamePattern.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileNamePattern.java
index 88adfe6..be3c8b5 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileNamePattern.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileNamePattern.java
@@ -66,7 +66,7 @@ public class FileNamePattern extends ContextAwareBase {
       Parser<Object> p = new Parser<Object>(patternForParsing, new AlmostAsIsEscapeUtil());
       p.setContext(context);
       Node t = p.parse();
-      this.headTokenConverter = p.compile(t, CONVERTER_MAP);
+      this.headTokenConverter = p.compile(t, CONVERTER_MAP, Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
 
     } catch (ScanException sce) {
       addError("Failed to parse pattern \"" + pattern + "\".", sce);
diff --git a/logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java b/logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java
index 1e7cc80..235b30a 100644
--- a/logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java
+++ b/logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java
@@ -30,7 +30,5 @@ import org.junit.runners.Suite.SuiteClasses;
   ch.qos.logback.core.sift.PackageTest.class, 
   ch.qos.logback.core.encoder.PackageTest.class,
   ch.qos.logback.core.recovery.PackageTest.class})
-public class
-        AllCoreTest {
-
+public class AllCoreTest {
 }
diff --git a/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/CompilerTest.java b/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/CompilerTest.java
index 8c64c59..39bb596 100644
--- a/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/CompilerTest.java
+++ b/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/CompilerTest.java
@@ -29,7 +29,7 @@ import ch.qos.logback.core.pattern.Converter123;
 import ch.qos.logback.core.pattern.ConverterHello;
 import ch.qos.logback.core.status.StatusChecker;
 import ch.qos.logback.core.util.StatusPrinter;
-
+import static ch.qos.logback.core.pattern.parser.Parser.DEFAULT_COMPOSITE_CONVERTER_MAP;
 
 public class CompilerTest  {
 
@@ -56,7 +56,7 @@ public class CompilerTest  {
   public void testLiteral() throws Exception {
     Parser<Object> p = new Parser<Object>("hello");
     Node t = p.parse();
-    Converter<Object> head = p.compile(t, converterMap);
+    Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
     String result = write(head, new Object());
     assertEquals("hello", result);
   }
@@ -67,7 +67,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc Hello", result); 
     }
@@ -75,7 +75,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %hello %OTT");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc Hello 123", result);
     }
@@ -87,7 +87,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %7hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc   Hello", result);
     }
@@ -96,7 +96,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %-7hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc Hello  ", result);
     }
@@ -105,7 +105,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %.3hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc llo", result);
     }
@@ -114,7 +114,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %.-3hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc Hel", result);
     }
@@ -123,7 +123,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %4.5OTT");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc  123", result);
     }
@@ -131,7 +131,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %-4.5OTT");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc 123 ", result);
     }
@@ -139,7 +139,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %3.4hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc ello", result);
     }
@@ -147,7 +147,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("abc %-3.-4hello");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("abc Hell", result);
     }
@@ -168,7 +168,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("%(ABC %hello)");
       p.setContext(c);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       StatusPrinter.print(c);
       assertEquals("ABC Hello", result);
@@ -177,7 +177,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("%(ABC %hello)");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap,DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("ABC Hello", result);
     }
@@ -189,7 +189,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("xyz %4.10(ABC)");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("xyz  ABC", result);
     }
@@ -198,7 +198,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("xyz %-4.10(ABC)");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("xyz ABC ", result);
     }
@@ -207,7 +207,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("xyz %.2(ABC %hello)");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("xyz lo", result);
     }
@@ -216,7 +216,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("xyz %.-2(ABC)");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("xyz AB", result);
     }
@@ -225,7 +225,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("xyz %30.30(ABC %20hello)");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("xyz       ABC                Hello", result);
     }
@@ -236,7 +236,7 @@ public class CompilerTest  {
     Parser<Object> p = new Parser<Object>("%unknown");
     p.setContext(context);
     Node t = p.parse();
-    p.compile(t, converterMap);
+    p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
     StatusChecker chercker = new StatusChecker(context.getStatusManager());
     assertTrue(chercker
         .containsMatch("\\[unknown] is not a valid conversion word"));
@@ -248,7 +248,7 @@ public class CompilerTest  {
       Parser<Object> p = new Parser<Object>("xyz %hello\\_world");
       p.setContext(context);
       Node t = p.parse();
-      Converter<Object> head = p.compile(t, converterMap);
+      Converter<Object> head = p.compile(t, converterMap, DEFAULT_COMPOSITE_CONVERTER_MAP);
       String result = write(head, new Object());
       assertEquals("xyz Helloworld", result);
     }
diff --git a/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/ParserTest.java b/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/ParserTest.java
index 6fab0fc..e220964 100644
--- a/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/ParserTest.java
+++ b/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/ParserTest.java
@@ -25,6 +25,8 @@ import ch.qos.logback.core.pattern.FormatInfo;
 
 public class ParserTest {
 
+  String BARE = Token.BARE_COMPOSITE_KEYWORD_TOKEN.getValue().toString();
+
   @Test
   public void testBasic() throws Exception {
     Parser p = new Parser("hello");
@@ -40,7 +42,7 @@ public class ParserTest {
       Parser p = new Parser("hello%xyz");
       Node t = p.parse();
       Node witness = new Node(Node.LITERAL, "hello");
-      witness.next = new KeywordNode("xyz");
+      witness.next = new SimpleKeywordNode("xyz");
       assertEquals(witness, t);
     }
 
@@ -48,7 +50,7 @@ public class ParserTest {
       Parser p = new Parser("hello%xyz{x}");
       Node t = p.parse();
       Node witness = new Node(Node.LITERAL, "hello");
-      KeywordNode n = new KeywordNode("xyz");
+      SimpleKeywordNode n = new SimpleKeywordNode("xyz");
       List<String> optionList = new ArrayList<String>();
       optionList.add("x");
       n.setOptions(optionList);
@@ -64,8 +66,8 @@ public class ParserTest {
       Node t = p.parse();
 
       Node witness = new Node(Node.LITERAL, "hello");
-      CompositeNode composite = new CompositeNode();
-      Node child = new KeywordNode("child");
+      CompositeNode composite = new CompositeNode(BARE);
+      Node child = new SimpleKeywordNode("child");
       composite.setChildNode(child);
       witness.next = composite;
 
@@ -81,8 +83,8 @@ public class ParserTest {
       Node t = p.parse();
 
       Node witness = new Node(Node.LITERAL, "hello");
-      CompositeNode composite = new CompositeNode();
-      Node child = new KeywordNode("child");
+      CompositeNode composite = new CompositeNode(BARE);
+      Node child = new SimpleKeywordNode("child");
       composite.setChildNode(child);
       witness.next = composite;
       child.next = new Node(Node.LITERAL, " ");
@@ -93,11 +95,11 @@ public class ParserTest {
       Parser p = new Parser("hello%(%child %h)");
       Node t = p.parse();
       Node witness = new Node(Node.LITERAL, "hello");
-      CompositeNode composite = new CompositeNode();
-      Node child = new KeywordNode("child");
+      CompositeNode composite = new CompositeNode(BARE);
+      Node child = new SimpleKeywordNode("child");
       composite.setChildNode(child);
       child.next = new Node(Node.LITERAL, " ");
-      child.next.next = new KeywordNode("h");
+      child.next.next = new SimpleKeywordNode("h");
       witness.next = composite;
       assertEquals(witness, t);
     }
@@ -106,14 +108,14 @@ public class ParserTest {
       Parser p = new Parser("hello%(%child %h) %m");
       Node t = p.parse();
       Node witness = new Node(Node.LITERAL, "hello");
-      CompositeNode composite = new CompositeNode();
-      Node child = new KeywordNode("child");
+      CompositeNode composite = new CompositeNode(BARE);
+      Node child = new SimpleKeywordNode("child");
       composite.setChildNode(child);
       child.next = new Node(Node.LITERAL, " ");
-      child.next.next = new KeywordNode("h");
+      child.next.next = new SimpleKeywordNode("h");
       witness.next = composite;
       composite.next = new Node(Node.LITERAL, " ");
-      composite.next.next = new KeywordNode("m");
+      composite.next.next = new SimpleKeywordNode("m");
       assertEquals(witness, t);
     }
 
@@ -121,17 +123,17 @@ public class ParserTest {
       Parser p = new Parser("hello%( %child \\(%h\\) ) %m");
       Node t = p.parse();
       Node witness = new Node(Node.LITERAL, "hello");
-      CompositeNode composite = new CompositeNode();
+      CompositeNode composite = new CompositeNode(BARE);
       Node child = new Node(Node.LITERAL, " ");
       composite.setChildNode(child);
       Node c = child;
-      c = c.next = new KeywordNode("child");
+      c = c.next = new SimpleKeywordNode("child");
       c = c.next = new Node(Node.LITERAL, " (");
-      c = c.next = new KeywordNode("h");
+      c = c.next = new SimpleKeywordNode("h");
       c = c.next = new Node(Node.LITERAL, ") ");
       witness.next = composite;
       composite.next = new Node(Node.LITERAL, " ");
-      composite.next.next = new KeywordNode("m");
+      composite.next.next = new SimpleKeywordNode("m");
       assertEquals(witness, t);
 
     }
@@ -142,15 +144,15 @@ public class ParserTest {
     {
       Parser p = new Parser("%top %(%child%(%h))");
       Node t = p.parse();
-      Node witness = new KeywordNode("top");
+      Node witness = new SimpleKeywordNode("top");
       Node w = witness.next = new Node(Node.LITERAL, " ");
-      CompositeNode composite = new CompositeNode();
+      CompositeNode composite = new CompositeNode(BARE);
       w = w.next = composite;
-      Node child = new KeywordNode("child");
+      Node child = new SimpleKeywordNode("child");
       composite.setChildNode(child);
-      composite = new CompositeNode();
+      composite = new CompositeNode(BARE);
       child.next = composite;
-      composite.setChildNode(new KeywordNode("h"));
+      composite.setChildNode(new SimpleKeywordNode("h"));
 
       assertEquals(witness, t);
     }
@@ -161,14 +163,14 @@ public class ParserTest {
     {
       Parser p = new Parser("%45x");
       Node t = p.parse();
-      FormattingNode witness = new KeywordNode("x");
+      FormattingNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(45, Integer.MAX_VALUE));
       assertEquals(witness, t);
     }
     {
       Parser p = new Parser("%4.5x");
       Node t = p.parse();
-      FormattingNode witness = new KeywordNode("x");
+      FormattingNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(4, 5));
       assertEquals(witness, t);
     }
@@ -176,14 +178,14 @@ public class ParserTest {
     {
       Parser p = new Parser("%-4.5x");
       Node t = p.parse();
-      FormattingNode witness = new KeywordNode("x");
+      FormattingNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(4, 5, false, true));
       assertEquals(witness, t);
     }
     {
       Parser p = new Parser("%-4.-5x");
       Node t = p.parse();
-      FormattingNode witness = new KeywordNode("x");
+      FormattingNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(4, 5, false, false));
       assertEquals(witness, t);
     }
@@ -191,10 +193,10 @@ public class ParserTest {
     {
       Parser p = new Parser("%-4.5x %12y");
       Node t = p.parse();
-      FormattingNode witness = new KeywordNode("x");
+      FormattingNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(4, 5, false, true));
       Node n = witness.next = new Node(Node.LITERAL, " ");
-      n = n.next = new KeywordNode("y");
+      n = n.next = new SimpleKeywordNode("y");
       ((FormattingNode) n).setFormatInfo(new FormatInfo(12, Integer.MAX_VALUE));
       assertEquals(witness, t);
     }
@@ -205,7 +207,7 @@ public class ParserTest {
     {
       Parser p = new Parser("%45x{'test '}");
       Node t = p.parse();
-      KeywordNode witness = new KeywordNode("x");
+      SimpleKeywordNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(45, Integer.MAX_VALUE));
       List<String> ol = new ArrayList<String>();
       ol.add("test ");
@@ -216,7 +218,7 @@ public class ParserTest {
     {
       Parser p = new Parser("%45x{a, b}");
       Node t = p.parse();
-      KeywordNode witness = new KeywordNode("x");
+      SimpleKeywordNode witness = new SimpleKeywordNode("x");
       witness.setFormatInfo(new FormatInfo(45, Integer.MAX_VALUE));
       List<String> ol = new ArrayList<String>();
       ol.add("a");
@@ -232,7 +234,7 @@ public class ParserTest {
       {
       Parser p = new Parser("%x{}a");
       Node t = p.parse();
-      KeywordNode witness = new KeywordNode("x");
+      SimpleKeywordNode witness = new SimpleKeywordNode("x");
       witness.next = new Node(Node.LITERAL, "a");
       assertEquals(witness, t);
     }
@@ -246,7 +248,7 @@ public class ParserTest {
       Node t = p.parse();
 
       Node witness = new Node(Node.LITERAL, "hello");
-      CompositeNode composite = new CompositeNode();
+      CompositeNode composite = new CompositeNode(BARE);
       composite.setFormatInfo(new FormatInfo(5, Integer.MAX_VALUE));
       Node child = new Node(Node.LITERAL, "XYZ");
       composite.setChildNode(child);
diff --git a/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/TokenStreamTest.java b/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/TokenStreamTest.java
index d737a56..c9d12a0 100644
--- a/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/TokenStreamTest.java
+++ b/logback-core/src/test/java/ch/qos/logback/core/pattern/parser/TokenStreamTest.java
@@ -74,7 +74,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%c").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "c"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "c"));
       assertEquals(witness, tl);
     }
 
@@ -83,9 +83,9 @@ public class TokenStreamTest {
       List tl = new TokenStream("%a%b").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "a"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "a"));
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "b"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "b"));
       assertEquals(witness, tl);
     }
 
@@ -96,7 +96,7 @@ public class TokenStreamTest {
       witness.add(new Token(Token.LITERAL, "xyz"));
       witness.add(Token.PERCENT_TOKEN);
       witness.add(new Token(Token.FORMAT_MODIFIER, "-34"));
-      witness.add(new Token(Token.KEYWORD, "c"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "c"));
       assertEquals(witness, tl);
     }
   }
@@ -106,15 +106,15 @@ public class TokenStreamTest {
     List tl = new TokenStream("%d{1234} [%34.-67toto] %n").tokenize();
     List<Token> witness = new ArrayList<Token>();
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "d"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "d"));
     witness.add(new Token(Token.OPTION, "1234"));
     witness.add(new Token(Token.LITERAL, " ["));
     witness.add(Token.PERCENT_TOKEN);
     witness.add(new Token(Token.FORMAT_MODIFIER, "34.-67"));
-    witness.add(new Token(Token.KEYWORD, "toto"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "toto"));
     witness.add(new Token(Token.LITERAL, "] "));
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "n"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "n"));
     assertEquals(witness, tl);
   }
 
@@ -132,7 +132,7 @@ public class TokenStreamTest {
     List tl = new TokenStream("%()").tokenize();
     List<Token> witness = new ArrayList<Token>();
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
     assertEquals(witness, tl);
   }
@@ -158,7 +158,7 @@ public class TokenStreamTest {
       List<Token> witness = new ArrayList<Token>();
       witness.add(new Token(Token.LITERAL, "\\"));
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       assertEquals(witness, tl);
     }
 
@@ -166,7 +166,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x\\)").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(new Token(Token.LITERAL, ")"));
       assertEquals(witness, tl);
     }
@@ -175,7 +175,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x\\_a").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(new Token(Token.LITERAL, "a"));
       assertEquals(witness, tl);
     }
@@ -183,9 +183,9 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x\\_%b").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "b"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "b"));
       assertEquals(witness, tl);
     }
   }
@@ -196,7 +196,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x{t}").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(new Token(Token.OPTION, "t"));
       assertEquals(witness, tl);
     }
@@ -205,7 +205,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x{t,y}").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(new Token(Token.OPTION, "t,y"));
       assertEquals(witness, tl);
     }
@@ -214,7 +214,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x{\"hello world.\", \"12y  \"}").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(new Token(Token.OPTION, "\"hello world.\", \"12y  \""));
       assertEquals(witness, tl);
     }
@@ -223,7 +223,7 @@ public class TokenStreamTest {
       List tl = new TokenStream("%x{opt\\}}").tokenize();
       List<Token> witness = new ArrayList<Token>();
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "x"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "x"));
       witness.add(new Token(Token.OPTION, "opt}"));
       assertEquals(witness, tl);
     }
@@ -234,10 +234,10 @@ public class TokenStreamTest {
     List tl = new TokenStream("%(hello %class{.4?})").tokenize();
     List<Token> witness = new ArrayList<Token>();
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(new Token(Token.LITERAL, "hello "));
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "class"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "class"));
     witness.add(new Token(Token.OPTION, ".4?"));
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
     assertEquals(witness, tl);
@@ -249,14 +249,14 @@ public class TokenStreamTest {
     List<Token> witness = new ArrayList<Token>();
     witness.add(new Token(Token.LITERAL, "X "));
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "a"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "a"));
     witness.add(new Token(Token.LITERAL, " "));
     witness.add(Token.PERCENT_TOKEN);
     witness.add(new Token(Token.FORMAT_MODIFIER, "-12.550"));
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(new Token(Token.LITERAL, "hello "));
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "class"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "class"));
     witness.add(new Token(Token.OPTION, ".4?"));
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
     assertEquals(witness, tl);
@@ -268,18 +268,18 @@ public class TokenStreamTest {
     List<Token> witness = new ArrayList<Token>();
     witness.add(Token.PERCENT_TOKEN);
     witness.add(new Token(Token.FORMAT_MODIFIER, "-1"));
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "d"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "d"));
     witness.add(new Token(Token.LITERAL, " "));
     witness.add(Token.PERCENT_TOKEN);
     witness.add(new Token(Token.FORMAT_MODIFIER, "45"));
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "class"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "class"));
     witness.add(new Token(Token.LITERAL, " "));
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "file"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "file"));
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
 
@@ -291,13 +291,13 @@ public class TokenStreamTest {
     List tl = new TokenStream("%(%a%(%b))").tokenize();
     List<Token> witness = new ArrayList<Token>();
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "a"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "a"));
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(Token.LEFT_PARENTHESIS_TOKEN);
+    witness.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
     witness.add(Token.PERCENT_TOKEN);
-    witness.add(new Token(Token.KEYWORD, "b"));
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "b"));
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
     witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
 
@@ -312,7 +312,7 @@ public class TokenStreamTest {
       List<Token> witness = new ArrayList<Token>();
       witness.add(new Token(Token.LITERAL, "("));
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "h"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "h"));
       witness.add(new Token(Token.LITERAL, ")"));
       assertEquals(witness, tl);
     }
@@ -321,7 +321,7 @@ public class TokenStreamTest {
       List<Token> witness = new ArrayList<Token>();
       witness.add(new Token(Token.LITERAL, "("));
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "h"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "h"));
       witness.add(new Token(Token.LITERAL, ")"));
       assertEquals(witness, tl);
     }
@@ -329,14 +329,54 @@ public class TokenStreamTest {
 
   @Test
   public void testWindowsLikeBackSlashes() throws ScanException {
-    {
-      List tl = new TokenStream("c:\\hello\\world.%i",
-          new AlmostAsIsEscapeUtil()).tokenize();
+    List tl = new TokenStream("c:\\hello\\world.%i",
+            new AlmostAsIsEscapeUtil()).tokenize();
+
+    List<Token> witness = new ArrayList<Token>();
+    witness.add(new Token(Token.LITERAL, "c:\\hello\\world."));
+    witness.add(Token.PERCENT_TOKEN);
+    witness.add(new Token(Token.SIMPLE_KEYWORD, "i"));
+    assertEquals(witness, tl);
+  }
 
+  @Test
+  public void compositedKeyword() throws ScanException {
+    {
+      List tl = new TokenStream("%d(A)",
+              new AlmostAsIsEscapeUtil()).tokenize();
+      List<Token> witness = new ArrayList<Token>();
+      witness.add(Token.PERCENT_TOKEN);
+      witness.add(new Token(Token.COMPOSITE_KEYWORD, "d"));
+      witness.add(new Token(Token.LITERAL, "A"));
+      witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
+      assertEquals(witness, tl);
+    }
+    {
+      List tl = new TokenStream("a %subst(%b C)",
+              new AlmostAsIsEscapeUtil()).tokenize();
       List<Token> witness = new ArrayList<Token>();
-      witness.add(new Token(Token.LITERAL, "c:\\hello\\world."));
+      witness.add(new Token(Token.LITERAL, "a "));
+      witness.add(Token.PERCENT_TOKEN);
+      witness.add(new Token(Token.COMPOSITE_KEYWORD, "subst"));
       witness.add(Token.PERCENT_TOKEN);
-      witness.add(new Token(Token.KEYWORD, "i"));
+      witness.add(new Token(Token.SIMPLE_KEYWORD, "b"));
+      witness.add(new Token(Token.LITERAL, " C"));
+      witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
+      assertEquals(witness, tl);
+    }
+  }
+  @Test
+  public void compositedKeywordFollowedByOptions() throws ScanException {
+    {
+      List tl = new TokenStream("%d(A){o}",
+              new AlmostAsIsEscapeUtil()).tokenize();
+      List<Token> witness = new ArrayList<Token>();
+      witness.add(Token.PERCENT_TOKEN);
+      witness.add(new Token(Token.COMPOSITE_KEYWORD, "d"));
+      witness.add(new Token(Token.LITERAL, "A"));
+      witness.add(Token.RIGHT_PARENTHESIS_TOKEN);
+      witness.add(new Token(Token.OPTION, "o"));
+
       assertEquals(witness, tl);
     }
   }

-----------------------------------------------------------------------

Summary of changes:
 goscp                                              |    3 +
 .../qos/logback/classic/pattern/DateConverter.java |    1 +
 .../classic/turbo/ReconfigureOnChangeTest.java     |    2 +-
 .../ch/qos/logback/core/OutputStreamAppender.java  |    5 +-
 .../ch/qos/logback/core/html/HTMLLayoutBase.java   |    2 +-
 .../logback/core/pattern/CompositeConverter.java   |   16 +-
 .../ch/qos/logback/core/pattern/ConverterUtil.java |   15 +-
 .../qos/logback/core/pattern/DynamicConverter.java |    8 +-
 .../core/pattern/IdentityCompositeConverter.java   |   13 +-
 .../logback/core/pattern/PatternLayoutBase.java    |    2 +-
 .../core/pattern/ReplacingCompositeConverter.java  |   25 ++--
 .../qos/logback/core/pattern/parser/Compiler.java  |  109 +++++++----
 .../logback/core/pattern/parser/CompositeNode.java |    7 +-
 .../ch/qos/logback/core/pattern/parser/Node.java   |    4 +-
 .../core/pattern/parser/OptionTokenizer.java       |    2 +-
 .../ch/qos/logback/core/pattern/parser/Parser.java |   89 +++++++---
 .../{KeywordNode.java => SimpleKeywordNode.java}   |  124 +++++++------
 .../ch/qos/logback/core/pattern/parser/Token.java  |   17 +-
 .../logback/core/pattern/parser/TokenStream.java   |  189 +++++++++++---------
 .../core/rolling/helper/FileNamePattern.java       |    2 +-
 .../test/java/ch/qos/logback/core/AllCoreTest.java |    4 +-
 .../logback/core/pattern/parser/CompilerTest.java  |   42 +++---
 .../logback/core/pattern/parser/ParserTest.java    |   66 ++++----
 .../core/pattern/parser/TokenStreamTest.java       |  116 ++++++++----
 24 files changed, 495 insertions(+), 368 deletions(-)
 create mode 100644 goscp
 copy logback-classic/src/main/groovy/ch/qos/logback/classic/gaffer/NestedType.groovy => logback-core/src/main/java/ch/qos/logback/core/pattern/IdentityCompositeConverter.java (73%)
 copy logback-classic/src/main/groovy/ch/qos/logback/classic/gaffer/AppenderDelegate.groovy => logback-core/src/main/java/ch/qos/logback/core/pattern/ReplacingCompositeConverter.java (53%)
 rename logback-core/src/main/java/ch/qos/logback/core/pattern/parser/{KeywordNode.java => SimpleKeywordNode.java} (71%)


hooks/post-receive
-- 
Logback: the generic, reliable, fast and flexible logging framework.


More information about the logback-dev mailing list