diff --git a/src/java/org/apache/commons/lang/text/MultiFormat.java b/src/java/org/apache/commons/lang/text/MultiFormat.java
new file mode 100644
index 000000000..fb102a45f
--- /dev/null
+++ b/src/java/org/apache/commons/lang/text/MultiFormat.java
@@ -0,0 +1,159 @@
+/*
+ * 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.ArrayList;
+
+import org.apache.commons.lang.Validate;
+
+/**
+ * Format that tries a number of delegates in turn until one is successful.
+ * Contrast to {@link CompositeFormat}.
+ *
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class MultiFormat extends Format {
+ private static final long serialVersionUID = -6128683973856547540L;
+
+ /**
+ * Provides a builder with a fluent interface. Example:
+ *
+ *
+ *
+ * MultiFormat mf = new MultiFormat.Builder().add(new FooFormat()).add(
+ * new BarFormat()).add(new BazFormat()).toMultiFormat();
+ *
+ *
+ */
+ public static class Builder {
+ private ArrayList delegates = new ArrayList();
+
+ /**
+ * Add a delegate format.
+ *
+ * @param delegate
+ * @return the builder
+ */
+ public Builder add(Format delegate) {
+ Validate.notNull(delegate, "delegate format is null");
+ delegates.add(delegate);
+ return this;
+ }
+
+ /**
+ * Render the {@link MultiFormat} instance from this Builder.
+ *
+ * @return MultiFormat
+ */
+ public MultiFormat toMultiFormat() {
+ return new MultiFormat((Format[]) delegates
+ .toArray(new Format[delegates.size()]));
+ }
+
+ }
+
+ private Format[] delegates;
+
+ /**
+ * Create a new MultiFormat.
+ */
+ public MultiFormat() {
+ }
+
+ /**
+ * Create a new MultiFormat.
+ *
+ * @param delegates
+ */
+ public MultiFormat(Format[] delegates) {
+ setDelegates(delegates);
+ }
+
+ /*
+ * (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) {
+ Format[] d = getValidDelegates();
+ for (int i = 0; i < d.length; i++) {
+ try {
+ return d[i].format(obj, toAppendTo, pos);
+ } catch (IllegalArgumentException e) {
+ continue;
+ }
+ }
+ throw new IllegalArgumentException("No delegate Format can parse "
+ + 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();
+ Format[] d = getDelegates();
+ for (int i = 0; i < d.length; i++) {
+ Object o = d[i].parseObject(source, pos);
+ if (pos.getErrorIndex() < 0) {
+ return o;
+ }
+ // set up for next attempt:
+ pos.setIndex(start);
+ pos.setErrorIndex(-1);
+ }
+ pos.setErrorIndex(start);
+ return null;
+ }
+
+ /**
+ * Set the delegates.
+ *
+ * @param delegates
+ * the Format[] delegates to set.
+ */
+ public void setDelegates(Format[] delegates) {
+ Validate.noNullElements(delegates,
+ "Null elements present in delegates Format[]");
+ this.delegates = delegates;
+ }
+
+ /**
+ * Get the delegates.
+ *
+ * @return Format[].
+ */
+ public Format[] getDelegates() {
+ return delegates;
+ }
+
+ private Format[] getValidDelegates() {
+ Format[] result = getDelegates();
+ Validate.notEmpty(result, "No delegate Formats configured");
+ return result;
+ }
+}
diff --git a/src/test/org/apache/commons/lang/text/MultiFormatTest.java b/src/test/org/apache/commons/lang/text/MultiFormatTest.java
new file mode 100644
index 000000000..c77dac507
--- /dev/null
+++ b/src/test/org/apache/commons/lang/text/MultiFormatTest.java
@@ -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.DateFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.lang.Validate;
+
+import junit.framework.TestCase;
+
+/**
+ * Test MultiFormat
+ *
+ * @author Matt Benson
+ * @since 2.4
+ * @version $Id$
+ */
+public class MultiFormatTest extends TestCase {
+ private class GuardedFormat extends Format {
+ private static final long serialVersionUID = 1L;
+
+ Format delegate;
+ Class[] allowableTypes;
+
+ /**
+ * Create a new MultiFormatTest.GuardedFormat.
+ */
+ public GuardedFormat(Format delegate, Class[] allowableTypes) {
+ Validate.notNull(delegate);
+ this.delegate = delegate;
+ Validate.notNull(allowableTypes);
+ this.allowableTypes = allowableTypes;
+ }
+
+ public StringBuffer format(Object obj, StringBuffer toAppendTo,
+ FieldPosition pos) {
+ Class c = obj == null ? null : obj.getClass();
+ for (int i = 0; i < allowableTypes.length; i++) {
+ if (ClassUtils.isAssignable(c, allowableTypes[i])) {
+ return delegate.format(obj, toAppendTo, pos);
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+
+ public Object parseObject(String source, ParsePosition pos) {
+ return delegate.parseObject(source, pos);
+ }
+ }
+
+ private Format format;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ // silliness to avoid the DateFormat grabbing the Integer, or the
+ // integer parsing the first (month) date component:
+ format = new MultiFormat.Builder().add(
+ new GuardedFormat(DateFormat.getDateInstance(DateFormat.SHORT,
+ Locale.US), new Class[] { Date.class })).add(
+ NumberFormat.getIntegerInstance(Locale.US)).toMultiFormat();
+ }
+
+ public void testWTF() {
+ System.out.println(DateFormat.getDateInstance(DateFormat.SHORT,
+ Locale.US).format(new Integer(1000)));
+ }
+
+ public void testFormatNumber() {
+ assertEquals("1,000", format.format(new Integer(1000)));
+ }
+
+ public void testParseNumber() throws ParseException {
+ assertEquals(new Integer(-1000).intValue(), ((Number) format
+ .parseObject("-1,000")).intValue());
+ }
+
+ public void testFormatDate() {
+ assertEquals("1/1/70", format.format(new GregorianCalendar(1970,
+ Calendar.JANUARY, 01).getTime()));
+ }
+
+ public void testParseDate() throws ParseException {
+ assertEquals(new GregorianCalendar(1970, Calendar.JANUARY, 01)
+ .getTime(), format.parseObject("1/1/70"));
+ }
+
+ public void testFormatObject() {
+ try {
+ format.format(new Object());
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // okay
+ }
+ }
+
+ public void testParseGarbage() {
+ try {
+ format.parseObject("garbage");
+ fail("expected ParseException");
+ } catch (ParseException e) {
+ //okay
+ }
+ }
+}
diff --git a/src/test/org/apache/commons/lang/text/TextTestSuite.java b/src/test/org/apache/commons/lang/text/TextTestSuite.java
index 59f2953c3..cbcb1b126 100644
--- a/src/test/org/apache/commons/lang/text/TextTestSuite.java
+++ b/src/test/org/apache/commons/lang/text/TextTestSuite.java
@@ -56,6 +56,7 @@ public static Test suite() {
suite.addTest(StrMatcherTest.suite());
suite.addTest(StrSubstitutorTest.suite());
suite.addTest(StrTokenizerTest.suite());
+ suite.addTestSuite(MultiFormatTest.class);
return suite;
}