diff --git a/.gitattributes b/.gitattributes index 7f14ac68542..7d7b04f6c10 100644 --- a/.gitattributes +++ b/.gitattributes @@ -272,6 +272,7 @@ icu4j/main/classes/core/.settings/edu.umd.cs.findbugs.core.prefs -text icu4j/main/classes/core/.settings/org.eclipse.core.resources.prefs -text icu4j/main/classes/core/.settings/org.eclipse.jdt.core.prefs -text icu4j/main/classes/core/manifest.stub -text +icu4j/main/classes/core/src/com/ibm/icu/impl/DontCareFieldPosition.java -text icu4j/main/classes/core/src/com/ibm/icu/text/QuantityFormatter.java -text icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java -text icu4j/main/classes/currdata/.externalToolBuilders/copy-data-currdata.launch -text diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/DontCareFieldPosition.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/DontCareFieldPosition.java new file mode 100644 index 00000000000..cf65de57883 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/DontCareFieldPosition.java @@ -0,0 +1,41 @@ +/* +********************************************************************** +* Copyright (c) 2004-2013, International Business Machines +* Corporation and others. All Rights Reserved. +********************************************************************** +*/ +package com.ibm.icu.impl; + +import java.text.FieldPosition; + +/** + * DontCareFieldPosition is a subclass of FieldPosition that indicates that the + * caller is not interested in the start and end position of any field. + *

+ * DontCareFieldPosition is a singleton, and its instance is immutable. + *

+ * A format method use fpos == DontCareFieldPosition.INSTANCE + * to tell whether or not it needs to calculate a field position. + * + */ +public final class DontCareFieldPosition extends FieldPosition { + + public static final DontCareFieldPosition INSTANCE = new DontCareFieldPosition(); + + private DontCareFieldPosition() { + // Pick some random number to be sure that we don't accidentally match with + // a field. + super(-913028704); + } + + @Override + public void setBeginIndex(int i) { + // Do nothing + } + + @Override + public void setEndIndex(int i) { + // Do nothing + } + +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java index d1ea18545d1..7511f0fb473 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java @@ -28,6 +28,7 @@ import java.util.Map.Entry; import java.util.MissingResourceException; import java.util.Set; +import com.ibm.icu.impl.DontCareFieldPosition; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.SimpleCache; import com.ibm.icu.util.Currency; @@ -239,6 +240,10 @@ public class MeasureFormat extends UFormat { * then its indices are set to the beginning and end of the first such field * encountered. MeasureFormat itself does not supply any fields. * + * Calling a + * formatMeasures method is preferred over calling + * this method as they give better performance. + * * @param obj must be a Collection, Measure[], or Measure object. * @param toAppendTo Formatted string appended here. * @pram pos Identifies a field in the formatted text. @@ -296,7 +301,7 @@ public class MeasureFormat extends UFormat { */ public String formatMeasures(Measure... measures) { StringBuilder result = this.formatMeasures( - new StringBuilder(), new FieldPosition(0), measures); + new StringBuilder(), DontCareFieldPosition.INSTANCE, measures); return result.toString(); } @@ -335,48 +340,29 @@ public class MeasureFormat extends UFormat { } } - // Zero out our field position so that we can tell when we find our field. - FieldPosition fpos = new FieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField()); - FieldPosition dummyPos = new FieldPosition(0); - - int fieldPositionFoundIndex = -1; - StringBuilder[] results = new StringBuilder[measures.length]; - for (int i = 0; i < measures.length; ++i) { - if (fieldPositionFoundIndex == -1) { - results[i] = formatMeasure(measures[i], new StringBuilder(), fpos); - if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { - fieldPositionFoundIndex = i; - } - } else { - results[i] = formatMeasure(measures[i], new StringBuilder(), dummyPos); - } - } ListFormatter listFormatter = ListFormatter.getInstance(getLocale(), length == FormatWidth.WIDE ? ListFormatter.Style.DURATION : ListFormatter.Style.DURATION_SHORT); - - // Fix up FieldPosition indexes if our field is found. - if (fieldPositionFoundIndex != -1) { - String listPattern = listFormatter.getPatternForNumItems(measures.length); - int positionInPattern = listPattern.indexOf("{" + fieldPositionFoundIndex + "}"); - if (positionInPattern == -1) { - throw new IllegalStateException("Can't find position with ListFormatter."); - } - // Now we have to adjust our position in pattern - // based on the previous values. - for (int i = 0; i < fieldPositionFoundIndex; i++) { - positionInPattern += (results[i].length() - ("{" + i + "}").length()); - } - fieldPosition.setBeginIndex(fpos.getBeginIndex() + positionInPattern); - fieldPosition.setEndIndex(fpos.getEndIndex() + positionInPattern); - } + String[] results = null; + if (fieldPosition == DontCareFieldPosition.INSTANCE) { + // Fast track: No field position. + results = new String[measures.length]; + for (int i = 0; i < measures.length; i++) { + results[i] = formatMeasure(measures[i]); + } + } else { + + // Slow track: Have to calculate field position. + results = formatMeasuresSlowTrack(listFormatter, fieldPosition, measures); + } + // This is safe because appendable is of type T. try { return (T) appendable.append(listFormatter.format((Object[]) results)); } catch (IOException e) { throw new RuntimeException(e); } - } + } /** * Two MeasureFormats, a and b, are equal if and only if they have the same width, @@ -612,6 +598,12 @@ public class MeasureFormat extends UFormat { return unitToStyleToCountToFormat; } + private String formatMeasure(Measure measure) { + return formatMeasure( + measure, new StringBuilder(), + DontCareFieldPosition.INSTANCE).toString(); + } + private T formatMeasure( Measure measure, T appendable, FieldPosition fieldPosition) { Number n = measure.getNumber(); @@ -689,6 +681,43 @@ public class MeasureFormat extends UFormat { return new MeasureProxy(getLocale(), length, numberFormat.get(), CURRENCY_FORMAT); } + private String[] formatMeasuresSlowTrack(ListFormatter listFormatter, FieldPosition fieldPosition, + Measure... measures) { + String[] results = new String[measures.length]; + + // Zero out our field position so that we can tell when we find our field. + FieldPosition fpos = new FieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField()); + + int fieldPositionFoundIndex = -1; + for (int i = 0; i < measures.length; ++i) { + if (fieldPositionFoundIndex == -1) { + results[i] = formatMeasure(measures[i], new StringBuilder(), fpos).toString(); + if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { + fieldPositionFoundIndex = i; + } + } else { + results[i] = formatMeasure(measures[i]); + } + } + + // Fix up FieldPosition indexes if our field is found. + if (fieldPositionFoundIndex != -1) { + String listPattern = listFormatter.getPatternForNumItems(measures.length); + int positionInPattern = listPattern.indexOf("{" + fieldPositionFoundIndex + "}"); + if (positionInPattern == -1) { + throw new IllegalStateException("Can't find position with ListFormatter."); + } + // Now we have to adjust our position in pattern + // based on the previous values. + for (int i = 0; i < fieldPositionFoundIndex; i++) { + positionInPattern += (results[i].length() - ("{" + i + "}").length()); + } + fieldPosition.setBeginIndex(fpos.getBeginIndex() + positionInPattern); + fieldPosition.setEndIndex(fpos.getEndIndex() + positionInPattern); + } + return results; + } + // type is one of "hm", "ms" or "hms" private static DateFormat loadNumericDurationFormat( ICUResourceBundle r, String type) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java index 9fd647175db..ad58a0bf8f4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java @@ -515,7 +515,7 @@ if ( searchPluralCount.equals("other") ) { // boilerplate code to make TimeUnitFormat otherwise follow the contract of // MeasureFormat - + /** * @draft ICU 53 * @provisional diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java index 2d85fa0940c..4e0be3b366f 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java @@ -163,6 +163,11 @@ public class MeasureUnitTest extends TestFmwk { assertSame("Identity check", expected, actual); } } + + public void testFormatMeasureSingleArg() { + MeasureFormat mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE); + assertEquals("", "5 meters", mf.format(new Measure(5, MeasureUnit.METER))); + } public void testMultiples() { ULocale russia = new ULocale("ru");