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 2007/10/30 16:06:58 UTC

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

Author: mbenson
Date: Tue Oct 30 08:06:54 2007
New Revision: 590106

URL: http://svn.apache.org/viewvc?rev=590106&view=rev
Log:
[LANG-362] Add ExtendedMessageFormat

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ChoiceMetaFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ChoiceMetaFormat.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ChoiceMetaFormat.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ChoiceMetaFormat.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.ChoiceFormat;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+
+/**
+ * Stock "choice" MetaFormat.
+ * 
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class ChoiceMetaFormat extends MetaFormatSupport {
+    private static final long serialVersionUID = 3802197832963795129L;
+
+    /**
+     * Singleton-usable instance.
+     */
+    public static final ChoiceMetaFormat INSTANCE = new ChoiceMetaFormat();
+
+    /**
+     * Create a new ChoiceMetaFormat.
+     */
+    public ChoiceMetaFormat() {
+        super();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer,
+     *      java.text.FieldPosition)
+     */
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+            FieldPosition pos) {
+        if (obj instanceof ChoiceFormat) {
+            return toAppendTo.append(((ChoiceFormat) obj).toPattern());
+        }
+        throw new IllegalArgumentException(String.valueOf(obj));
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.text.Format#parseObject(java.lang.String,
+     *      java.text.ParsePosition)
+     */
+    public Object parseObject(String source, ParsePosition pos) {
+        int start = pos.getIndex();
+        seekFormatElementEnd(source, pos);
+        return new ChoiceFormat(source.substring(start, pos.getIndex()));
+    }
+
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormat.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormat.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormat.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.DateFormat;
+import java.util.Locale;
+
+/**
+ * Stock "date" MetaFormat.
+ * 
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class DateMetaFormat extends DateMetaFormatSupport {
+    private static final long serialVersionUID = -4732179430347600208L;
+
+    /**
+     * Create a new DateMetaFormat.
+     */
+    public DateMetaFormat() {
+        super();
+    }
+
+    /**
+     * Create a new DateMetaFormat.
+     * 
+     * @param locale
+     */
+    public DateMetaFormat(Locale locale) {
+        super(locale);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractDateMetaFormat#createSubformatInstance(int)
+     */
+    protected DateFormat createSubformatInstance(int style) {
+        return DateFormat.getDateInstance(style, getLocale());
+    }
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormatSupport.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormatSupport.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormatSupport.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DateMetaFormatSupport.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * date/time metaFormat support.
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public abstract class DateMetaFormatSupport extends MetaFormatSupport {
+    /** "Default" subformat name */
+    protected static final String DEFAULT = "";
+
+    /** "Short" subformat name */
+    protected static final String SHORT = "short";
+
+    /** "Medium" subformat name */
+    protected static final String MEDIUM = "medium";
+
+    /** "Long" subformat name */
+    protected static final String LONG = "long";
+
+    /** "Full" subformat name */
+    protected static final String FULL = "full";
+
+    private Locale locale;
+    private boolean handlePatterns = true;
+
+    private transient boolean initialized;
+    private transient Map styleMap;
+    private transient Map inverseStyleMap;
+    private transient Map subformats;
+    private transient Map reverseSubformats;
+    private transient DateFormatSymbols dateFormatSymbols;
+
+    /**
+     * Create a new AbstractDateMetaFormat.
+     */
+    public DateMetaFormatSupport() {
+        this(Locale.getDefault());
+    }
+
+    /**
+     * Create a new AbstractDateMetaFormat.
+     * 
+     * @param locale
+     */
+    public DateMetaFormatSupport(Locale locale) {
+        super();
+        this.locale = locale;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer,
+     *      java.text.FieldPosition)
+     */
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+            FieldPosition pos) {
+        String subformat = getSubformatName(obj);
+        if (subformat != null) {
+            return toAppendTo.append(subformat);
+        }
+        if (isHandlePatterns() && obj instanceof SimpleDateFormat) {
+            SimpleDateFormat sdf = (SimpleDateFormat) obj;
+            if (sdf.getDateFormatSymbols().equals(dateFormatSymbols)) {
+                return toAppendTo.append(sdf.toPattern());
+            }
+        }
+        throw new IllegalArgumentException(String.valueOf(obj));
+    }
+
+    private String getSubformatName(Object subformat) {
+        initialize();
+        if (reverseSubformats.containsKey(subformat)) {
+            return (String) inverseStyleMap.get(reverseSubformats
+                    .get(subformat));
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.text.Format#parseObject(java.lang.String,
+     *      java.text.ParsePosition)
+     */
+    public Object parseObject(String source, ParsePosition pos) {
+        int start = pos.getIndex();
+        seekFormatElementEnd(source, pos);
+        if (pos.getErrorIndex() >= 0) {
+            return null;
+        }
+        String subformat = source.substring(start, pos.getIndex()).trim();
+        Object result = getSubformat(subformat);
+        if (result != null) {
+            return result;
+        }
+        if (isHandlePatterns()) {
+            return new SimpleDateFormat(subformat, getLocale());
+        }
+        pos.setErrorIndex(start);
+        return null;
+    }
+
+    private Format getSubformat(String subformat) {
+        initialize();
+        if (!styleMap.containsKey(subformat)) {
+            return null;
+        }
+        initialize();
+        return (Format) subformats.get(styleMap.get(subformat));
+    }
+
+    /**
+     * Get the locale in use by this {@link DateMetaFormatSupport}.
+     * 
+     * @return Locale
+     */
+    public Locale getLocale() {
+        return locale;
+    }
+
+    private synchronized void initialize() {
+        if (!initialized) {
+            styleMap = createStyleMap();
+            inverseStyleMap = createInverseStyleMap();
+            subformats = new HashMap();
+            reverseSubformats = new HashMap();
+            for (Iterator iter = styleMap.values().iterator(); iter.hasNext();) {
+                Integer style = (Integer) iter.next();
+                if (subformats.containsKey(style)) {
+                    continue;
+                }
+                Format sf = createSubformatInstance(style.intValue());
+                subformats.put(style, sf);
+                if (inverseStyleMap.containsKey(style)) {
+                    reverseSubformats.put(sf, style);
+                }
+            }
+            dateFormatSymbols = new DateFormatSymbols(getLocale());
+        }
+        initialized = true;
+    }
+
+    /**
+     * Create a subformat for the given <code>DateFormat</code> style
+     * constant.
+     * 
+     * @param style
+     * @return a DateFormat instance.
+     */
+    protected abstract DateFormat createSubformatInstance(int style);
+
+    /**
+     * Get whether this metaformat can parse date/time pattern formats in
+     * addition to named formats.
+     * 
+     * @return boolean.
+     */
+    public boolean isHandlePatterns() {
+        return handlePatterns;
+    }
+
+    /**
+     * Set whether this metaformat can parse date/time pattern formats in
+     * addition to named formats.
+     * 
+     * @param handlePatterns
+     *            the boolean handlePatterns to set.
+     * @return <code>this</code> for fluent usage.
+     */
+    public DateMetaFormatSupport setHandlePatterns(boolean handlePatterns) {
+        this.handlePatterns = handlePatterns;
+        return this;
+    }
+
+    /**
+     * Create the style map.
+     * 
+     * @return Map
+     */
+    protected Map createStyleMap() {
+        HashMap result = new HashMap();
+        result.put(SHORT, new Integer(DateFormat.SHORT));
+        result.put(MEDIUM, new Integer(DateFormat.MEDIUM));
+        result.put(LONG, new Integer(DateFormat.LONG));
+        result.put(FULL, new Integer(DateFormat.FULL));
+        result.put(DEFAULT, new Integer(DateFormat.DEFAULT));
+        return result;
+    }
+
+    /**
+     * Create the inverse style map.
+     * 
+     * @return Map
+     */
+    protected Map createInverseStyleMap() {
+        Map invertMe = createStyleMap();
+        invertMe.remove(DEFAULT);
+        return invert(invertMe);
+    }
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DefaultMetaFormatFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DefaultMetaFormatFactory.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DefaultMetaFormatFactory.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/DefaultMetaFormatFactory.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.Validate;
+
+/**
+ * Factory methods to produce metaformat instances that behave like
+ * java.text.MessageFormat.
+ * 
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+/* package-private */ class DefaultMetaFormatFactory {
+
+    /** Number key */
+    public static final String NUMBER_KEY = "number";
+
+    /** Date key */
+    public static final String DATE_KEY = "date";
+
+    /** Time key */
+    public static final String TIME_KEY = "time";
+
+    /** Choice key */
+    public static final String CHOICE_KEY = "choice";
+
+    private static final String[] NO_SUBFORMAT_KEYS = new String[] {
+            NUMBER_KEY, DATE_KEY, TIME_KEY };
+
+    private static final String[] NO_PATTERN_KEYS = new String[] { NUMBER_KEY,
+            DATE_KEY, TIME_KEY, CHOICE_KEY };
+
+    private static final String[] PATTERN_KEYS = new String[] { DATE_KEY,
+            TIME_KEY };
+
+    private static class OrderedNameKeyedMetaFormat extends NameKeyedMetaFormat {
+        private static final long serialVersionUID = -7688772075239431055L;
+
+        private List keys;
+
+        private OrderedNameKeyedMetaFormat(String[] names, Format[] formats) {
+            super(createMap(names, formats));
+            this.keys = Arrays.asList(names);
+        }
+
+        private static Map createMap(String[] names, Format[] formats) {
+            Validate.isTrue(ArrayUtils.isSameLength(names, formats));
+            HashMap result = new HashMap(names.length);
+            for (int i = 0; i < names.length; i++) {
+                result.put(names[i], formats[i]);
+            }
+            return result;
+        }
+
+        protected Iterator iterateKeys() {
+            return keys.iterator();
+        }
+    }
+
+    /**
+     * Get a default metaformat for the specified Locale.
+     * 
+     * @param locale
+     *            the Locale for the resulting Format instance.
+     * @return Format
+     */
+    public static Format getFormat(final Locale locale) {
+        Format nmf = new NumberMetaFormat(locale);
+        Format dmf = new DateMetaFormat(locale).setHandlePatterns(false);
+        Format tmf = new TimeMetaFormat(locale).setHandlePatterns(false);
+
+        return new MultiFormat(new Format[] {
+                new OrderedNameKeyedMetaFormat(NO_SUBFORMAT_KEYS, new Format[] {
+                        getDefaultFormat(nmf), getDefaultFormat(dmf),
+                        getDefaultFormat(tmf) }),
+                new OrderedNameKeyedMetaFormat(NO_PATTERN_KEYS, new Format[] {
+                        nmf, dmf, tmf, ChoiceMetaFormat.INSTANCE }),
+                new OrderedNameKeyedMetaFormat(PATTERN_KEYS,
+                        new Format[] { new DateMetaFormat(locale),
+                                new TimeMetaFormat(locale) }) });
+    }
+
+    private static Format getDefaultFormat(Format metaformat) {
+        ParsePosition pos = new ParsePosition(0);
+        Object o = metaformat.parseObject("", pos);
+        return pos.getErrorIndex() < 0 ? (Format) o : null;
+    }
+}

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

Added: 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=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/ExtendedMessageFormat.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import org.apache.commons.lang.Validate;
+
+/**
+ * Extends <code>MessageFormat</code> to allow pluggable/additional formatting
+ * options for embedded format elements; requires a "meta-format", i.e. a
+ * <code>Format</code> capable of parsing and formatting other
+ * <code>Format</code>s.
+ * 
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class ExtendedMessageFormat extends MessageFormat {
+    private static final long serialVersionUID = -2362048321261811743L;
+
+    /**
+     * 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.
+     * 
+     * @return Format
+     */
+    public static Format createDefaultMetaFormat() {
+        return createDefaultMetaFormat(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>.
+     * 
+     * @param locale
+     *            the Locale for the resulting Format instance.
+     * @return Format
+     */
+    public static Format createDefaultMetaFormat(Locale locale) {
+        return DefaultMetaFormatFactory.getFormat(locale);
+    }
+
+    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 = '\'';
+
+        private String stripFormats(String pattern) {
+            StringBuffer sb = new StringBuffer(pattern.length());
+            ParsePosition pos = new ParsePosition(0);
+            while (pos.getIndex() < pattern.length()) {
+                switch (pattern.charAt(pos.getIndex())) {
+                case QUOTE:
+                    appendQuotedString(pattern, pos, sb, true);
+                    break;
+                case START_FE:
+                    int start = pos.getIndex();
+                    readArgumentIndex(pattern, next(pos));
+                    sb.append(pattern, start, pos.getIndex());
+                    if (pattern.charAt(pos.getIndex()) == START_FMT) {
+                        eatFormat(pattern, next(pos));
+                    }
+                    if (pattern.charAt(pos.getIndex()) != END_FE) {
+                        throw new IllegalArgumentException(
+                                "Unreadable format element at position "
+                                        + start);
+                    }
+                    // fall through
+                default:
+                    sb.append(pattern.charAt(pos.getIndex()));
+                    next(pos);
+                }
+            }
+            return sb.toString();
+        }
+
+        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) {
+                        sb.append(START_FMT).append(
+                                metaFormat.format(formats[fe]));
+                    }
+                    break;
+                default:
+                    sb.append(pattern.charAt(pos.getIndex()));
+                    next(pos);
+                }
+            }
+            return sb.toString();
+        }
+
+        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()]);
+        }
+
+        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());
+        }
+
+        private ParsePosition next(ParsePosition pos) {
+            pos.setIndex(pos.getIndex() + 1);
+            return pos;
+        }
+
+        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());
+                }
+                if (!Character.isDigit(c)) {
+                    throw new IllegalArgumentException(
+                            "Invalid format argument index at position "
+                                    + start);
+                }
+            }
+            throw new IllegalArgumentException(
+                    "Unterminated format element at position " + start);
+        }
+
+        private StringBuffer appendQuotedString(String pattern,
+                ParsePosition pos, StringBuffer appendTo, boolean escapingOn) {
+            int start = pos.getIndex();
+            if (escapingOn && pattern.charAt(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(pattern, lastHold, pos.getIndex()).append(
+                            QUOTE);
+                    pos.setIndex(i + ESCAPED_QUOTE.length());
+                    lastHold = pos.getIndex();
+                    continue;
+                }
+                switch (pattern.charAt(pos.getIndex())) {
+                case QUOTE:
+                    next(pos);
+                    return appendTo == null ? null : appendTo.append(pattern,
+                            lastHold, pos.getIndex());
+                default:
+                    next(pos);
+                }
+            }
+            throw new IllegalArgumentException(
+                    "Unterminated quoted string at position " + start);
+        }
+
+        private void getQuotedString(String pattern, ParsePosition pos,
+                boolean escapingOn) {
+            appendQuotedString(pattern, pos, null, escapingOn);
+        }
+
+        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);
+        }
+    }
+
+    private static final Parser PARSER = new Parser();
+
+    private Format metaFormat;
+    private String strippedPattern;
+
+    /**
+     * Create a new ExtendedMessageFormat.
+     * 
+     * @param pattern
+     * @param metaFormat
+     * @throws IllegalArgumentException
+     *             if <code>metaFormat</code> is <code>null</code> or in
+     *             case of a bad pattern.
+     */
+    public ExtendedMessageFormat(String pattern, 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);
+        setMetaFormat(metaFormat);
+        applyPattern(pattern);
+    }
+
+    /**
+     * Apply the specified pattern.
+     * 
+     * @param pattern
+     *            pattern String
+     */
+    public final void applyPattern(String pattern) {
+        if (metaFormat == null) {
+            return;
+        }
+        applyPatternPre(pattern);
+        strippedPattern = PARSER.stripFormats(pattern);
+        super.applyPattern(strippedPattern);
+        setFormats(PARSER.parseFormats(pattern, metaFormat));
+        applyPatternPost(pattern);
+    }
+
+    /**
+     * Pre-execution hook that allows subclasses to customize the behavior of
+     * the final applyPattern implementation.
+     * 
+     * @param pattern
+     */
+    protected void applyPatternPre(String pattern) {
+        // noop
+    }
+
+    /**
+     * Post-execution hook that allows subclasses to customize the behavior of
+     * the final applyPattern implementation.
+     * 
+     * @param pattern
+     */
+    protected void applyPatternPost(String pattern) {
+        // noop
+    }
+
+    /**
+     * Render the pattern from the current state of the
+     * <code>ExtendedMessageFormat</code>.
+     * 
+     * @return pattern String
+     */
+    public String toPattern() {
+        return PARSER.insertFormats(strippedPattern, getFormats(), metaFormat);
+    }
+
+    /**
+     * Get the meta-format currently configured.
+     * 
+     * @return Format.
+     */
+    public synchronized Format getMetaFormat() {
+        return metaFormat;
+    }
+
+    /**
+     * Set the meta-format. Has no effect until a subsequent call to
+     * {@link #applyPattern(String)}.
+     * 
+     * @param metaFormat
+     *            the Format metaFormat to set.
+     */
+    public synchronized void setMetaFormat(Format metaFormat) {
+        Validate.notNull(metaFormat, "metaFormat is null");
+        this.metaFormat = metaFormat;
+    }
+
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/MetaFormatSupport.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/MetaFormatSupport.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/MetaFormatSupport.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/MetaFormatSupport.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * metaFormat support.
+ * 
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public abstract class MetaFormatSupport extends Format {
+
+    private static final char END_FE = '}';
+    private static final char START_FE = '{';
+    private static final char QUOTE = '\'';
+
+    /**
+     * Invert the specified Map.
+     * 
+     * @param map
+     *            the Map to invert.
+     * @return a new Map instance.
+     * @throws NullPointerException
+     *             if <code>map</code> is <code>null</code>.
+     */
+    protected Map invert(Map map) {
+        Map result = new HashMap(map.size());
+        for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            result.put(entry.getValue(), entry.getKey());
+        }
+        return result;
+    }
+
+    /**
+     * Find the end of the subformat.
+     * 
+     * @param source
+     * @param pos
+     */
+    protected void seekFormatElementEnd(String source, ParsePosition pos) {
+        int depth = 1;
+        boolean quote = false;
+        for (; pos.getIndex() < source.length(); next(pos)) {
+            switch (source.charAt(pos.getIndex())) {
+            case QUOTE:
+                quote ^= true;
+                break;
+            case START_FE:
+                depth += quote ? 0 : 1;
+                break;
+            case END_FE:
+                depth -= quote ? 0 : 1;
+                if (depth == 0) {
+                    return;
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Advance the parse index by 1.
+     * 
+     * @param pos
+     *            the ParsePosition to advance.
+     * @return <code>pos</code>
+     */
+    protected ParsePosition next(ParsePosition pos) {
+        pos.setIndex(pos.getIndex() + 1);
+        return pos;
+    }
+
+    // provide default javadoc >;)
+    /**
+     * Parse an object from the specified String and ParsePosition. If an error
+     * occurs <code>pos.getErrorIndex()</code> will contain a value >= zero,
+     * indicating the index at which the parse error occurred.
+     * 
+     * @param source
+     *            String to parse
+     * @param pos
+     *            ParsePosition marking index into <code>source</code>
+     * @return Object parsed
+     */
+    public abstract Object parseObject(String source, ParsePosition pos);
+
+    /**
+     * Format the specified object, appending to the given StringBuffer, and
+     * optionally respecting the specified FieldPosition.
+     * 
+     * @param obj
+     *            the object to format
+     * @param toAppendTo
+     *            the StringBuffer to which the formatted object should be
+     *            appended
+     * @param pos
+     *            FieldPosition associated with <code>obj</code>
+     * @return <code>toAppendTo</code>
+     * @throws NullPointerException
+     *             if <code>toAppendTo</code> or <code>pos</code> is
+     *             <code>null</code>
+     * @throws IllegalArgumentException
+     *             if unable to format <code>obj</code>
+     */
+    public abstract StringBuffer format(Object obj, StringBuffer toAppendTo,
+            FieldPosition pos);
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NameKeyedMetaFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NameKeyedMetaFormat.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NameKeyedMetaFormat.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NameKeyedMetaFormat.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.lang.ObjectUtils;
+
+/**
+ * Basic metaFormat that requires enough configuration information to
+ * parse/format other Formats for use by ExtendedMessageFormat.
+ * 
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class NameKeyedMetaFormat extends MetaFormatSupport {
+    private static final long serialVersionUID = 5963121202601122213L;
+
+    private static final char TRIGGER_END = '}';
+    private static final char TRIGGER_SUBFORMAT = ',';
+
+    /**
+     * Provides a builder with a fluent interface. Example:
+     * <p>
+     * <code>
+     * <pre>
+     * NameKeyedMetaFormat nkmf = new NameKeyedMetaFormat.Builder().put(&quot;foo&quot;,
+     *         new FooFormat()).put(&quot;bar&quot;, new BarFormat())
+     *         .put(&quot;baz&quot;, new BazFormat()).toNameKeyedMetaFormat();
+     * </pre></code>
+     * </p>
+     */
+    public static class Builder {
+        private HashMap keyedFormats = new HashMap();
+
+        /**
+         * Add the specified format with the specified string key.
+         * 
+         * @param key
+         * @param format
+         * @return
+         */
+        public Builder put(String key, Format format) {
+            keyedFormats.put(key, format);
+            return this;
+        }
+
+        /**
+         * Render the {@link NameKeyedMetaFormat} instance from this Builder.
+         * 
+         * @return NameKeyedMetaFormat
+         */
+        public NameKeyedMetaFormat toNameKeyedMetaFormat() {
+            return new NameKeyedMetaFormat(keyedFormats);
+        }
+    }
+
+    private Map/* <String, Format> */keyedFormats = new HashMap();
+
+    /**
+     * Create a new NameKeyedMetaFormat.
+     */
+    public NameKeyedMetaFormat(Map keyedFormats) {
+        this.keyedFormats = keyedFormats;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.MetaFormatSupport#format(java.lang.Object,
+     *      java.lang.StringBuffer, java.text.FieldPosition)
+     */
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+            FieldPosition pos) {
+        int start = toAppendTo.length();
+        // first try to match a sans-subformat format:
+        for (Iterator iter = iterateKeys(); iter.hasNext();) {
+            Object key = iter.next();
+            if (ObjectUtils.equals(keyedFormats.get(key), obj)) {
+                return toAppendTo.append(key);
+            }
+        }
+        // now try again with subformats:
+        for (Iterator iter = iterateKeys(); iter.hasNext();) {
+            Object key = iter.next();
+            try {
+                ((Format) keyedFormats.get(key)).format(obj, toAppendTo, pos);
+                if (toAppendTo.length() > start) {
+                    toAppendTo.insert(start, ',');
+                }
+                return toAppendTo.insert(start, key);
+            } catch (Exception e) {
+                continue;
+            }
+        }
+        throw new IllegalArgumentException("Cannot format " + obj);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.MetaFormatSupport#parseObject(java.lang.String,
+     *      java.text.ParsePosition)
+     */
+    public Object parseObject(String source, ParsePosition pos) {
+        int start = pos.getIndex();
+        boolean subformat = false;
+        for (; pos.getIndex() < source.length(); next(pos)) {
+            char c = source.charAt(pos.getIndex());
+            if (c == TRIGGER_SUBFORMAT) {
+                subformat = true;
+                break;
+            }
+            if (c == TRIGGER_END) {
+                break;
+            }
+        }
+        String key = source.substring(start, pos.getIndex());
+        Format format = (Format) keyedFormats.get(key);
+        if (format == null) {
+            format = (Format) keyedFormats.get(key.trim());
+            if (format == null) {
+                pos.setErrorIndex(start);
+                return null;
+            }
+        }
+        if (subformat) {
+            return format.parseObject(source, next(pos));
+        }
+        return format;
+    }
+
+    /**
+     * Extension point to alter the iteration order of the delegate format keys.
+     * 
+     * @return Iterator.
+     */
+    protected Iterator iterateKeys() {
+        return keyedFormats.keySet().iterator();
+    }
+
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NumberMetaFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NumberMetaFormat.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NumberMetaFormat.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/NumberMetaFormat.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Stock "number" MetaFormat.
+ * 
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class NumberMetaFormat extends MetaFormatSupport {
+    private static final long serialVersionUID = -5876397363537288952L;
+    private static final String DEFAULT = "";
+    private static final String INTEGER = "integer";
+    private static final String CURRENCY = "currency";
+    private static final String PERCENT = "percent";
+
+    private Locale locale;
+
+    private transient Map subformats;
+    private transient Map reverseSubformats;
+    private transient DecimalFormatSymbols decimalFormatSymbols;
+
+    /**
+     * Create a new NumberMetaFormat.
+     */
+    public NumberMetaFormat() {
+        this(Locale.getDefault());
+    }
+
+    /**
+     * Create a new NumberMetaFormat.
+     * 
+     * @param locale
+     */
+    public NumberMetaFormat(Locale locale) {
+        super();
+        this.locale = locale;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractMetaFormat#format(java.lang.Object,
+     *      java.lang.StringBuffer, java.text.FieldPosition)
+     */
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+            FieldPosition pos) {
+        initialize();
+        String subformat = (String) reverseSubformats.get(obj);
+        if (subformat != null) {
+            return toAppendTo.append(subformat);
+        }
+        if (obj instanceof DecimalFormat) {
+            DecimalFormat df = (DecimalFormat) obj;
+            if (df.getDecimalFormatSymbols().equals(decimalFormatSymbols)) {
+                return toAppendTo.append(df.toPattern());
+            }
+        }
+        throw new IllegalArgumentException();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.text.Format#parseObject(java.lang.String,
+     *      java.text.ParsePosition)
+     */
+    public Object parseObject(String source, ParsePosition pos) {
+        int start = pos.getIndex();
+        seekFormatElementEnd(source, pos);
+        if (pos.getErrorIndex() >= 0) {
+            return null;
+        }
+        String subformat = source.substring(start, pos.getIndex()).trim();
+        initialize();
+        Object result = subformats.get(subformat);
+        if (result != null) {
+            return result;
+        }
+        return new DecimalFormat(subformat, decimalFormatSymbols);
+    }
+
+    /**
+     * Get the locale in use by this <code>NumberMetaFormat</code>.
+     * 
+     * @return Locale
+     */
+    public Locale getLocale() {
+        return locale;
+    }
+
+    private synchronized void initialize() {
+        if (subformats == null) {
+            subformats = new HashMap();
+            subformats.put(DEFAULT, NumberFormat.getInstance(getLocale()));
+            subformats.put(INTEGER, NumberFormat
+                    .getIntegerInstance(getLocale()));
+            subformats.put(CURRENCY, NumberFormat
+                    .getCurrencyInstance(getLocale()));
+            subformats.put(PERCENT, NumberFormat
+                    .getPercentInstance(getLocale()));
+
+            reverseSubformats = invert(subformats);
+            decimalFormatSymbols = new DecimalFormatSymbols(getLocale());
+        }
+    }
+}

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

Added: commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/TimeMetaFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/TimeMetaFormat.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/TimeMetaFormat.java (added)
+++ commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/TimeMetaFormat.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.DateFormat;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Stock "time" MetaFormat.
+ * 
+ * @see {@link ExtendedMessageFormat}
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class TimeMetaFormat extends DateMetaFormatSupport {
+    private static final long serialVersionUID = -4959095416302142342L;
+
+    /**
+     * Create a new TimeMetaFormat.
+     */
+    public TimeMetaFormat() {
+        super();
+    }
+
+    /**
+     * Create a new NumberMetaFormat.
+     * 
+     * @param locale
+     */
+    public TimeMetaFormat(Locale locale) {
+        super(locale);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractDateMetaFormat#createSubformatInstance(int)
+     */
+    protected DateFormat createSubformatInstance(int style) {
+        return DateFormat.getTimeInstance(style, getLocale());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractDateMetaFormat#createReverseStyleMap()
+     */
+    protected Map createInverseStyleMap() {
+        Map invertMe = createStyleMap();
+        invertMe.remove(DEFAULT);
+        invertMe.remove(FULL);
+        return invert(invertMe);
+    }
+}

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

Added: commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/AbstractMessageFormatTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/AbstractMessageFormatTest.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/AbstractMessageFormatTest.java (added)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/AbstractMessageFormatTest.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract testcase to verify behavior of default-configuration
+ * ExtendedMessageFormat vs. MessageFormat.
+ * 
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public abstract class AbstractMessageFormatTest extends TestCase {
+    protected static final Object[] NUMBERS = { new Double(0.1),
+            new Double(1.1), new Double(2.1) };
+
+    protected static final Object[] DATES = {
+            new GregorianCalendar(1970, Calendar.JANUARY, 01, 0, 15, 20)
+                    .getTime(),
+            new GregorianCalendar(1970, Calendar.FEBRUARY, 02, 12, 30, 35)
+                    .getTime(),
+            new GregorianCalendar(1970, Calendar.MARCH, 03, 18, 45, 50)
+                    .getTime() };
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see junit.framework.TestCase#setUp()
+     */
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected abstract MessageFormat createMessageFormat(String pattern);
+
+    protected void doAssertions(String expected, String pattern, Object[] args) {
+        doAssertions(expected, pattern, args, pattern);
+    }
+
+    protected void doAssertions(String expected, String pattern, Object[] args,
+            String toPattern) {
+        MessageFormat f = createMessageFormat(pattern);
+        assertEquals(expected, f.format(args));
+        assertEquals(toPattern, f.toPattern());
+    }
+
+    public void testPlain() {
+        StringBuffer pattern = new StringBuffer();
+        for (int i = 0; i < NUMBERS.length; i++) {
+            if (i > 0) {
+                pattern.append("; ");
+            }
+            pattern.append("Object ").append(i).append(": ").append(NUMBERS[i]);
+        }
+        String p = pattern.toString();
+        doAssertions(p, p, NUMBERS);
+    }
+
+    public void testSimple() {
+        doAssertions("Object 0: 0.1; Object 1: 1.1; Object 2: 2.1",
+                "Object 0: {0}; Object 1: {1}; Object 2: {2}", NUMBERS);
+    }
+
+    public void testNumber() {
+        doAssertions(
+                "Number 0: 0.1; Number 1: 1.1; Number 2: 2.1",
+                "Number 0: {0,number}; Number 1: {1,number}; Number 2: {2,number}",
+                NUMBERS);
+    }
+
+    public void testNumberLooseFormatting() {
+        doAssertions(
+                "Number 0: 0.1; Number 1: 1.1; Number 2: 2.1",
+                "Number 0: {0, number }; Number 1: {1, number }; Number 2: {2, number }",
+                NUMBERS,
+                "Number 0: {0,number}; Number 1: {1,number}; Number 2: {2,number}");
+    }
+
+    public void testInteger() {
+        doAssertions(
+                "Number 0: 0; Number 1: 1; Number 2: 2",
+                "Number 0: {0,number,integer}; Number 1: {1,number,integer}; Number 2: {2,number,integer}",
+                NUMBERS);
+    }
+
+    public void testIntegerLooseFormatting() {
+        doAssertions(
+                "Number 0: 0; Number 1: 1; Number 2: 2",
+                "Number 0: {0, number , integer }; Number 1: {1, number , integer }; Number 2: {2, number , integer }",
+                NUMBERS,
+                "Number 0: {0,number,integer}; Number 1: {1,number,integer}; Number 2: {2,number,integer}");
+    }
+
+    public void testCurrency() {
+        doAssertions(
+                "Number 0: $0.10; Number 1: $1.10; Number 2: $2.10",
+                "Number 0: {0,number,currency}; Number 1: {1,number,currency}; Number 2: {2,number,currency}",
+                NUMBERS);
+    }
+
+    public void testPercent() {
+        doAssertions(
+                "Number 0: 10%; Number 1: 110%; Number 2: 210%",
+                "Number 0: {0,number,percent}; Number 1: {1,number,percent}; Number 2: {2,number,percent}",
+                NUMBERS);
+    }
+
+    public void testNumberPattern() {
+        doAssertions(
+                "Number 0: 000.100; Number 1: 001.100; Number 2: 002.100",
+                "Number 0: {0,number,#000.000}; Number 1: {1,number,#000.000}; Number 2: {2,number,#000.000}",
+                NUMBERS);
+    }
+
+    public void testDate() {
+        doAssertions(
+                "Date 0: Jan 1, 1970; Date 1: Feb 2, 1970; Date 2: Mar 3, 1970",
+                "Date 0: {0,date}; Date 1: {1,date}; Date 2: {2,date}", DATES);
+    }
+
+    public void testDateLooseFormatting() {
+        doAssertions(
+                "Date 0: Jan 1, 1970; Date 1: Feb 2, 1970; Date 2: Mar 3, 1970",
+                "Date 0: {0, date }; Date 1: {1, date }; Date 2: {2,  date  }",
+                DATES, "Date 0: {0,date}; Date 1: {1,date}; Date 2: {2,date}");
+    }
+
+    public void testShortDate() {
+        doAssertions(
+                "Date 0: 1/1/70; Date 1: 2/2/70; Date 2: 3/3/70",
+                "Date 0: {0,date,short}; Date 1: {1,date,short}; Date 2: {2,date,short}",
+                DATES);
+    }
+
+    public void testShortDateLooseFormatting() {
+        doAssertions(
+                "Date 0: 1/1/70; Date 1: 2/2/70; Date 2: 3/3/70",
+                "Date 0: {0, date , short }; Date 1: {1,  date  , short }; Date 2: {2, date ,  short  }",
+                DATES,
+                "Date 0: {0,date,short}; Date 1: {1,date,short}; Date 2: {2,date,short}");
+    }
+
+    public void testMediumDate() {
+        doAssertions(
+                "Date 0: Jan 1, 1970; Date 1: Feb 2, 1970; Date 2: Mar 3, 1970",
+                "Date 0: {0,date,medium}; Date 1: {1,date,medium}; Date 2: {2,date,medium}",
+                DATES, "Date 0: {0,date}; Date 1: {1,date}; Date 2: {2,date}");
+    }
+
+    public void testLongDate() {
+        doAssertions(
+                "Date 0: January 1, 1970; Date 1: February 2, 1970; Date 2: March 3, 1970",
+                "Date 0: {0,date,long}; Date 1: {1,date,long}; Date 2: {2,date,long}",
+                DATES);
+    }
+
+    public void testFullDate() {
+        doAssertions(
+                "Date 0: Thursday, January 1, 1970; Date 1: Monday, February 2, 1970; Date 2: Tuesday, March 3, 1970",
+                "Date 0: {0,date,full}; Date 1: {1,date,full}; Date 2: {2,date,full}",
+                DATES);
+    }
+
+    public void testDatePattern() {
+        doAssertions(
+                "Date 0: AD1970.1; Date 1: AD1970.33; Date 2: AD1970.62",
+                "Date 0: {0,date,Gyyyy.D}; Date 1: {1,date,Gyyyy.D}; Date 2: {2,date,Gyyyy.D}",
+                DATES);
+    }
+
+    public void testTime() {
+        doAssertions(
+                "Time 0: 12:15:20 AM; Time 1: 12:30:35 PM; Time 2: 6:45:50 PM",
+                "Time 0: {0,time}; Time 1: {1,time}; Time 2: {2,time}", DATES);
+    }
+
+    public void testShortTime() {
+        doAssertions(
+                "Time 0: 12:15 AM; Time 1: 12:30 PM; Time 2: 6:45 PM",
+                "Time 0: {0,time,short}; Time 1: {1,time,short}; Time 2: {2,time,short}",
+                DATES);
+    }
+
+    public void testMediumTime() {
+        doAssertions(
+                "Time 0: 12:15:20 AM; Time 1: 12:30:35 PM; Time 2: 6:45:50 PM",
+                "Time 0: {0,time,medium}; Time 1: {1,time,medium}; Time 2: {2,time,medium}",
+                DATES, "Time 0: {0,time}; Time 1: {1,time}; Time 2: {2,time}");
+    }
+
+    public void testLongTime() {
+        DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG);
+        StringBuffer expected = new StringBuffer();
+        for (int i = 0; i < DATES.length; i++) {
+            if (i > 0) {
+                expected.append("; ");
+            }
+            expected.append("Time ").append(i).append(": ").append(
+                    df.format(DATES[i]));
+        }
+        doAssertions(
+                expected.toString(),
+                "Time 0: {0,time,long}; Time 1: {1,time,long}; Time 2: {2,time,long}",
+                DATES);
+    }
+
+    public void testFullTime() {
+        DateFormat df = DateFormat.getTimeInstance(DateFormat.FULL);
+        StringBuffer expected = new StringBuffer();
+        for (int i = 0; i < DATES.length; i++) {
+            if (i > 0) {
+                expected.append("; ");
+            }
+            expected.append("Time ").append(i).append(": ").append(
+                    df.format(DATES[i]));
+        }
+        doAssertions(
+                expected.toString(),
+                "Time 0: {0,time,full}; Time 1: {1,time,full}; Time 2: {2,time,full}",
+                DATES,
+                "Time 0: {0,time,long}; Time 1: {1,time,long}; Time 2: {2,time,long}");
+    }
+
+    public void testTimePattern() {
+        doAssertions(
+                "Time 0: AM01520; Time 1: PM123035; Time 2: PM184550",
+                "Time 0: {0,time,aHms}; Time 1: {1,time,aHms}; Time 2: {2,time,aHms}",
+                DATES,
+                "Time 0: {0,date,aHms}; Time 1: {1,date,aHms}; Time 2: {2,date,aHms}");
+    }
+
+    public void testChoice() {
+        String choice = "0.0#x|1.0#y|2.0#z";
+        StringBuffer pattern = new StringBuffer();
+        for (int i = 0; i < 3; i++) {
+            if (i > 0) {
+                pattern.append("; ");
+            }
+            pattern.append("Choice ").append(i).append(": {").append(i).append(
+                    ",choice,").append(choice).append("}");
+        }
+        doAssertions("Choice 0: x; Choice 1: y; Choice 2: z", pattern
+                .toString(), NUMBERS);
+    }
+
+    public void testChoiceLooseFormatting() {
+        String choice = "0.0#x |1.0#y |2.0#z ";
+        StringBuffer pattern = new StringBuffer();
+        for (int i = 0; i < 3; i++) {
+            if (i > 0) {
+                pattern.append("; ");
+            }
+            pattern.append("Choice ").append(i).append(": {").append(i).append(
+                    ",choice,").append(choice).append("}");
+        }
+        doAssertions("Choice 0: x ; Choice 1: y ; Choice 2: z ", pattern
+                .toString(), NUMBERS);
+    }
+
+    public void testChoiceRecursive() {
+        String choice = "0.0#{0}|1.0#{1}|2.0#{2}";
+        StringBuffer pattern = new StringBuffer();
+        for (int i = 0; i < 3; i++) {
+            if (i > 0) {
+                pattern.append("; ");
+            }
+            pattern.append("Choice ").append(i).append(": {").append(i).append(
+                    ",choice,").append(choice).append("}");
+        }
+        doAssertions("Choice 0: 0.1; Choice 1: 1.1; Choice 2: 2.1", pattern
+                .toString(), NUMBERS);
+    }
+}

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

Added: 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=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java (added)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/ExtendedMessageFormatBaselineTest.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+
+/**
+ * Baseline tests for {@link ExtendedMessageFormat}
+ * 
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class ExtendedMessageFormatBaselineTest extends AbstractMessageFormatTest {
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractMessageFormatTest#createMessageFormat(java.lang.String)
+     */
+    protected MessageFormat createMessageFormat(String pattern) {
+        return new ExtendedMessageFormat(pattern, ExtendedMessageFormat.createDefaultMetaFormat(Locale.US));
+    }
+
+}

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

Added: 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=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java (added)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatExtensionTest.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.text;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+/**
+ * Extension tests for {@link ExtendedMessageFormat}
+ * 
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class MessageFormatExtensionTest extends AbstractMessageFormatTest {
+
+    static class ProperNameCapitalizationFormat extends Format {
+        private static final long serialVersionUID = -6081911520622186866L;
+        private static final StrMatcher MATCH = StrMatcher
+                .charSetMatcher(" ,.");
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.text.Format#format(java.lang.Object,
+         *      java.lang.StringBuffer, java.text.FieldPosition)
+         */
+        public StringBuffer format(Object obj, StringBuffer toAppendTo,
+                FieldPosition fpos) {
+            char[] buffer = String.valueOf(obj).toCharArray();
+            ParsePosition pos = new ParsePosition(0);
+            while (pos.getIndex() < buffer.length) {
+                char c = buffer[pos.getIndex()];
+                if (Character.isLowerCase(c)) {
+                    c = Character.toUpperCase(c);
+                }
+                if (Character.isUpperCase(c)) {
+                    toAppendTo.append(c);
+                    next(pos);
+                }
+                int start = pos.getIndex();
+                seekDelimiter(buffer, pos);
+                toAppendTo.append(new String(buffer, start, pos.getIndex()
+                        - start).toLowerCase());
+            }
+            return toAppendTo;
+        }
+
+        /**
+         * Unable to do much; return the String.
+         */
+        public Object parseObject(String source, ParsePosition pos) {
+            return source.substring(pos.getIndex());
+        }
+
+        private static void seekDelimiter(char[] buffer, ParsePosition pos) {
+            for (; pos.getIndex() < buffer.length
+                    && MATCH.isMatch(buffer, pos.getIndex()) == 0; next(pos))
+                ;
+            if (pos.getIndex() >= buffer.length) {
+                return;
+            }
+            int len = 0;
+            do {
+                len = MATCH.isMatch(buffer, pos.getIndex());
+                pos.setIndex(pos.getIndex() + len);
+            } while (len > 0 && pos.getIndex() < buffer.length);
+        }
+
+        private static void next(ParsePosition pos) {
+            pos.setIndex(pos.getIndex() + 1);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractMessageFormatTest#createMessageFormat(java.lang.String)
+     */
+    protected MessageFormat createMessageFormat(String pattern) {
+        return new ExtendedMessageFormat(pattern, new MultiFormat.Builder()
+                .add(ExtendedMessageFormat.createDefaultMetaFormat(Locale.US)).add(
+                        new NameKeyedMetaFormat.Builder().put("properName",
+                                new ProperNameCapitalizationFormat())
+                                .toNameKeyedMetaFormat()).toMultiFormat());
+    }
+
+    public void testProperName() {
+        doAssertions("John Q. Public; John Q. Public",
+                "{0,properName}; {1,properName}", new String[] {
+                        "JOHN Q. PUBLIC", "john q. public" });
+    }
+
+    public void testMixed() {
+        doAssertions("John Q. Public was born on Thursday, January 1, 1970.",
+                "{0,properName} was born on {1,date,full}.", new Object[] {
+                        "john q. public",
+                        new GregorianCalendar(1970, Calendar.JANUARY, 01, 0,
+                                15, 20).getTime() });
+    }
+}

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

Added: commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatTest.java?rev=590106&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatTest.java (added)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/MessageFormatTest.java Tue Oct 30 08:06:54 2007
@@ -0,0 +1,22 @@
+package org.apache.commons.lang.text;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+
+/**
+ * Baseline tests for java.text.MessageFormat.
+ * 
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class MessageFormatTest extends AbstractMessageFormatTest {
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.commons.lang.text.AbstractMessageFormatTest#createMessageFormat(java.lang.String)
+     */
+    protected MessageFormat createMessageFormat(String pattern) {
+        return new MessageFormat(pattern, Locale.US);
+    }
+}

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

Modified: commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/TextTestSuite.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/TextTestSuite.java?rev=590106&r1=590105&r2=590106&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/TextTestSuite.java (original)
+++ commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/TextTestSuite.java Tue Oct 30 08:06:54 2007
@@ -57,6 +57,9 @@
         suite.addTest(StrSubstitutorTest.suite());
         suite.addTest(StrTokenizerTest.suite());
         suite.addTestSuite(MultiFormatTest.class);
+        suite.addTestSuite(MessageFormatTest.class);
+        suite.addTestSuite(ExtendedMessageFormatBaselineTest.class);
+        suite.addTestSuite(MessageFormatExtensionTest.class);
         return suite;
     }