mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-08 06:53:45 +00:00
ICU-10017 Git rid of special code in TimePeriod that copies non-immutable Number types. Add comments explaining that Number objects need to be immutable. Add unit test for TestPeriod.equals/hashCode. In JAVA implementation remove extra boolean flag for numeric and treat NUMERIC as a full fledged style as much as possible.
X-SVN-Rev: 33490
This commit is contained in:
parent
77f8f1e2c2
commit
d8a9e311a3
3 changed files with 31 additions and 117 deletions
|
@ -79,9 +79,7 @@ public class TimeUnitFormat extends MeasureFormat {
|
|||
*/
|
||||
public static final int NUMERIC = 2;
|
||||
|
||||
// For now we don't consider NUMERIC a full-fledged style. NUMERIC is
|
||||
// congruent to ABBREVIATED_NAME unless formatPeriod() is being called.
|
||||
private static final int TOTAL_STYLES = 2;
|
||||
private static final int TOTAL_STYLES = 3;
|
||||
|
||||
private static final long serialVersionUID = -3707773153184971529L;
|
||||
|
||||
|
@ -103,10 +101,6 @@ public class TimeUnitFormat extends MeasureFormat {
|
|||
private transient MessageFormat hourMinuteSecond;
|
||||
private transient boolean isReady;
|
||||
private int style;
|
||||
|
||||
// When style is set to NUMERIC, this field is set to true and the style
|
||||
// field becomes ABBREVIATED_NAME.
|
||||
private boolean isNumericStyle;
|
||||
|
||||
/**
|
||||
* Create empty format using full name style, for example, "hours".
|
||||
|
@ -147,10 +141,6 @@ public class TimeUnitFormat extends MeasureFormat {
|
|||
* @stable ICU 4.2
|
||||
*/
|
||||
public TimeUnitFormat(ULocale locale, int style) {
|
||||
if (style == NUMERIC) {
|
||||
style = ABBREVIATED_NAME;
|
||||
isNumericStyle = true;
|
||||
}
|
||||
if (style < FULL_NAME || style >= TOTAL_STYLES) {
|
||||
throw new IllegalArgumentException("style should be either FULL_NAME or ABBREVIATED_NAME style");
|
||||
}
|
||||
|
@ -245,7 +235,9 @@ public class TimeUnitFormat extends MeasureFormat {
|
|||
Map<String, Object[]> countToPattern = timeUnitToCountToPatterns.get(amount.getTimeUnit());
|
||||
double number = amount.getNumber().doubleValue();
|
||||
String count = pluralRules.select(number);
|
||||
MessageFormat pattern = (MessageFormat)(countToPattern.get(count))[style];
|
||||
// A hack since NUMERIC really isn't a full fledged style
|
||||
int effectiveStyle = (style == NUMERIC) ? ABBREVIATED_NAME : style;
|
||||
MessageFormat pattern = (MessageFormat)(countToPattern.get(count))[effectiveStyle];
|
||||
return pattern.format(new Object[]{amount.getNumber()}, toAppendTo, pos);
|
||||
}
|
||||
|
||||
|
@ -259,7 +251,7 @@ public class TimeUnitFormat extends MeasureFormat {
|
|||
if (!isReady) {
|
||||
setup();
|
||||
}
|
||||
if (isNumericStyle) {
|
||||
if (style == NUMERIC) {
|
||||
String result = formatPeriodAsNumeric(timePeriod);
|
||||
if (result != null) {
|
||||
return result;
|
||||
|
@ -296,6 +288,10 @@ public class TimeUnitFormat extends MeasureFormat {
|
|||
for (Entry<String, Object[]> patternEntry : countToPattern.entrySet()) {
|
||||
String count = patternEntry.getKey();
|
||||
for (int styl = FULL_NAME; styl < TOTAL_STYLES; ++styl) {
|
||||
if (styl == NUMERIC) {
|
||||
// Numeric isn't a real style, so skip it.
|
||||
continue;
|
||||
}
|
||||
MessageFormat pattern = (MessageFormat)(patternEntry.getValue())[styl];
|
||||
pos.setErrorIndex(-1);
|
||||
pos.setIndex(oldPos);
|
||||
|
|
|
@ -6,15 +6,10 @@
|
|||
*/
|
||||
package com.ibm.icu.util;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import com.ibm.icu.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* TimePeriod represents a time period. TimePeriod objects are immutable.
|
||||
* <p>Example usage:
|
||||
|
@ -60,9 +55,8 @@ public final class TimePeriod implements Iterable<TimeUnitAmount> {
|
|||
* Returns a new TimePeriod that matches the given time unit amounts.
|
||||
* @param amounts the TimeUnitAmounts. Must be non-empty. Normalization of the
|
||||
* amounts and inclusion/exclusion of 0 amounts is up to caller. The Number
|
||||
* in each TimeUnitAmount must either be a Byte, Short, Integer, Long, Float,
|
||||
* Double, BigInteger, or BigDecimal or it must implement Cloneable and have
|
||||
* a public clone method.
|
||||
* object in each TimeUnitAmount must not change. Otherwise the created
|
||||
* TimePeriod object may not work as expected.
|
||||
* @return the new TimePeriod object
|
||||
* @throws IllegalArgumentException if multiple TimeUnitAmount objects match
|
||||
* the same time unit or if any but the smallest TimeUnit has a fractional value
|
||||
|
@ -81,7 +75,7 @@ public final class TimePeriod implements Iterable<TimeUnitAmount> {
|
|||
// This line is necessary to guarantee immutability of the TimePeriod
|
||||
// class. A Number object, which is in TimeUnitAmount, need not be immutable,
|
||||
// but Double is immutable.
|
||||
fields[index] = cloneIfNecessary(tua);
|
||||
fields[index] = tua;
|
||||
size++;
|
||||
}
|
||||
if (size == 0) {
|
||||
|
@ -111,7 +105,7 @@ public final class TimePeriod implements Iterable<TimeUnitAmount> {
|
|||
* @draft ICU 52
|
||||
*/
|
||||
public TimeUnitAmount getAmount(TimeUnit timeUnit) {
|
||||
return cloneIfNecessary(fields[timeUnit.getIndex()]);
|
||||
return fields[timeUnit.getIndex()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,43 +160,6 @@ public final class TimePeriod implements Iterable<TimeUnitAmount> {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static TimeUnitAmount cloneIfNecessary(TimeUnitAmount tua) {
|
||||
if (tua == null) {
|
||||
return null;
|
||||
}
|
||||
Number number = tua.getNumber();
|
||||
if (number instanceof Double || number instanceof Integer ||
|
||||
number instanceof Long || number instanceof Float ||
|
||||
number instanceof Byte || number instanceof Short ||
|
||||
number instanceof BigInteger || number instanceof BigDecimal) {
|
||||
return tua;
|
||||
}
|
||||
return new TimeUnitAmount(cloneNumber(number), tua.getTimeUnit());
|
||||
}
|
||||
|
||||
private static Number cloneNumber(Number number) {
|
||||
// Since Number doesn't implement Cloneable, we have to use reflection
|
||||
// to find the clone() method. If this method throws an exception, it
|
||||
// means that the subclass of Number can't be cloned.
|
||||
Class<? extends Number> clz = number.getClass();
|
||||
Method cloneMethod;
|
||||
try {
|
||||
cloneMethod = clz.getMethod("clone");
|
||||
cloneMethod.setAccessible(true);
|
||||
return (Number) cloneMethod.invoke(number);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class TPIterator implements Iterator<TimeUnitAmount> {
|
||||
|
||||
private int index = 0;
|
||||
|
|
|
@ -88,6 +88,7 @@ public class TimeUnitTest extends TestFmwk {
|
|||
}
|
||||
|
||||
public void TestAPI() {
|
||||
try {
|
||||
TimeUnitFormat format = new TimeUnitFormat();
|
||||
format.setLocale(new ULocale("pt_BR"));
|
||||
formatParsing(format);
|
||||
|
@ -110,6 +111,9 @@ public class TimeUnitTest extends TestFmwk {
|
|||
format = new TimeUnitFormat(new Locale("ja"));
|
||||
format.setNumberFormat(NumberFormat.getNumberInstance(new Locale("en")));
|
||||
formatParsing(format);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -398,21 +402,20 @@ public class TimeUnitTest extends TestFmwk {
|
|||
}
|
||||
}
|
||||
|
||||
public void TestTimePeriodWithMutableNumber() {
|
||||
MutableInt mutableInput = new MutableInt(3);
|
||||
TimePeriod tp = TimePeriod.forAmounts(
|
||||
new TimeUnitAmount(mutableInput, TimeUnit.HOUR));
|
||||
mutableInput.set(5);
|
||||
MutableInt mutableOutput = (MutableInt) tp.getAmount(TimeUnit.HOUR).getNumber();
|
||||
assertEquals(
|
||||
"Mutating input shouldn't affect TimePeriod.",
|
||||
3,
|
||||
mutableOutput.intValue());
|
||||
mutableOutput.set(5);
|
||||
assertEquals(
|
||||
"Mutating output shouldn't affect TimePeriod.",
|
||||
3,
|
||||
((MutableInt) tp.getAmount(TimeUnit.HOUR).getNumber()).intValue());
|
||||
public void TestTimePeriodEqualsHashCode() {
|
||||
TimePeriod our_19m_28s = TimePeriod.forAmounts(
|
||||
new TimeUnitAmount(28.0, TimeUnit.SECOND),
|
||||
new TimeUnitAmount(19.0, TimeUnit.MINUTE));
|
||||
assertEquals("TimePeriod equals", _19m_28s, our_19m_28s);
|
||||
assertEquals("Hash code", _19m_28s.hashCode(), our_19m_28s.hashCode());
|
||||
TimePeriod our_19m_29s = TimePeriod.forAmounts(
|
||||
new TimeUnitAmount(29.0, TimeUnit.SECOND),
|
||||
new TimeUnitAmount(19.0, TimeUnit.MINUTE));
|
||||
assertNotEquals("TimePeriod not equals", _19m_28s, our_19m_29s);
|
||||
|
||||
// It may be possible for non-equal objects to have equal hashCodes, but we
|
||||
// are betting on the probability of that to be miniscule.
|
||||
assertNotEquals("TimePeriod hash not equals", _19m_28s.hashCode(), our_19m_29s.hashCode());
|
||||
}
|
||||
|
||||
private void verifyFormatPeriod(String desc, TimeUnitFormat tuf, Object[][] testData) {
|
||||
|
@ -429,46 +432,4 @@ public class TimeUnitTest extends TestFmwk {
|
|||
errln(builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class MutableInt extends Number implements Cloneable {
|
||||
|
||||
private int value;
|
||||
|
||||
public MutableInt(int x) {
|
||||
value = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void set(int x) {
|
||||
value = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableInt clone() {
|
||||
try {
|
||||
return (MutableInt) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue