mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 22:44:49 +00:00
ICU-11445 Bring per unit formatting in MeasureFormat out of tech preview.
X-SVN-Rev: 36885
This commit is contained in:
parent
b3897ebba9
commit
4cbb21d8b5
8 changed files with 625 additions and 240 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -268,6 +268,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/Pair.java -text
|
||||
icu4j/main/classes/currdata/.externalToolBuilders/copy-data-currdata.launch -text
|
||||
icu4j/main/classes/currdata/.settings/org.eclipse.core.resources.prefs -text
|
||||
icu4j/main/classes/currdata/.settings/org.eclipse.jdt.core.prefs -text
|
||||
|
|
53
icu4j/main/classes/core/src/com/ibm/icu/impl/Pair.java
Normal file
53
icu4j/main/classes/core/src/com/ibm/icu/impl/Pair.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2014, International Business Machines Corporation and
|
||||
* others. All Rights Reserved.
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.impl;
|
||||
|
||||
/**
|
||||
* A pair of objects: first and second.
|
||||
*
|
||||
* @param <F> first object type
|
||||
* @param <S> second object type
|
||||
*/
|
||||
public class Pair<F, S> {
|
||||
public final F first;
|
||||
public final S second;
|
||||
|
||||
protected Pair(F first, S second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pair object
|
||||
* @param first must be non-null
|
||||
* @param second must be non-null
|
||||
* @return The pair object.
|
||||
*/
|
||||
public static <F, S> Pair<F, S> of(F first, S second) {
|
||||
if (first == null || second == null) {
|
||||
throw new IllegalArgumentException("Pair.of requires non null values.");
|
||||
}
|
||||
return new Pair<F, S>(first, second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof Pair)) {
|
||||
return false;
|
||||
}
|
||||
Pair<?, ?> rhs = (Pair<?, ?>) other;
|
||||
return first.equals(rhs.first) && second.equals(rhs.second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return first.hashCode() * 37 + second.hashCode();
|
||||
}
|
||||
}
|
|
@ -38,12 +38,15 @@ public class SimplePatternFormatter {
|
|||
// [0] first offset; [1] first placeholderId; [2] second offset;
|
||||
// [3] second placeholderId etc.
|
||||
private final int[] placeholderIdsOrderedByOffset;
|
||||
|
||||
private final boolean firstPlaceholderReused;
|
||||
|
||||
private SimplePatternFormatter(String pattern, PlaceholdersBuilder builder) {
|
||||
this.patternWithoutPlaceholders = pattern;
|
||||
this.placeholderIdsOrderedByOffset =
|
||||
builder.getPlaceholderIdsOrderedByOffset();
|
||||
this.placeholderCount = builder.getPlaceholderCount();
|
||||
this.firstPlaceholderReused = builder.getFirstPlaceholderReused();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,7 +54,7 @@ public class SimplePatternFormatter {
|
|||
* @param pattern The string.
|
||||
* @return the new SimplePatternFormatter object.
|
||||
*/
|
||||
public static SimplePatternFormatter compile(CharSequence pattern) {
|
||||
public static SimplePatternFormatter compile(String pattern) {
|
||||
PlaceholdersBuilder placeholdersBuilder = new PlaceholdersBuilder();
|
||||
PlaceholderIdBuilder idBuilder = new PlaceholderIdBuilder();
|
||||
StringBuilder newPattern = new StringBuilder();
|
||||
|
@ -121,89 +124,92 @@ public class SimplePatternFormatter {
|
|||
return placeholderCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance starts with placeholder with given id.
|
||||
*/
|
||||
public boolean startsWithPlaceholder(int id) {
|
||||
if (placeholderIdsOrderedByOffset.length == 0) {
|
||||
return false;
|
||||
}
|
||||
return (placeholderIdsOrderedByOffset[0] == 0 && placeholderIdsOrderedByOffset[1] == id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given values.
|
||||
*/
|
||||
public String format(CharSequence... values) {
|
||||
return format(new StringBuilder(), null, values).toString();
|
||||
return formatAndAppend(new StringBuilder(), null, values).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given values.
|
||||
*
|
||||
* @param appendTo the result appended here. Optimization: If the pattern this object
|
||||
* represents starts with a placeholder AND appendTo references the value of that same
|
||||
* placeholder (corresponding values parameter must also be a StringBuilder), then that
|
||||
* placeholder value is not copied to appendTo (Its already there). If the value of the
|
||||
* starting placeholder is very large, this optimization can offer huge savings.
|
||||
* @param appendTo the result appended here.
|
||||
* @param offsets position of first value in appendTo stored in offsets[0];
|
||||
* second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
|
||||
* corresponding value is not in appendTo. offsets.length and values.length may
|
||||
* differ. If caller is not interested in offsets, caller may pass null here.
|
||||
* @param values the values
|
||||
* differ. If offsets.length < values.length then only the first offsets are written out;
|
||||
* If offsets.length > values.length then the extra offsets get -1.
|
||||
* If caller is not interested in offsets, caller may pass null here.
|
||||
* @param values the placeholder values. A placeholder value may not be the same object as
|
||||
* appendTo.
|
||||
* @return appendTo
|
||||
*/
|
||||
public StringBuilder format(
|
||||
public StringBuilder formatAndAppend(
|
||||
StringBuilder appendTo, int[] offsets, CharSequence... values) {
|
||||
if (values.length < placeholderCount) {
|
||||
throw new IllegalArgumentException("Too few values.");
|
||||
}
|
||||
int offsetLen = offsets == null ? 0 : offsets.length;
|
||||
for (int i = 0; i < offsetLen; i++) {
|
||||
offsets[i] = -1;
|
||||
PlaceholderValues placeholderValues = new PlaceholderValues(values);
|
||||
if (placeholderValues.isAppendToInAnyIndexExcept(appendTo, -1)) {
|
||||
throw new IllegalArgumentException("Parameter values cannot be the same as appendTo.");
|
||||
}
|
||||
if (placeholderIdsOrderedByOffset.length == 0) {
|
||||
appendTo.append(patternWithoutPlaceholders);
|
||||
return appendTo;
|
||||
}
|
||||
if (placeholderIdsOrderedByOffset[0] > 0 ||
|
||||
appendTo != values[placeholderIdsOrderedByOffset[1]]) {
|
||||
appendTo.append(
|
||||
patternWithoutPlaceholders,
|
||||
0,
|
||||
placeholderIdsOrderedByOffset[0]);
|
||||
setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[1],
|
||||
appendTo.length(),
|
||||
offsets,
|
||||
offsetLen);
|
||||
appendTo.append(values[placeholderIdsOrderedByOffset[1]]);
|
||||
} else {
|
||||
setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[1],
|
||||
0,
|
||||
offsets,
|
||||
offsetLen);
|
||||
}
|
||||
for (int i = 2; i < placeholderIdsOrderedByOffset.length; i += 2) {
|
||||
appendTo.append(
|
||||
patternWithoutPlaceholders,
|
||||
placeholderIdsOrderedByOffset[i - 2],
|
||||
placeholderIdsOrderedByOffset[i]);
|
||||
setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[i + 1],
|
||||
appendTo.length(),
|
||||
offsets,
|
||||
offsetLen);
|
||||
appendTo.append(values[placeholderIdsOrderedByOffset[i + 1]]);
|
||||
}
|
||||
appendTo.append(
|
||||
patternWithoutPlaceholders,
|
||||
placeholderIdsOrderedByOffset[placeholderIdsOrderedByOffset.length - 2],
|
||||
patternWithoutPlaceholders.length());
|
||||
formatReturningOffsetLength(appendTo, offsets, placeholderValues);
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given values.
|
||||
*
|
||||
* @param result The result is stored here overwriting any previously stored value.
|
||||
* @param offsets position of first value in result stored in offsets[0];
|
||||
* second in offsets[1]; third in offsets[2] etc. An offset of -1 means that the
|
||||
* corresponding value is not in result. offsets.length and values.length may
|
||||
* differ. If offsets.length < values.length then only the first offsets are written out;
|
||||
* If offsets.length > values.length then the extra offsets get -1.
|
||||
* If caller is not interested in offsets, caller may pass null here.
|
||||
* @param values the placeholder values. A placeholder value may be result itself in which case
|
||||
* The previous value of result is used.
|
||||
* @return result
|
||||
*/
|
||||
public StringBuilder formatAndReplace(
|
||||
StringBuilder result, int[] offsets, CharSequence... values) {
|
||||
if (values.length < placeholderCount) {
|
||||
throw new IllegalArgumentException("Too few values.");
|
||||
}
|
||||
PlaceholderValues placeholderValues = new PlaceholderValues(values);
|
||||
int placeholderAtStart = getUniquePlaceholderAtStart();
|
||||
|
||||
// If patterns starts with a placeholder and the value for that placeholder
|
||||
// is result, then we can may be able optimize by just appending to result.
|
||||
if (placeholderAtStart >= 0 && values[placeholderAtStart] == result) {
|
||||
|
||||
// If result is the value for other placeholders, call off optimization.
|
||||
if (placeholderValues.isAppendToInAnyIndexExcept(result, placeholderAtStart)) {
|
||||
placeholderValues.snapshotAppendTo(result);
|
||||
result.setLength(0);
|
||||
formatReturningOffsetLength(result, offsets, placeholderValues);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Otherwise we can optimize
|
||||
int offsetLength = formatReturningOffsetLength(result, offsets, placeholderValues);
|
||||
|
||||
// We have to make the offset for the placeholderAtStart placeholder be 0.
|
||||
// Otherwise it would be the length of the previous value of result.
|
||||
if (offsetLength > placeholderAtStart) {
|
||||
offsets[placeholderAtStart] = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (placeholderValues.isAppendToInAnyIndexExcept(result, -1)) {
|
||||
placeholderValues.snapshotAppendTo(result);
|
||||
}
|
||||
result.setLength(0);
|
||||
formatReturningOffsetLength(result, offsets, placeholderValues);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this object using values {0}, {1} etc. Note that this is
|
||||
* not the same as the original pattern string used to build this object.
|
||||
|
@ -214,7 +220,81 @@ public class SimplePatternFormatter {
|
|||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = String.format("{%d}", i);
|
||||
}
|
||||
return format(new StringBuilder(), null, values).toString();
|
||||
return formatAndAppend(new StringBuilder(), null, values).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this pattern with none of the placeholders.
|
||||
*/
|
||||
public String getPatternWithNoPlaceholders() {
|
||||
return patternWithoutPlaceholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just like format, but uses placeholder values exactly as they are.
|
||||
* A placeholder value that is the same object as appendTo is treated
|
||||
* as the empty string. In addition, returns the length of the offsets
|
||||
* array. Returns 0 if offsets is null.
|
||||
*/
|
||||
private int formatReturningOffsetLength(
|
||||
StringBuilder appendTo,
|
||||
int[] offsets,
|
||||
PlaceholderValues values) {
|
||||
int offsetLen = offsets == null ? 0 : offsets.length;
|
||||
for (int i = 0; i < offsetLen; i++) {
|
||||
offsets[i] = -1;
|
||||
}
|
||||
if (placeholderIdsOrderedByOffset.length == 0) {
|
||||
appendTo.append(patternWithoutPlaceholders);
|
||||
return offsetLen;
|
||||
}
|
||||
appendTo.append(
|
||||
patternWithoutPlaceholders,
|
||||
0,
|
||||
placeholderIdsOrderedByOffset[0]);
|
||||
setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[1],
|
||||
appendTo.length(),
|
||||
offsets,
|
||||
offsetLen);
|
||||
CharSequence placeholderValue = values.get(placeholderIdsOrderedByOffset[1]);
|
||||
if (placeholderValue != appendTo) {
|
||||
appendTo.append(placeholderValue);
|
||||
}
|
||||
for (int i = 2; i < placeholderIdsOrderedByOffset.length; i += 2) {
|
||||
appendTo.append(
|
||||
patternWithoutPlaceholders,
|
||||
placeholderIdsOrderedByOffset[i - 2],
|
||||
placeholderIdsOrderedByOffset[i]);
|
||||
setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[i + 1],
|
||||
appendTo.length(),
|
||||
offsets,
|
||||
offsetLen);
|
||||
placeholderValue = values.get(placeholderIdsOrderedByOffset[i + 1]);
|
||||
if (placeholderValue != appendTo) {
|
||||
appendTo.append(placeholderValue);
|
||||
}
|
||||
}
|
||||
appendTo.append(
|
||||
patternWithoutPlaceholders,
|
||||
placeholderIdsOrderedByOffset[placeholderIdsOrderedByOffset.length - 2],
|
||||
patternWithoutPlaceholders.length());
|
||||
return offsetLen;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the placeholder at the beginning of this pattern (e.g 3 for placeholder {3}).
|
||||
* Returns -1 if the beginning of pattern is text or if the placeholder at beginning
|
||||
* of this pattern is used again elsewhere in pattern.
|
||||
*/
|
||||
private int getUniquePlaceholderAtStart() {
|
||||
if (placeholderIdsOrderedByOffset.length == 0
|
||||
|| firstPlaceholderReused || placeholderIdsOrderedByOffset[0] != 0) {
|
||||
return -1;
|
||||
}
|
||||
return placeholderIdsOrderedByOffset[1];
|
||||
}
|
||||
|
||||
private static void setPlaceholderOffset(
|
||||
|
@ -262,6 +342,7 @@ public class SimplePatternFormatter {
|
|||
private static class PlaceholdersBuilder {
|
||||
private List<Integer> placeholderIdsOrderedByOffset = new ArrayList<Integer>();
|
||||
private int placeholderCount = 0;
|
||||
private boolean firstPlaceholderReused = false;
|
||||
|
||||
public void add(int placeholderId, int offset) {
|
||||
placeholderIdsOrderedByOffset.add(offset);
|
||||
|
@ -269,6 +350,12 @@ public class SimplePatternFormatter {
|
|||
if (placeholderId >= placeholderCount) {
|
||||
placeholderCount = placeholderId + 1;
|
||||
}
|
||||
int len = placeholderIdsOrderedByOffset.size();
|
||||
if (len > 2
|
||||
&& placeholderIdsOrderedByOffset.get(len - 1)
|
||||
.equals(placeholderIdsOrderedByOffset.get(1))) {
|
||||
firstPlaceholderReused = true;
|
||||
}
|
||||
}
|
||||
|
||||
public int getPlaceholderCount() {
|
||||
|
@ -282,12 +369,55 @@ public class SimplePatternFormatter {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean getFirstPlaceholderReused() {
|
||||
return firstPlaceholderReused;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this pattern with none of the placeholders.
|
||||
* Represents placeholder values.
|
||||
*/
|
||||
public String getPatternWithNoPlaceholders() {
|
||||
return patternWithoutPlaceholders;
|
||||
private static class PlaceholderValues {
|
||||
private final CharSequence[] values;
|
||||
private CharSequence appendTo;
|
||||
private String appendToCopy;
|
||||
|
||||
public PlaceholderValues(CharSequence ...values) {
|
||||
this.values = values;
|
||||
this.appendTo = null;
|
||||
this.appendToCopy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if appendTo value is at any index besides exceptIndex.
|
||||
*/
|
||||
public boolean isAppendToInAnyIndexExcept(CharSequence appendTo, int exceptIndex) {
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
if (i != exceptIndex && values[i] == appendTo) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each appendTo value, stores the snapshot of it in its place.
|
||||
*/
|
||||
public void snapshotAppendTo(CharSequence appendTo) {
|
||||
this.appendTo = appendTo;
|
||||
this.appendToCopy = appendTo.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return placeholder at given index.
|
||||
*/
|
||||
public CharSequence get(int index) {
|
||||
if (appendTo == null || appendTo != values[index]) {
|
||||
return values[index];
|
||||
}
|
||||
return appendToCopy;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -270,10 +270,8 @@ final public class ListFormatter {
|
|||
throw new IllegalArgumentException("Need {0} and {1} only in pattern " + pattern);
|
||||
}
|
||||
int[] offsets = (recordOffset || offsetRecorded()) ? new int[2] : null;
|
||||
StringBuilder nextBuilder =
|
||||
pattern.startsWithPlaceholder(0) ? current : new StringBuilder();
|
||||
current = pattern.format(
|
||||
nextBuilder, offsets, current, next.toString());
|
||||
pattern.formatAndReplace(
|
||||
current, offsets, current, next.toString());
|
||||
if (offsets != null) {
|
||||
if (offsets[0] == -1 || offsets[1] == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
|
|
|
@ -498,30 +498,38 @@ public class MeasureFormat extends UFormat {
|
|||
}
|
||||
|
||||
/**
|
||||
* Like formatMeasures but formats with a per unit.
|
||||
* Formats a single measure per unit.
|
||||
*
|
||||
* Will format to a string such as "5 kilometers, 300 meters per hour."
|
||||
*
|
||||
* @param appendTo the formatted string appended here.
|
||||
* @param fieldPosition Identifies a field in the formatted text.
|
||||
* @param perUnit for the example above would be MeasureUnit.HOUR.
|
||||
* @param measures the measures to format.
|
||||
* An example of such a formatted string is "3.5 meters per second."
|
||||
*
|
||||
* @param measure the measure object. In above example, 3.5 meters.
|
||||
* @param perUnit the per unit. In above example, it is MeasureUnit.SECOND
|
||||
* @param appendTo formatted string appended here.
|
||||
* @param pos The field position.
|
||||
* @return appendTo.
|
||||
* @internal Technology preview
|
||||
* @deprecated This API is ICU internal only.
|
||||
* @draft ICU 55
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@Deprecated
|
||||
public StringBuilder formatMeasuresPer(
|
||||
StringBuilder appendTo, FieldPosition fieldPosition, MeasureUnit perUnit, Measure... measures) {
|
||||
public StringBuilder formatMeasurePerUnit(
|
||||
Measure measure,
|
||||
MeasureUnit perUnit,
|
||||
StringBuilder appendTo,
|
||||
FieldPosition pos) {
|
||||
MeasureUnit resolvedUnit = MeasureUnit.resolveUnitPerUnit(
|
||||
measure.getUnit(), perUnit);
|
||||
if (resolvedUnit != null) {
|
||||
Measure newMeasure = new Measure(measure.getNumber(), resolvedUnit);
|
||||
return formatMeasure(newMeasure, numberFormat, appendTo, pos);
|
||||
}
|
||||
FieldPosition fpos = new FieldPosition(
|
||||
fieldPosition.getFieldAttribute(), fieldPosition.getField());
|
||||
int offset = withPerUnit(
|
||||
formatMeasures(new StringBuilder(), fpos, measures),
|
||||
pos.getFieldAttribute(), pos.getField());
|
||||
int offset = withPerUnitAndAppend(
|
||||
formatMeasure(measure, numberFormat, new StringBuilder(), fpos),
|
||||
perUnit,
|
||||
appendTo);
|
||||
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
|
||||
fieldPosition.setBeginIndex(fpos.getBeginIndex() + offset);
|
||||
fieldPosition.setEndIndex(fpos.getEndIndex() + offset);
|
||||
pos.setBeginIndex(fpos.getBeginIndex() + offset);
|
||||
pos.setEndIndex(fpos.getEndIndex() + offset);
|
||||
}
|
||||
return appendTo;
|
||||
}
|
||||
|
@ -852,20 +860,21 @@ public class MeasureFormat extends UFormat {
|
|||
return true;
|
||||
}
|
||||
|
||||
private int withPerUnit(CharSequence formatted, MeasureUnit perUnit, StringBuilder appendTo) {
|
||||
private int withPerUnitAndAppend(
|
||||
CharSequence formatted, MeasureUnit perUnit, StringBuilder appendTo) {
|
||||
int[] offsets = new int[1];
|
||||
Map<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern =
|
||||
unitToStyleToPerUnitPattern.get(perUnit);
|
||||
SimplePatternFormatter perUnitPattern = styleToPerUnitPattern.get(formatWidth);
|
||||
if (perUnitPattern != null) {
|
||||
perUnitPattern.format(appendTo, offsets, formatted);
|
||||
perUnitPattern.formatAndAppend(appendTo, offsets, formatted);
|
||||
return offsets[0];
|
||||
}
|
||||
SimplePatternFormatter perPattern = styleToPerPattern.get(formatWidth);
|
||||
Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(perUnit);
|
||||
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
|
||||
String perUnitString = countToFormat.getByVariant("one").getPatternWithNoPlaceholders().trim();
|
||||
perPattern.format(appendTo, offsets, formatted, perUnitString);
|
||||
perPattern.formatAndAppend(appendTo, offsets, formatted, perUnitString);
|
||||
return offsets[0];
|
||||
}
|
||||
|
||||
|
@ -898,7 +907,7 @@ public class MeasureFormat extends UFormat {
|
|||
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
|
||||
SimplePatternFormatter formatter = countToFormat.getByVariant(keyword);
|
||||
int[] offsets = new int[1];
|
||||
formatter.format(appendTo, offsets, formattedNumber);
|
||||
formatter.formatAndAppend(appendTo, offsets, formattedNumber);
|
||||
if (offsets[0] != -1) { // there is a number (may not happen with, say, Arabic dual)
|
||||
// Fix field position
|
||||
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.MissingResourceException;
|
|||
import java.util.Set;
|
||||
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.Pair;
|
||||
import com.ibm.icu.text.UnicodeSet;
|
||||
|
||||
/**
|
||||
|
@ -196,6 +197,16 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
return MeasureUnit.addUnit(type, subType, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* For ICU use only.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static MeasureUnit resolveUnitPerUnit(MeasureUnit unit, MeasureUnit perUnit) {
|
||||
return unitPerUnitToSingleUnit.get(Pair.of(unit, perUnit));
|
||||
}
|
||||
|
||||
static final UnicodeSet ASCII = new UnicodeSet('a', 'z').freeze();
|
||||
static final UnicodeSet ASCII_HYPHEN = new UnicodeSet('-', '-', 'a', 'z').freeze();
|
||||
|
@ -1154,6 +1165,18 @@ public class MeasureUnit implements Serializable {
|
|||
*/
|
||||
public static final MeasureUnit TEASPOON = MeasureUnit.internalGetInstance("volume", "teaspoon");
|
||||
|
||||
private static HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>unitPerUnitToSingleUnit =
|
||||
new HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>();
|
||||
|
||||
static {
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.KILOMETER, MeasureUnit.HOUR), MeasureUnit.KILOMETER_PER_HOUR);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.MILE, MeasureUnit.GALLON), MeasureUnit.MILE_PER_GALLON);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.MILE, MeasureUnit.HOUR), MeasureUnit.MILE_PER_HOUR);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.METER, MeasureUnit.SECOND), MeasureUnit.METER_PER_SECOND);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.LITER, MeasureUnit.KILOMETER), MeasureUnit.LITER_PER_KILOMETER);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.POUND, MeasureUnit.SQUARE_INCH), MeasureUnit.POUND_PER_SQUARE_INCH);
|
||||
}
|
||||
|
||||
// End generated MeasureUnit constants
|
||||
/* Private */
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.TreeMap;
|
|||
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.dev.test.serializable.SerializableTest;
|
||||
import com.ibm.icu.impl.Pair;
|
||||
import com.ibm.icu.impl.Utility;
|
||||
import com.ibm.icu.math.BigDecimal;
|
||||
import com.ibm.icu.text.MeasureFormat;
|
||||
|
@ -44,6 +45,28 @@ import com.ibm.icu.util.ULocale;
|
|||
*/
|
||||
public class MeasureUnitTest extends TestFmwk {
|
||||
|
||||
static class OrderedPair<F extends Comparable, S extends Comparable> extends Pair<F, S> implements Comparable<OrderedPair<F, S>> {
|
||||
|
||||
OrderedPair(F first, S second) {
|
||||
super(first, second);
|
||||
}
|
||||
|
||||
public static <F extends Comparable, S extends Comparable> OrderedPair<F, S> of(F first, S second) {
|
||||
if (first == null || second == null) {
|
||||
throw new IllegalArgumentException("OrderedPair.of requires non null values.");
|
||||
}
|
||||
return new OrderedPair<F, S>(first, second);
|
||||
}
|
||||
|
||||
public int compareTo(OrderedPair<F, S> other) {
|
||||
int result = first.compareTo(other.first);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
return second.compareTo(other.second);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String[] DRAFT_VERSIONS = {"53", "54"};
|
||||
|
||||
private static final HashSet<String> DRAFT_VERSION_SET = new HashSet<String>();
|
||||
|
@ -742,56 +765,59 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
}
|
||||
}
|
||||
|
||||
public void testMultiplesPer() {
|
||||
Object[][] data = new Object[][] {
|
||||
// perUnit pattern
|
||||
{ULocale.ENGLISH, FormatWidth.WIDE, MeasureUnit.SECOND, "2 miles, 1 foot, 2.3 inches per second"},
|
||||
{ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.SECOND, "2 mi, 1 ft, 2.3 inps"},
|
||||
{ULocale.ENGLISH, FormatWidth.NARROW, MeasureUnit.SECOND, "2mi 1\u2032 2.3\u2033/s"},
|
||||
// global per pattern
|
||||
{ULocale.ENGLISH, FormatWidth.WIDE, MeasureUnit.MINUTE, "2 miles, 1 foot, 2.3 inches per minute"},
|
||||
{ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.MINUTE, "2 mi, 1 ft, 2.3 in/min"},
|
||||
{ULocale.ENGLISH, FormatWidth.NARROW, MeasureUnit.MINUTE, "2mi 1\u2032 2.3\u2033/m"}
|
||||
};
|
||||
for (Object[] row : data) {
|
||||
MeasureFormat mf = MeasureFormat.getInstance(
|
||||
(ULocale) row[0], (FormatWidth) row[1]);
|
||||
assertEquals(
|
||||
"testMultiples",
|
||||
row[3],
|
||||
mf.formatMeasuresPer(
|
||||
new StringBuilder(),
|
||||
new FieldPosition(0),
|
||||
(MeasureUnit) row[2],
|
||||
new Measure(2, MeasureUnit.MILE),
|
||||
new Measure(1, MeasureUnit.FOOT),
|
||||
new Measure(2.3, MeasureUnit.INCH)).toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void testSimplePer() {
|
||||
Object DONT_CARE = null;
|
||||
Object[][] data = new Object[][] {
|
||||
// per unit singular
|
||||
{1, MeasureUnit.SECOND, "1 lbps"},
|
||||
// per unit plural
|
||||
{2, MeasureUnit.SECOND, "2 lbps"},
|
||||
// compound singular
|
||||
{1, MeasureUnit.MINUTE, "1 lb/min"},
|
||||
// compound plural
|
||||
{2, MeasureUnit.MINUTE, "2 lb/min"},
|
||||
// per unit pattern
|
||||
{FormatWidth.WIDE, 1.0, MeasureUnit.SECOND, "1 pound per second", DONT_CARE, 0, 0},
|
||||
{FormatWidth.WIDE, 2.0, MeasureUnit.SECOND, "2 pounds per second", DONT_CARE, 0, 0},
|
||||
// compound pattern
|
||||
{FormatWidth.WIDE, 1.0, MeasureUnit.MINUTE, "1 pound per minute", DONT_CARE, 0, 0},
|
||||
{FormatWidth.WIDE, 2.0, MeasureUnit.MINUTE, "2 pounds per minute", DONT_CARE, 0, 0},
|
||||
// per unit
|
||||
{FormatWidth.SHORT, 1.0, MeasureUnit.SECOND, "1 lbps", DONT_CARE, 0, 0},
|
||||
{FormatWidth.SHORT, 2.0, MeasureUnit.SECOND, "2 lbps", DONT_CARE, 0, 0},
|
||||
// compound
|
||||
{FormatWidth.SHORT, 1.0, MeasureUnit.MINUTE, "1 lb/min", DONT_CARE, 0, 0},
|
||||
{FormatWidth.SHORT, 2.0, MeasureUnit.MINUTE, "2 lb/min", DONT_CARE, 0, 0},
|
||||
// per unit
|
||||
{FormatWidth.NARROW, 1.0, MeasureUnit.SECOND, "1#/s", DONT_CARE, 0, 0},
|
||||
{FormatWidth.NARROW, 2.0, MeasureUnit.SECOND, "2#/s", DONT_CARE, 0, 0},
|
||||
// compound
|
||||
{FormatWidth.NARROW, 1.0, MeasureUnit.MINUTE, "1#/m", DONT_CARE, 0, 0},
|
||||
{FormatWidth.NARROW, 2.0, MeasureUnit.MINUTE, "2#/m", DONT_CARE, 0, 0},
|
||||
// field positions
|
||||
{FormatWidth.SHORT, 23.3, MeasureUnit.SECOND, "23.3 lbps", NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3},
|
||||
{FormatWidth.SHORT, 23.3, MeasureUnit.SECOND, "23.3 lbps", NumberFormat.Field.INTEGER, 0, 2},
|
||||
{FormatWidth.SHORT, 23.3, MeasureUnit.MINUTE, "23.3 lb/min", NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3},
|
||||
{FormatWidth.SHORT, 23.3, MeasureUnit.MINUTE, "23.3 lb/min", NumberFormat.Field.INTEGER, 0, 2},
|
||||
|
||||
};
|
||||
|
||||
for (Object[] row : data) {
|
||||
FormatWidth formatWidth = (FormatWidth) row[0];
|
||||
Number amount = (Number) row[1];
|
||||
MeasureUnit perUnit = (MeasureUnit) row[2];
|
||||
String expected = row[3].toString();
|
||||
NumberFormat.Field field = (NumberFormat.Field) row[4];
|
||||
int startOffset = ((Integer) row[5]).intValue();
|
||||
int endOffset = ((Integer) row[6]).intValue();
|
||||
MeasureFormat mf = MeasureFormat.getInstance(
|
||||
ULocale.ENGLISH, FormatWidth.SHORT);
|
||||
ULocale.ENGLISH, formatWidth);
|
||||
FieldPosition pos = field != null ? new FieldPosition(field) : new FieldPosition(0);
|
||||
String prefix = "Prefix: ";
|
||||
assertEquals(
|
||||
"",
|
||||
row[2],
|
||||
mf.formatMeasuresPer(
|
||||
new StringBuilder(),
|
||||
new FieldPosition(0),
|
||||
(MeasureUnit) row[1],
|
||||
new Measure((Number) row[0], MeasureUnit.POUND)).toString());
|
||||
prefix + expected,
|
||||
mf.formatMeasurePerUnit(
|
||||
new Measure(amount, MeasureUnit.POUND),
|
||||
perUnit,
|
||||
new StringBuilder(prefix),
|
||||
pos).toString());
|
||||
if (field != DONT_CARE) {
|
||||
assertEquals("startOffset", startOffset, pos.getBeginIndex() - prefix.length());
|
||||
assertEquals("endOffset", endOffset, pos.getEndIndex() - prefix.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -808,11 +834,11 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
assertEquals(
|
||||
"",
|
||||
row[1],
|
||||
mf.formatMeasuresPer(
|
||||
new StringBuilder(),
|
||||
new FieldPosition(0),
|
||||
mf.formatMeasurePerUnit(
|
||||
new Measure((Number) row[0], MeasureUnit.FOOT),
|
||||
MeasureUnit.SECOND,
|
||||
new Measure((Number) row[0], MeasureUnit.FOOT)).toString());
|
||||
new StringBuilder(),
|
||||
new FieldPosition(0)).toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,64 +955,6 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
|
||||
}
|
||||
|
||||
public void testFieldPositionMultipleWithPer() {
|
||||
MeasureFormat fmt = MeasureFormat.getInstance(
|
||||
ULocale.ENGLISH, FormatWidth.SHORT);
|
||||
FieldPosition pos = new FieldPosition(NumberFormat.Field.INTEGER);
|
||||
String result = fmt.formatMeasuresPer(
|
||||
new StringBuilder(),
|
||||
pos,
|
||||
MeasureUnit.SECOND,
|
||||
new Measure(354, MeasureUnit.METER),
|
||||
new Measure(23, MeasureUnit.CENTIMETER)).toString();
|
||||
assertEquals("result", "354 m, 23 cmps", result);
|
||||
|
||||
// According to javadocs for {@link Format#format} FieldPosition is set to
|
||||
// beginning and end of first such field encountered instead of the last
|
||||
// such field encountered.
|
||||
assertEquals("beginIndex", 0, pos.getBeginIndex());
|
||||
assertEquals("endIndex", 3, pos.getEndIndex());
|
||||
|
||||
pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
result = fmt.formatMeasuresPer(
|
||||
new StringBuilder("123456: "),
|
||||
pos,
|
||||
MeasureUnit.SECOND,
|
||||
new Measure(354, MeasureUnit.METER),
|
||||
new Measure(23, MeasureUnit.CENTIMETER),
|
||||
new Measure(5.4, MeasureUnit.MILLIMETER)).toString();
|
||||
assertEquals("result", "123456: 354 m, 23 cm, 5.4 mmps", result);
|
||||
assertEquals("beginIndex", 23, pos.getBeginIndex());
|
||||
assertEquals("endIndex", 24, pos.getEndIndex());
|
||||
|
||||
pos = new FieldPosition(NumberFormat.Field.INTEGER);
|
||||
result = fmt.formatMeasuresPer(
|
||||
new StringBuilder(),
|
||||
pos,
|
||||
MeasureUnit.MINUTE,
|
||||
new Measure(354, MeasureUnit.METER),
|
||||
new Measure(23, MeasureUnit.CENTIMETER)).toString();
|
||||
assertEquals("result", "354 m, 23 cm/min", result);
|
||||
|
||||
// According to javadocs for {@link Format#format} FieldPosition is set to
|
||||
// beginning and end of first such field encountered instead of the last
|
||||
// such field encountered.
|
||||
assertEquals("beginIndex", 0, pos.getBeginIndex());
|
||||
assertEquals("endIndex", 3, pos.getEndIndex());
|
||||
|
||||
pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
result = fmt.formatMeasuresPer(
|
||||
new StringBuilder("123456: "),
|
||||
pos,
|
||||
MeasureUnit.MINUTE,
|
||||
new Measure(354, MeasureUnit.METER),
|
||||
new Measure(23, MeasureUnit.CENTIMETER),
|
||||
new Measure(5.4, MeasureUnit.MILLIMETER)).toString();
|
||||
assertEquals("result", "123456: 354 m, 23 cm, 5.4 mm/min", result);
|
||||
assertEquals("beginIndex", 23, pos.getBeginIndex());
|
||||
assertEquals("endIndex", 24, pos.getEndIndex());
|
||||
}
|
||||
|
||||
public void testOldFormatWithList() {
|
||||
List<Measure> measures = new ArrayList<Measure>(2);
|
||||
measures.add(new Measure(5, MeasureUnit.ACRE));
|
||||
|
@ -1027,6 +995,19 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
}
|
||||
}
|
||||
|
||||
public void testUnitPerUnitResolution() {
|
||||
// Ticket 11274
|
||||
MeasureFormat fmt = MeasureFormat.getInstance(Locale.ENGLISH, FormatWidth.SHORT);
|
||||
|
||||
// This fails unless we resolve to MeasureUnit.POUND_PER_SQUARE_INCH
|
||||
assertEquals("", "50 psi",
|
||||
fmt.formatMeasurePerUnit(
|
||||
new Measure(50, MeasureUnit.POUND),
|
||||
MeasureUnit.SQUARE_INCH,
|
||||
new StringBuilder(),
|
||||
new FieldPosition(0)).toString());
|
||||
}
|
||||
|
||||
public void testEqHashCode() {
|
||||
MeasureFormat mf = MeasureFormat.getInstance(ULocale.CANADA, FormatWidth.SHORT);
|
||||
MeasureFormat mfeq = MeasureFormat.getInstance(ULocale.CANADA, FormatWidth.SHORT);
|
||||
|
@ -1108,6 +1089,42 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
|
||||
}
|
||||
|
||||
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
|
||||
// for MeasureFormat during the release process.
|
||||
static Map<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> getUnitsToPerParts() {
|
||||
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
|
||||
Map<MeasureUnit, Pair<String, String>> unitsToPerStrings =
|
||||
new HashMap<MeasureUnit, Pair<String, String>>();
|
||||
Map<String, MeasureUnit> namesToUnits = new HashMap<String, MeasureUnit>();
|
||||
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
|
||||
String type = entry.getKey();
|
||||
// Currency types are always atomic units, so we can skip these
|
||||
if (type.equals("currency")) {
|
||||
continue;
|
||||
}
|
||||
for (MeasureUnit unit : entry.getValue()) {
|
||||
String javaName = toJAVAName(unit);
|
||||
String[] nameParts = javaName.split("_PER_");
|
||||
if (nameParts.length == 1) {
|
||||
namesToUnits.put(nameParts[0], unit);
|
||||
} else if (nameParts.length == 2) {
|
||||
unitsToPerStrings.put(unit, Pair.of(nameParts[0], nameParts[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> unitsToPerUnits =
|
||||
new HashMap<MeasureUnit, Pair<MeasureUnit, MeasureUnit>>();
|
||||
for (Map.Entry<MeasureUnit, Pair<String, String>> entry : unitsToPerStrings.entrySet()) {
|
||||
Pair<String, String> perStrings = entry.getValue();
|
||||
MeasureUnit unit = namesToUnits.get(perStrings.first);
|
||||
MeasureUnit perUnit = namesToUnits.get(perStrings.second);
|
||||
if (unit != null && perUnit != null) {
|
||||
unitsToPerUnits.put(entry.getKey(), Pair.of(unit, perUnit));
|
||||
}
|
||||
}
|
||||
return unitsToPerUnits;
|
||||
}
|
||||
|
||||
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
|
||||
// for MeasureFormat during the release process.
|
||||
static void generateCXXHConstants(String thisVersion) {
|
||||
|
@ -1196,9 +1213,9 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
|
||||
// for MeasureFormat during the release process.
|
||||
static void generateCXXConstants() {
|
||||
System.out.println("static final MeasureUnit");
|
||||
Map<String, MeasureUnit> seen = new HashMap<String, MeasureUnit>();
|
||||
System.out.println("");
|
||||
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
|
||||
|
||||
System.out.println("static const int32_t gOffsets[] = {");
|
||||
int index = 0;
|
||||
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
|
||||
|
@ -1219,6 +1236,7 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
System.out.printf(" %d\n", index);
|
||||
System.out.println("};");
|
||||
System.out.println();
|
||||
System.out.println("// Must be sorted alphabetically.");
|
||||
System.out.println("static const char * const gTypes[] = {");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
|
||||
|
@ -1231,47 +1249,89 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
System.out.println();
|
||||
System.out.println("};");
|
||||
System.out.println();
|
||||
System.out.println("// Must be grouped by type and sorted alphabetically within each type.");
|
||||
System.out.println("static const char * const gSubTypes[] = {");
|
||||
first = true;
|
||||
int offset = 0;
|
||||
int typeIdx = 0;
|
||||
Map<MeasureUnit, Integer> measureUnitToOffset = new HashMap<MeasureUnit, Integer>();
|
||||
Map<MeasureUnit, Pair<Integer, Integer>> measureUnitToTypeSubType =
|
||||
new HashMap<MeasureUnit, Pair<Integer, Integer>>();
|
||||
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
|
||||
int subTypeIdx = 0;
|
||||
for (MeasureUnit unit : entry.getValue()) {
|
||||
if (!first) {
|
||||
System.out.println(",");
|
||||
}
|
||||
System.out.print(" \"" + unit.getSubtype() + "\"");
|
||||
first = false;
|
||||
measureUnitToOffset.put(unit, offset);
|
||||
measureUnitToTypeSubType.put(unit, Pair.of(typeIdx, subTypeIdx));
|
||||
offset++;
|
||||
subTypeIdx++;
|
||||
}
|
||||
typeIdx++;
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println("};");
|
||||
System.out.println();
|
||||
|
||||
int typeIdx = 0;
|
||||
// Build unit per unit offsets to corresponding type sub types sorted by
|
||||
// unit first and then per unit.
|
||||
TreeMap<OrderedPair<Integer, Integer>, Pair<Integer, Integer>> unitPerUnitOffsetsToTypeSubType
|
||||
= new TreeMap<OrderedPair<Integer, Integer>, Pair<Integer, Integer>>();
|
||||
for (Map.Entry<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> entry
|
||||
: getUnitsToPerParts().entrySet()) {
|
||||
Pair<MeasureUnit, MeasureUnit> unitPerUnit = entry.getValue();
|
||||
unitPerUnitOffsetsToTypeSubType.put(
|
||||
OrderedPair.of(
|
||||
measureUnitToOffset.get(unitPerUnit.first),
|
||||
measureUnitToOffset.get(unitPerUnit.second)),
|
||||
measureUnitToTypeSubType.get(entry.getKey()));
|
||||
}
|
||||
|
||||
System.out.println("// Must be sorted by first value and then second value.");
|
||||
System.out.println("static int32_t unitPerUnitToSingleUnit[][4] = {");
|
||||
first = true;
|
||||
for (Map.Entry<OrderedPair<Integer, Integer>, Pair<Integer, Integer>> entry
|
||||
: unitPerUnitOffsetsToTypeSubType.entrySet()) {
|
||||
if (!first) {
|
||||
System.out.println(",");
|
||||
}
|
||||
first = false;
|
||||
OrderedPair<Integer, Integer> unitPerUnitOffsets = entry.getKey();
|
||||
Pair<Integer, Integer> typeSubType = entry.getValue();
|
||||
System.out.printf(" {%d, %d, %d, %d}",
|
||||
unitPerUnitOffsets.first,
|
||||
unitPerUnitOffsets.second,
|
||||
typeSubType.first,
|
||||
typeSubType.second);
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println("};");
|
||||
System.out.println();
|
||||
|
||||
Map<String, MeasureUnit> seen = new HashMap<String, MeasureUnit>();
|
||||
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
|
||||
int subTypeIdx = 0;
|
||||
|
||||
String type = entry.getKey();
|
||||
if (type.equals("currency")) {
|
||||
typeIdx++;
|
||||
continue;
|
||||
}
|
||||
for (MeasureUnit unit : entry.getValue()) {
|
||||
String name = toCamelCase(unit);
|
||||
String javaName = toJAVAName(unit);
|
||||
Pair<Integer, Integer> typeSubType = measureUnitToTypeSubType.get(unit);
|
||||
if (typeSubType == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
checkForDup(seen, name, unit);
|
||||
if (isDraft(javaName)) {
|
||||
System.out.println("#ifndef U_HIDE_DRAFT_API");
|
||||
}
|
||||
System.out.printf("MeasureUnit *MeasureUnit::create%s(UErrorCode &status) {\n", name);
|
||||
System.out.printf(" return MeasureUnit::create(%d, %d, status);\n", typeIdx, subTypeIdx);
|
||||
System.out.printf(" return MeasureUnit::create(%d, %d, status);\n",
|
||||
typeSubType.first, typeSubType.second);
|
||||
System.out.println("}");
|
||||
if (isDraft(javaName)) {
|
||||
System.out.println("#endif /* U_HIDE_DRAFT_API */");
|
||||
}
|
||||
System.out.println();
|
||||
subTypeIdx++;
|
||||
System.out.println();
|
||||
}
|
||||
typeIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String toCamelCase(MeasureUnit unit) {
|
||||
|
@ -1403,6 +1463,16 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
System.out.println();
|
||||
}
|
||||
}
|
||||
System.out.println(" private static HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>unitPerUnitToSingleUnit =");
|
||||
System.out.println(" new HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>();");
|
||||
System.out.println();
|
||||
System.out.println(" static {");
|
||||
for (Map.Entry<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> unitPerUnitEntry
|
||||
: getUnitsToPerParts().entrySet()) {
|
||||
Pair<MeasureUnit, MeasureUnit> unitPerUnit = unitPerUnitEntry.getValue();
|
||||
System.out.println(" unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit." + toJAVAName(unitPerUnit.first) + ", MeasureUnit." + toJAVAName(unitPerUnit.second) + "), MeasureUnit." + toJAVAName(unitPerUnitEntry.getKey()) + ");");
|
||||
}
|
||||
System.out.println(" }");
|
||||
}
|
||||
|
||||
private static String getVersion(String javaName, String thisVersion) {
|
||||
|
|
|
@ -48,7 +48,7 @@ public class SimplePatternFormatterTest extends TestFmwk {
|
|||
assertEquals(
|
||||
"toString2",
|
||||
"This doesn't have templates {0}",
|
||||
fmt.format(new StringBuilder(), offsets).toString());
|
||||
fmt.formatAndAppend(new StringBuilder(), offsets).toString());
|
||||
assertEquals(
|
||||
"offsets[0]",
|
||||
-1,
|
||||
|
@ -78,20 +78,38 @@ public class SimplePatternFormatterTest extends TestFmwk {
|
|||
"Templates {1}{2} and {3} are here.").getPatternWithNoPlaceholders());
|
||||
}
|
||||
|
||||
public void TestWithPlaceholders() {
|
||||
public void TestTooFewPlaceholderValues() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
|
||||
"Templates {2}{1} and {4} are out of order.");
|
||||
assertFalse("startsWithPlaceholder", fmt.startsWithPlaceholder(2));
|
||||
assertEquals(
|
||||
"getPlaceholderCount",
|
||||
5,
|
||||
fmt.getPlaceholderCount());
|
||||
try {
|
||||
fmt.format("freddy", "tommy", "frog", "leg");
|
||||
fail("Expected UnsupportedOperationException");
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected
|
||||
}
|
||||
try {
|
||||
fmt.formatAndAppend(
|
||||
new StringBuilder(), null, "freddy", "tommy", "frog", "leg");
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected
|
||||
}
|
||||
try {
|
||||
fmt.formatAndReplace(
|
||||
new StringBuilder(), null, "freddy", "tommy", "frog", "leg");
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
public void TestWithPlaceholders() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
|
||||
"Templates {2}{1} and {4} are out of order.");
|
||||
assertEquals(
|
||||
"getPlaceholderCount",
|
||||
5,
|
||||
fmt.getPlaceholderCount());
|
||||
assertEquals(
|
||||
"toString",
|
||||
"Templates {2}{1} and {4} are out of order.",
|
||||
|
@ -100,38 +118,121 @@ public class SimplePatternFormatterTest extends TestFmwk {
|
|||
assertEquals(
|
||||
"format",
|
||||
"123456: Templates frogtommy and {0} are out of order.",
|
||||
fmt.format(
|
||||
fmt.formatAndAppend(
|
||||
new StringBuilder("123456: "),
|
||||
offsets,
|
||||
"freddy", "tommy", "frog", "leg", "{0}").toString());
|
||||
|
||||
int[] expectedOffsets = {-1, 22, 18, -1, 32, -1};
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
if (offsets[i] != expectedOffsets[i]) {
|
||||
fail("getOffset() returned wrong value for " + i);
|
||||
}
|
||||
verifyOffsets(expectedOffsets, offsets);
|
||||
}
|
||||
|
||||
public void TestFormatUseAppendToAsPlaceholder() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
|
||||
"Placeholders {0} and {1}");
|
||||
StringBuilder appendTo = new StringBuilder("previous:");
|
||||
try {
|
||||
fmt.formatAndAppend(appendTo, null, appendTo, "frog");
|
||||
fail("IllegalArgumentException expected.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected.
|
||||
}
|
||||
}
|
||||
|
||||
public void TestOptimization() {
|
||||
public void TestFormatReplaceNoOptimization() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile("{2}, {0}, {1} and {3}");
|
||||
assertTrue("startsWithPlaceholder", fmt.startsWithPlaceholder(2));
|
||||
assertFalse("startsWithPlaceholder", fmt.startsWithPlaceholder(0));
|
||||
int[] offsets = new int[4];
|
||||
StringBuilder appendTo = new StringBuilder("leg");
|
||||
StringBuilder result = new StringBuilder("original");
|
||||
assertEquals(
|
||||
"format",
|
||||
"leg, freddy, frog and by",
|
||||
fmt.format(
|
||||
appendTo,
|
||||
"frog, original, freddy and by",
|
||||
fmt.formatAndReplace(
|
||||
result,
|
||||
offsets,
|
||||
"freddy", "frog", appendTo, "by").toString());
|
||||
result, "freddy", "frog", "by").toString());
|
||||
|
||||
int[] expectedOffsets = {5, 13, 0, 22};
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
if (offsets[i] != expectedOffsets[i]) {
|
||||
fail("getOffset() returned wrong value for " + i);
|
||||
}
|
||||
}
|
||||
int[] expectedOffsets = {6, 16, 0, 27};
|
||||
verifyOffsets(expectedOffsets, offsets);
|
||||
}
|
||||
|
||||
|
||||
public void TestFormatReplaceNoOptimizationLeadingText() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile("boo {2}, {0}, {1} and {3}");
|
||||
int[] offsets = new int[4];
|
||||
StringBuilder result = new StringBuilder("original");
|
||||
assertEquals(
|
||||
"format",
|
||||
"boo original, freddy, frog and by",
|
||||
fmt.formatAndReplace(
|
||||
result,
|
||||
offsets,
|
||||
"freddy", "frog", result, "by").toString());
|
||||
|
||||
int[] expectedOffsets = {14, 22, 4, 31};
|
||||
verifyOffsets(expectedOffsets, offsets);
|
||||
}
|
||||
|
||||
public void TestFormatReplaceOptimization() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile("{2}, {0}, {1} and {3}");
|
||||
int[] offsets = new int[4];
|
||||
StringBuilder result = new StringBuilder("original");
|
||||
assertEquals(
|
||||
"format",
|
||||
"original, freddy, frog and by",
|
||||
fmt.formatAndReplace(
|
||||
result,
|
||||
offsets,
|
||||
"freddy", "frog", result, "by").toString());
|
||||
|
||||
int[] expectedOffsets = {10, 18, 0, 27};
|
||||
verifyOffsets(expectedOffsets, offsets);
|
||||
}
|
||||
|
||||
public void TestFormatReplaceOptimizationNoOffsets() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile("{2}, {0}, {1} and {3}");
|
||||
StringBuilder result = new StringBuilder("original");
|
||||
assertEquals(
|
||||
"format",
|
||||
"original, freddy, frog and by",
|
||||
fmt.formatAndReplace(
|
||||
result,
|
||||
null,
|
||||
"freddy", "frog", result, "by").toString());
|
||||
|
||||
}
|
||||
|
||||
public void TestFormatReplaceNoOptimizationNoOffsets() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
|
||||
"Placeholders {0} and {1}");
|
||||
StringBuilder result = new StringBuilder("previous:");
|
||||
assertEquals(
|
||||
"",
|
||||
"Placeholders previous: and frog",
|
||||
fmt.formatAndReplace(result, null, result, "frog").toString());
|
||||
}
|
||||
|
||||
public void TestFormatReplaceNoOptimizationLeadingPlaceholderUsedTwice() {
|
||||
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
|
||||
"{2}, {0}, {1} and {3} {2}");
|
||||
StringBuilder result = new StringBuilder("original");
|
||||
int[] offsets = new int[4];
|
||||
assertEquals(
|
||||
"",
|
||||
"original, freddy, frog and by original",
|
||||
fmt.formatAndReplace(
|
||||
result,
|
||||
offsets,
|
||||
"freddy", "frog", result, "by").toString());
|
||||
int[] expectedOffsets = {10, 18, 30, 27};
|
||||
verifyOffsets(expectedOffsets, offsets);
|
||||
}
|
||||
|
||||
void verifyOffsets(int[] expected, int[] actual) {
|
||||
for (int i = 0; i < expected.length; ++i) {
|
||||
if (expected[i] != actual[i]) {
|
||||
errln("Expected "+expected[i]+", got " + actual[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue