You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by mb...@apache.org on 2008/02/25 20:51:26 UTC

svn commit: r630969 - in /commons/proper/lang/trunk/src: java/org/apache/commons/lang/text/ test/org/apache/commons/lang/text/

Author: mbenson
Date: Mon Feb 25 11:51:18 2008
New Revision: 630969

URL: http://svn.apache.org/viewvc?rev=630969&view=rev
Log:
[LANG-362] simplify ExtendedMessageFormat design; recycle as much of super implementation as possible

Added:
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/FormatFactory.java   (with props)
Removed:
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ChoiceMetaFormat.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormat.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormatSupport.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DefaultMetaFormatFactory.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/MetaFormatSupport.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NameKeyedMetaFormat.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NumberMetaFormat.java
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/TimeMetaFormat.java
Modified:
    commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java
    commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java
    commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java

Modified: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java?rev=630969&r1=630968&r2=630969&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java (original)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java Mon Feb 25 11:51:18 2008
@@ -20,25 +20,19 @@
 import java.text.MessageFormat;
 import java.text.ParsePosition;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.Locale;
+import java.util.Map;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.Validate;
 
 /**
  * Extends <code>MessageFormat</code> to allow pluggable/additional formatting
- * options for embedded format elements; requires a "meta-format", that is a
- * <code>Format</code> capable of parsing and formatting other
- * <code>Format</code>s.
- * 
- * Limitations:
- * <ul>
- * <li><code>toPattern()</code> results are tailored to JDK 1.4+ output and
- * will produce fairly drastically different results on earlier JDKs.</li>
- * <li>Recursive choice formats do not inherit knowledge of the extended
- * formatters and are limited to those available with
- * <code>java.text.MessageFormat</code>.</li>
- * </ul>
+ * options for embedded format elements; requires elaboration.
+ *
+ * Note that the mutator methods for the replacement Formats are to be considered
+ * unnecessary and thus have been disabled (UnsupportedOperationException).
  * 
  * @author Matt Benson
  * @since 2.4
@@ -47,389 +41,401 @@
 public class ExtendedMessageFormat extends MessageFormat {
     private static final long serialVersionUID = -2362048321261811743L;
 
+    private static final String DUMMY_PATTERN = "";
+    private static final String ESCAPED_QUOTE = "''";
+    private static final char START_FMT = ',';
+    private static final char END_FE = '}';
+    private static final char START_FE = '{';
+    private static final char QUOTE = '\'';
+
+    private String toPattern;
+    private Map registry;
+
     /**
-     * Get a default meta-format for the default Locale. This will produce
-     * behavior identical to a <code>java.lang.MessageFormat</code> using the
-     * default locale.
+     * Create a new ExtendedMessageFormat for the default locale.
      * 
-     * @return Format
+     * @param pattern String
+     * @throws IllegalArgumentException in case of a bad pattern.
      */
