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:
Travis Keep 2013-04-03 22:05:00 +00:00
parent 77f8f1e2c2
commit d8a9e311a3
3 changed files with 31 additions and 117 deletions

View file

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

View file

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

View file

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