[LANG-362] Add ExtendedMessageFormat

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@590106 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Matthew Jason Benson 2007-10-30 15:06:54 +00:00
parent 013f19fdaf
commit 43cf3f491e
14 changed files with 1794 additions and 0 deletions

View File

@ -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()));
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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() });
}
}

View File

@ -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);
}
}

View File

@ -57,6 +57,9 @@ public class TextTestSuite extends TestCase {
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;
}