-    public static Format createDefaultMetaFormat() {
-        return createDefaultMetaFormat(Locale.getDefault());
+    public ExtendedMessageFormat(String pattern) {
+        this(pattern, Locale.getDefault());
     }
 
     /**
-     * Get a default meta-format for the specified Locale. This will produce
-     * behavior identical to a <code>java.lang.MessageFormat</code> using
-     * <code>locale</code>.
+     * Create a new ExtendedMessageFormat.
      * 
-     * @param locale the Locale for the resulting Format instance.
-     * @return Format
+     * @param pattern String
+     * @param locale Locale
+     * @throws IllegalArgumentException in case of a bad pattern.
      */
-    public static Format createDefaultMetaFormat(Locale locale) {
-        return DefaultMetaFormatFactory.getFormat(locale);
+    public ExtendedMessageFormat(String pattern, Locale locale) {
+        this(pattern, locale, null);
     }
 
     /**
-     * Conceptual demarcation of methods to parse the pattern.
+     * Create a new ExtendedMessageFormat for the default locale.
+     * 
+     * @param pattern String
+     * @param registry Registry of format factories:  Map<String, FormatFactory>
+     * @throws IllegalArgumentException in case of a bad pattern.
      */
-    private static class Parser {
-        private static final String ESCAPED_QUOTE = "''";
-        private static final char START_FMT = ',';
-        private static final char END_FE = '}';
-        private static final char START_FE = '{';
-        private static final char QUOTE = '\'';
-
-        /**
-         * Strip all formats from the pattern.
-         * 
-         * @param pattern String to strip
-         * @return stripped pattern
-         */
-        private String stripFormats(String pattern) {
-            StringBuffer sb = new StringBuffer(pattern.length());
-            ParsePosition pos = new ParsePosition(0);
-            char[] c = pattern.toCharArray();
-            while (pos.getIndex() < pattern.length()) {
-                switch (c[pos.getIndex()]) {
-                case QUOTE:
-                    appendQuotedString(pattern, pos, sb, true);
-                    break;
-                case START_FE:
-                    int start = pos.getIndex();
-                    readArgumentIndex(pattern, next(pos));
-                    sb.append(c, start, pos.getIndex() - start);
-                    if (c[pos.getIndex()] == START_FMT) {
-                        eatFormat(pattern, next(pos));
-                    }
-                    if (c[pos.getIndex()] != END_FE) {
-                        throw new IllegalArgumentException(
-                                "Unreadable format element at position "
-                                        + start);
-                    }
-                    // fall through
-                default:
-                    sb.append(c[pos.getIndex()]);
-                    next(pos);
-                }
-            }
-            return sb.toString();
-        }
+    public ExtendedMessageFormat(String pattern, Map registry) {
+        this(pattern, Locale.getDefault(), registry);
+    }
 
-        /**
-         * Insert formats back into the pattern for toPattern() support.
-         * 
-         * @param pattern source
-         * @param formats the Formats to insert
-         * @param metaFormat Format to format the Formats
-         * @return full pattern
-         */
-        private String insertFormats(String pattern, Format[] formats,
-                Format metaFormat) {
-            if (formats == null || formats.length == 0) {
-                return pattern;
-            }
-            StringBuffer sb = new StringBuffer(pattern.length() * 2);
-            ParsePosition pos = new ParsePosition(0);
-            int fe = -1;
-            while (pos.getIndex() < pattern.length()) {
-                char c = pattern.charAt(pos.getIndex());
-                switch (c) {
-                case QUOTE:
-                    appendQuotedString(pattern, pos, sb, false);
-                    break;
-                case START_FE:
-                    fe++;
-                    sb.append(START_FE).append(
-                            readArgumentIndex(pattern, next(pos)));
-                    if (formats[fe] != null) {
-                        String formatName = metaFormat.format(formats[fe]);
-                        if (StringUtils.isNotEmpty(formatName)) {
-                            sb.append(START_FMT).append(formatName);
-                        }
-                    }
-                    break;
-                default:
-                    sb.append(pattern.charAt(pos.getIndex()));
-                    next(pos);
-                }
-            }
-            return sb.toString();
-        }
+    /**
+     * Create a new ExtendedMessageFormat.
+     * 
+     * @param pattern String
+     * @param locale Locale
+     * @param registry Registry of format factories:  Map<String, FormatFactory>
+     * @throws IllegalArgumentException in case of a bad pattern.
+     */
+    public ExtendedMessageFormat(String pattern, Locale locale, Map registry) {
+        super(DUMMY_PATTERN, locale);
+        this.registry = registry;
+        applyPattern(pattern);
+    }
 
-        /**
-         * Parse the formats from the given pattern.
-         * 
-         * @param pattern String to parse
-         * @param metaFormat Format to parse the Formats
-         * @return array of parsed Formats
-         */
-        private Format[] parseFormats(String pattern, Format metaFormat) {
-            ArrayList result = new ArrayList();
-            ParsePosition pos = new ParsePosition(0);
-            while (pos.getIndex() < pattern.length()) {
-                switch (pattern.charAt(pos.getIndex())) {
-                case QUOTE:
-                    getQuotedString(pattern, next(pos), true);
-                    break;
-                case START_FE:
-                    int start = pos.getIndex();
-                    readArgumentIndex(pattern, next(pos));
-                    if (pattern.charAt(pos.getIndex()) == START_FMT) {
-                        seekNonWs(pattern, next(pos));
-                    }
-                    result.add(metaFormat.parseObject(pattern, pos));
-                    seekNonWs(pattern, pos);
-                    if (pattern.charAt(pos.getIndex()) != END_FE) {
-                        throw new IllegalArgumentException(
-                                "Unreadable format element at position "
-                                        + start);
-                    }
-                    // fall through
-                default:
-                    next(pos);
-                }
-            }
-            return (Format[]) result.toArray(new Format[result.size()]);
-        }
+    /**
+     * {@inheritDoc}
+     */
+    public String toPattern() {
+        return toPattern;
+    }
 
-        /**
-         * Consume whitespace from the current parse position.
-         * 
-         * @param pattern String to read
-         * @param pos current position
-         */
-        private void seekNonWs(String pattern, ParsePosition pos) {
-            int len = 0;
-            char[] buffer = pattern.toCharArray();
-            do {
-                len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex());
-                pos.setIndex(pos.getIndex() + len);
-            } while (len > 0 && pos.getIndex() < pattern.length());
-        }
-
-        /**
-         * Convenience method to advance parse position by 1
-         * 
-         * @param pos ParsePosition
-         * @return <code>pos</code>
-         */
-        private ParsePosition next(ParsePosition pos) {
-            pos.setIndex(pos.getIndex() + 1);
-            return pos;
-        }
-
-        /**
-         * Read the argument index from the current format element
-         * 
-         * @param pattern pattern to parse
-         * @param pos current parse position
-         * @return argument index as string
-         */
-        private String readArgumentIndex(String pattern, ParsePosition pos) {
-            int start = pos.getIndex();
-            for (; pos.getIndex() < pattern.length(); next(pos)) {
-                char c = pattern.charAt(pos.getIndex());
-                if (c == START_FMT || c == END_FE) {
-                    return pattern.substring(start, pos.getIndex());
+    /**
+     * Apply the specified pattern.
+     * 
+     * @param pattern String
+     */
+    public final void applyPattern(String pattern) {
+        if (registry == null) {
+            super.applyPattern(pattern);
+            toPattern = super.toPattern();
+            return;
+        }
+        ArrayList foundFormats = new ArrayList();
+        ArrayList foundDescriptions = new ArrayList();
+        StringBuffer stripCustom = new StringBuffer(pattern.length());
+
+        ParsePosition pos = new ParsePosition(0);
+        char[] c = pattern.toCharArray();
+        int fmtCount = 0;
+        while (pos.getIndex() < pattern.length()) {
+            switch (c[pos.getIndex()]) {
+            case QUOTE:
+                appendQuotedString(pattern, pos, stripCustom, true);
+                break;
+            case START_FE:
+                fmtCount++;
+                seekNonWs(pattern, pos);
+                int start = pos.getIndex();
+                int index = readArgumentIndex(pattern, next(pos));
+                stripCustom.append(START_FE).append(index);
+                seekNonWs(pattern, pos);
+                Format format = null;
+                String formatDescription = null;
+                if (c[pos.getIndex()] == START_FMT) {
+                    formatDescription = parseFormatDescription(pattern,
+                            next(pos));
+                    format = getFormat(formatDescription);
+                    if (format == null) {
+                        stripCustom.append(START_FMT).append(formatDescription);
+                    }
                 }
-                if (!Character.isDigit(c)) {
+                foundFormats.add(format);
+                foundDescriptions.add(format == null ? null : formatDescription);
+                Validate.isTrue(foundFormats.size() == fmtCount);
+                Validate.isTrue(foundDescriptions.size() == fmtCount);
+                if (c[pos.getIndex()] != END_FE) {
                     throw new IllegalArgumentException(
-                            "Invalid format argument index at position "
-                                    + start);
+                            "Unreadable format element at position " + start);
                 }
+                // fall through
+            default:
+                stripCustom.append(c[pos.getIndex()]);
+                next(pos);
             }
-            throw new IllegalArgumentException(
-                    "Unterminated format element at position " + start);
         }
-
-        /**
-         * Consume a quoted string, adding it to <code>appendTo</code> if
-         * specified.
-         * 
-         * @param pattern pattern to parse
-         * @param pos current parse position
-         * @param appendTo optional StringBuffer to append
-         * @param escapingOn whether to process escaped quotes
-         * @return <code>appendTo</code>
-         */
-        private StringBuffer appendQuotedString(String pattern,
-                ParsePosition pos, StringBuffer appendTo, boolean escapingOn) {
-            int start = pos.getIndex();
-            char[] c = pattern.toCharArray();
-            if (escapingOn && c[start] == QUOTE) {
-                return appendTo == null ? null : appendTo.append(QUOTE);
-            }
-            int lastHold = start;
-            for (int i = pos.getIndex(); i < pattern.length(); i++) {
-                if (escapingOn
-                        && pattern.substring(i).startsWith(ESCAPED_QUOTE)) {
-                    appendTo.append(c, lastHold, pos.getIndex() - lastHold)
-                            .append(QUOTE);
-                    pos.setIndex(i + ESCAPED_QUOTE.length());
-                    lastHold = pos.getIndex();
-                    continue;
-                }
-                switch (c[pos.getIndex()]) {
-                case QUOTE:
-                    next(pos);
-                    return appendTo == null ? null : appendTo.append(c,
-                            lastHold, pos.getIndex() - lastHold);
-                default:
-                    next(pos);
+        super.applyPattern(stripCustom.toString());
+        toPattern = insertFormats(super.toPattern(), foundDescriptions);
+        if (containsElements(foundFormats)) {
+            Format[] origFormats = getFormats();
+            for (int i = 0; i < origFormats.length; i++) {
+                Format f = (Format) foundFormats.get(i);
+                if (f != null) {
+                    origFormats[i] = f;
                 }
             }
-            throw new IllegalArgumentException(
-                    "Unterminated quoted string at position " + start);
+            super.setFormats(origFormats);
         }
+    }
 
-        /**
-         * Consume quoted string only
-         * 
-         * @param pattern pattern to parse
-         * @param pos current parse position
-         * @param escapingOn whether to process escaped quotes
-         */
-        private void getQuotedString(String pattern, ParsePosition pos,
-                boolean escapingOn) {
-            appendQuotedString(pattern, pos, null, escapingOn);
-        }
-
-        /**
-         * Consume the entire format found at the current position.
-         * 
-         * @param pattern string to parse
-         * @param pos current parse position
-         */
-        private void eatFormat(String pattern, ParsePosition pos) {
-            int start = pos.getIndex();
-            int depth = 1;
-            for (; pos.getIndex() < pattern.length(); next(pos)) {
-                switch (pattern.charAt(pos.getIndex())) {
-                case START_FE:
-                    depth++;
-                    break;
-                case END_FE:
-                    depth--;
-                    if (depth == 0) {
-                        return;
-                    }
-                    break;
-                case QUOTE:
-                    getQuotedString(pattern, pos, false);
-                    break;
-                }
-            }
-            throw new IllegalArgumentException(
-                    "Unterminated format element at position " + start);
-        }
+    /**
+     * {@inheritDoc}
+     * UNSUPPORTED
+     */
+    public void setFormat(int formatElementIndex, Format newFormat) {
+        throw new UnsupportedOperationException();
     }
 
-    private static final Parser PARSER = new Parser();
+    /**
+     * {@inheritDoc}
+     * UNSUPPORTED
+     */
+    public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
+        throw new UnsupportedOperationException();
+    }
 
-    private Format metaFormat;
-    private String strippedPattern;
+    /**
+     * {@inheritDoc}
+     * UNSUPPORTED
+     */
+    public void setFormats(Format[] newFormats) {
+        throw new UnsupportedOperationException();
+    }
 
     /**
-     * Create a new ExtendedMessageFormat for the default locale.
-     * 
-     * @param pattern String
-     * @param metaFormat Format
-     * @throws IllegalArgumentException if <code>metaFormat</code> is
-     *             <code>null</code> or in case of a bad pattern.
+     * {@inheritDoc}
+     * UNSUPPORTED
      */
-    public ExtendedMessageFormat(String pattern, Format metaFormat) {
-        this(pattern, Locale.getDefault(), metaFormat);
+    public void setFormatsByArgumentIndex(Format[] newFormats) {
+        throw new UnsupportedOperationException();
     }
 
     /**
-     * Create a new ExtendedMessageFormat.
+     * Get a custom format from a format description.
      * 
-     * @param pattern String
-     * @param locale Locale
-     * @param metaFormat Format
-     * @throws IllegalArgumentException if <code>metaFormat</code> is
-     *             <code>null</code> or in case of a bad pattern.
-     */
-    public ExtendedMessageFormat(String pattern, Locale locale,
-            Format metaFormat) {
-        /*
-         * We have to do some acrobatics here: the call to the super constructor
-         * will invoke applyPattern(), but we don't want to apply the pattern
-         * until we've installed our custom metaformat. So we check for that in
-         * our (final) applyPattern implementation, and re-call at the end of
-         * this constructor.
-         */
-        super(pattern);
-        setLocale(locale);
-        setMetaFormat(metaFormat);
-        applyPattern(pattern);
+     * @param desc String
+     * @return Format
+     */
+    private Format getFormat(String desc) {
+        if (registry != null) {
+            String name = desc;
+            String args = null;
+            int i = desc.indexOf(START_FMT);
+            if (i > 0) {
+                name = desc.substring(0, i).trim();
+                args = desc.substring(i + 1).trim();
+            }
+            FormatFactory factory = (FormatFactory) registry.get(name);
+            if (factory != null) {
+                return factory.getFormat(name, args, getLocale());
+            }
+        }
+        return null;
     }
 
     /**
-     * Apply the specified pattern.
+     * Read the argument index from the current format element
      * 
-     * @param pattern String
-     */
-    public final void applyPattern(String pattern) {
-        if (metaFormat == null) {
-            return;
+     * @param pattern pattern to parse
+     * @param pos current parse position
+     * @return argument index
+     */
+    private int readArgumentIndex(String pattern, ParsePosition pos) {
+        int start = pos.getIndex();
+        seekNonWs(pattern, pos);
+        StringBuffer result = new StringBuffer();
+        boolean error = false;
+        for (; !error && pos.getIndex() < pattern.length(); next(pos)) {
+            char c = pattern.charAt(pos.getIndex());
+            if (Character.isWhitespace(c)) {
+                seekNonWs(pattern, pos);
+                c = pattern.charAt(pos.getIndex());
+                if (c != START_FMT && c != END_FE) {
+                    error = true;
+                    continue;
+                }
+            }
+            if ((c == START_FMT || c == END_FE) && result.length() > 0) {
+                try {
+                    return Integer.parseInt(result.toString());
+                } catch (NumberFormatException e) {
+                    //we've already ensured only digits, so unless something outlandishly large was specified we should be okay.
+                }
+            }
+            error = !Character.isDigit(c);
+            result.append(c);
         }
-        applyPatternPre(pattern);
-        strippedPattern = PARSER.stripFormats(pattern);
-        super.applyPattern(strippedPattern);
-        setFormats(PARSER.parseFormats(pattern, metaFormat));
-        applyPatternPost(pattern);
+        if (error) {
+            throw new IllegalArgumentException(
+                    "Invalid format argument index at position " + start + ": "
+                            + pattern.substring(start, pos.getIndex()));
+        }
+        throw new IllegalArgumentException(
+                "Unterminated format element at position " + start);
     }
 
     /**
-     * Pre-execution hook by means of which a subclass can customize the
-     * behavior of the final applyPattern implementation.
+     * Parse the format component of a format element.
      * 
-     * @param pattern String
+     * @param pattern string to parse
+     * @param pos current parse position
+     * @return Format description String
+     */
+    private String parseFormatDescription(String pattern, ParsePosition pos) {
+        int start = pos.getIndex();
+        seekNonWs(pattern, pos);
+        int text = pos.getIndex();
+        int depth = 1;
+        for (; pos.getIndex() < pattern.length(); next(pos)) {
+            switch (pattern.charAt(pos.getIndex())) {
+            case START_FE:
+                depth++;
+                break;
+            case END_FE:
+                depth--;
+                if (depth == 0) {
+                    return pattern.substring(text, pos.getIndex());
+                }
+                break;
+            case QUOTE:
+                getQuotedString(pattern, pos, false);
+                break;
+            }
+        }
+        throw new IllegalArgumentException(
+                "Unterminated format element at position " + start);
+    }
+
+    /**
+     * Insert formats back into the pattern for toPattern() support.
+     *
+     * @param pattern source
+     * @param formats the Formats to insert
+     * @param metaFormat Format to format the Formats
+     * @return full pattern
      */
-    protected void applyPatternPre(String pattern) {
-        // noop
+    private String insertFormats(String pattern, ArrayList customPatterns) {
+        if (!containsElements(customPatterns)) {
+            return pattern;
+        }
+        StringBuffer sb = new StringBuffer(pattern.length() * 2);
+        ParsePosition pos = new ParsePosition(0);
+        int fe = -1;
+        int depth = 0;
+        while (pos.getIndex() < pattern.length()) {
+            char c = pattern.charAt(pos.getIndex());
+            switch (c) {
+            case QUOTE:
+                appendQuotedString(pattern, pos, sb, false);
+                break;
+            case START_FE:
+                depth++;
+                if (depth == 1) {
+                    fe++;
+                    sb.append(START_FE).append(
+                            readArgumentIndex(pattern, next(pos)));
+                    String customPattern = (String) customPatterns.get(fe);
+                    if (customPattern != null) {
+                        sb.append(START_FMT).append(customPattern);
+                    }
+                }
+                break;
+            case END_FE:
+                depth--;
+                //fall through:
+            default:
+                sb.append(c);
+                next(pos);
+            }
+        }
+        return sb.toString();
     }
 
     /**
-     * Post-execution hook by means of which a subclass can customize the
-     * behavior of the final applyPattern implementation.
+     * Consume whitespace from the current parse position.
      * 
-     * @param pattern String
+     * @param pattern String to read
+     * @param pos current position
      */
-    protected void applyPatternPost(String pattern) {
-        // noop
+    private void seekNonWs(String pattern, ParsePosition pos) {
+        int len = 0;
+        char[] buffer = pattern.toCharArray();
+        do {
+            len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex());
+            pos.setIndex(pos.getIndex() + len);
+        } while (len > 0 && pos.getIndex() < pattern.length());
     }
 
     /**
-     * Render the pattern from the current state of the
-     * <code>ExtendedMessageFormat</code>.
+     * Convenience method to advance parse position by 1
      * 
-     * @return pattern String
+     * @param pos ParsePosition
+     * @return <code>pos</code>
      */
-    public String toPattern() {
-        return PARSER.insertFormats(strippedPattern, getFormats(), metaFormat);
+    private ParsePosition next(ParsePosition pos) {
+        pos.setIndex(pos.getIndex() + 1);
+        return pos;
     }
 
     /**
-     * Get the meta-format currently configured.
+     * Consume a quoted string, adding it to <code>appendTo</code> if
+     * specified.
      * 
-     * @return Format.
-     */
-    public synchronized Format getMetaFormat() {
-        return metaFormat;
+     * @param pattern pattern to parse
+     * @param pos current parse position
+     * @param appendTo optional StringBuffer to append
+     * @param escapingOn whether to process escaped quotes
+     * @return <code>appendTo</code>
+     */
+    private StringBuffer appendQuotedString(String pattern, ParsePosition pos,
+            StringBuffer appendTo, boolean escapingOn) {
+        int start = pos.getIndex();
+        char[] c = pattern.toCharArray();
+        if (escapingOn && c[start] == QUOTE) {
+            return appendTo == null ? null : appendTo.append(QUOTE);
+        }
+        int lastHold = start;
+        for (int i = pos.getIndex(); i < pattern.length(); i++) {
+            if (escapingOn && pattern.substring(i).startsWith(ESCAPED_QUOTE)) {
+                appendTo.append(c, lastHold, pos.getIndex() - lastHold).append(
+                        QUOTE);
+                pos.setIndex(i + ESCAPED_QUOTE.length());
+                lastHold = pos.getIndex();
+                continue;
+            }
+            switch (c[pos.getIndex()]) {
+            case QUOTE:
+                next(pos);
+                return appendTo == null ? null : appendTo.append(c, lastHold,
+                        pos.getIndex() - lastHold);
+            default:
+                next(pos);
+            }
+        }
+        throw new IllegalArgumentException(
+                "Unterminated quoted string at position " + start);
     }
 
     /**
-     * Set the meta-format. Has no effect until a subsequent call to
-     * {@link #applyPattern(String)}.
+     * Consume quoted string only
      * 
-     * @param metaFormat the Format metaFormat to set.
+     * @param pattern pattern to parse
+     * @param pos current parse position
+     * @param escapingOn whether to process escaped quotes
      */
-    public synchronized void setMetaFormat(Format metaFormat) {
-        Validate.notNull(metaFormat, "metaFormat is null");
-        this.metaFormat = metaFormat;
+    private void getQuotedString(String pattern, ParsePosition pos,
+            boolean escapingOn) {
+        appendQuotedString(pattern, pos, null, escapingOn);
     }
 
+    /**
+     * Learn whether the specified Collection contains non-null elements.
+     * @param coll to check
+     * @return <code>true</code> if some Object was found, <code>false</code> otherwise.
+     */
+    private boolean containsElements(Collection coll) {
+        if (coll == null || coll.size() == 0) {
+            return false;
+        }
+        for (Iterator iter = coll.iterator(); iter.hasNext();) {
+            if (iter.next() != null) {
+                return true;
+            }
+        }
+        return false;
+    }
 }

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/FormatFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/FormatFactory.java?rev=630969&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/FormatFactory.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/FormatFactory.java Mon Feb 25 11:51:18 2008
@@ -0,0 +1,24 @@
+package org.apache.commons.lang.text;
+
+import java.text.Format;
+import java.util.Locale;
+
+/**
+ * Format factory.
+ * @since 2.4
+ * @author Niall Pemberton
+ * @version $Id$
+ */
+public interface FormatFactory {
+
+    /**
+     * Create or retrieve a format instance.
+     *
+     * @param name The format type name
+     * @param arguments Arguments used to create the format instance
+     * @param locale The locale, may be null
+     * @return The format instance
+     */
+    Format getFormat(String name, String arguments, Locale locale);
+
+}

Propchange: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/FormatFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java?rev=630969&r1=630968&r2=630969&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java (original)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java Mon Feb 25 11:51:18 2008
@@ -173,8 +173,7 @@
      * {@inheritDoc}
      */
     protected MessageFormat createMessageFormat(String pattern, Locale locale) {
-        return new ExtendedMessageFormat(pattern, locale, ExtendedMessageFormat
-                .createDefaultMetaFormat(locale));
+        return new ExtendedMessageFormat(pattern, locale);
     }
 
 }

Modified: commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java?rev=630969&r1=630968&r2=630969&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java (original)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java Mon Feb 25 11:51:18 2008
@@ -24,6 +24,7 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.HashMap;
 import java.util.Locale;
 
 /**
@@ -237,13 +238,13 @@
      * {@inheritDoc}
      */
     protected MessageFormat createMessageFormat(String pattern, Locale locale) {
-        return new ExtendedMessageFormat(pattern, locale,
-                new MultiFormat.Builder().add(
-                        new NameKeyedMetaFormat.Builder().put("properName",
-                                new ProperNameCapitalizationFormat())
-                                .toNameKeyedMetaFormat()).add(
-                        ExtendedMessageFormat.createDefaultMetaFormat(locale))
-                        .toMultiFormat());
+        final ProperNameCapitalizationFormat properNameCapitalizationFormat = new ProperNameCapitalizationFormat();
+        final FormatFactory ff = new FormatFactory() {
+            public Format getFormat(String name, String arguments, Locale locale) {
+                return "properName".equals(name) ? properNameCapitalizationFormat : null;
+            }
+        };
+        return new ExtendedMessageFormat(pattern, locale, new HashMap() { { put("properName", ff); }});
     }
 
     public void testProperName() {