1cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath/* GENERATED SOURCE. DO NOT MODIFY. */
2cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath// © 2017 and later: Unicode, Inc. and others.
3cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath// License & terms of use: http://www.unicode.org/copyright.html#License
4cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathpackage android.icu.dev.test.number;
5cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
6cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport static org.junit.Assert.assertEquals;
7cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport static org.junit.Assert.assertNotEquals;
8cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport static org.junit.Assert.assertTrue;
9cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport static org.junit.Assert.fail;
10cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
11cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.io.ByteArrayInputStream;
12cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.io.ByteArrayOutputStream;
13cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.io.IOException;
14cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.io.ObjectInputStream;
15cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.io.ObjectOutputStream;
16cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.lang.reflect.Field;
17cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.lang.reflect.InvocationTargetException;
18ab762bb740405d0fefcccf4a0899a234f995be13Narayan Kamathimport java.lang.reflect.Method;
19cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.lang.reflect.Modifier;
20cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath// TODO: enable in Java 8: import java.lang.reflect.Parameter;
21cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.math.BigDecimal;
22cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.math.BigInteger;
23cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.math.MathContext;
24cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.math.RoundingMode;
25cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.util.HashMap;
26cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.util.HashSet;
27cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.util.Map;
28cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport java.util.Set;
29cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
30cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport org.junit.Test;
31cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
32cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.dev.test.serializable.SerializableTestUtility;
33cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.impl.number.DecimalFormatProperties;
34cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.impl.number.Padder.PadPosition;
35cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.impl.number.Parse.GroupingMode;
36cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.impl.number.Parse.ParseMode;
37cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.impl.number.PatternStringParser;
38cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.text.CompactDecimalFormat.CompactStyle;
39cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.text.CurrencyPluralInfo;
40cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.text.MeasureFormat.FormatWidth;
41cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.text.PluralRules;
42cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.util.Currency;
43cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.util.Currency.CurrencyUsage;
44cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.util.MeasureUnit;
45cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.util.ULocale;
46cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathimport android.icu.testsharding.MainTestShard;
47cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
48cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath@MainTestShard
49cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamathpublic class PropertiesTest {
50cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
51cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath  @Test
52cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath  public void testBasicEquals() {
53cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p1 = new DecimalFormatProperties();
54cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p2 = new DecimalFormatProperties();
55cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    assertEquals(p1, p2);
56cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
57cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    p1.setPositivePrefix("abc");
58cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    assertNotEquals(p1, p2);
59cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    p2.setPositivePrefix("xyz");
60cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    assertNotEquals(p1, p2);
61cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    p1.setPositivePrefix("xyz");
62cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    assertEquals(p1, p2);
63cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath  }
64cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
65cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath  @Test
66cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath  public void testFieldCoverage() {
67cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p0 = new DecimalFormatProperties();
68cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p1 = new DecimalFormatProperties();
69cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p2 = new DecimalFormatProperties();
70cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p3 = new DecimalFormatProperties();
71cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    DecimalFormatProperties p4 = new DecimalFormatProperties();
72cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
73cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    Set<Integer> hashCodes = new HashSet<Integer>();
74cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
75cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath    for (Field field : fields) {
76cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath      if (Modifier.isStatic(field.getModifiers())) {
77cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath        continue;
78cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath      }
79cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath
80cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath      // Check for getters and setters
81cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath      String fieldNamePascalCase =
82cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath          Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1);
83cb318c6f4fe5b0e20099fa85f1b95ccb2d24119fNarayan Kamath      String getterName = "get" + fieldNamePascalCase;
84      String setterName = "set" + fieldNamePascalCase;
85      Method getter, setter;
86      try {
87        getter = DecimalFormatProperties.class.getMethod(getterName);
88        assertEquals(
89            "Getter does not return correct type", field.getType(), getter.getReturnType());
90      } catch (NoSuchMethodException e) {
91        fail("Could not find method " + getterName + " for field " + field);
92        continue;
93      } catch (SecurityException e) {
94        fail("Could not access method " + getterName + " for field " + field);
95        continue;
96      }
97      try {
98        setter = DecimalFormatProperties.class.getMethod(setterName, field.getType());
99        assertEquals(
100            "Method " + setterName + " does not return correct type",
101            DecimalFormatProperties.class,
102            setter.getReturnType());
103      } catch (NoSuchMethodException e) {
104        fail("Could not find method " + setterName + " for field " + field);
105        continue;
106      } catch (SecurityException e) {
107        fail("Could not access method " + setterName + " for field " + field);
108        continue;
109      }
110
111      // Check for parameter name equality.
112      // The parameter name is not always available, depending on compiler settings.
113      // TODO: Enable in Java 8
114      /*
115      Parameter param = setter.getParameters()[0];
116      if (!param.getName().subSequence(0, 3).equals("arg")) {
117        assertEquals("Parameter name should equal field name", field.getName(), param.getName());
118      }
119      */
120
121      try {
122        // Check for default value (should be null for objects)
123        if (field.getType() != Integer.TYPE && field.getType() != Boolean.TYPE) {
124          Object default0 = getter.invoke(p0);
125          assertEquals("Field " + field + " has non-null default value:", null, default0);
126        }
127
128        // Check for getter, equals, and hash code behavior
129        Object val0 = getSampleValueForType(field.getType(), 0);
130        Object val1 = getSampleValueForType(field.getType(), 1);
131        Object val2 = getSampleValueForType(field.getType(), 2);
132        assertNotEquals(val0, val1);
133        setter.invoke(p1, val0);
134        setter.invoke(p2, val0);
135        assertEquals(p1, p2);
136        assertEquals(p1.hashCode(), p2.hashCode());
137        assertEquals(getter.invoke(p1), getter.invoke(p2));
138        assertEquals(getter.invoke(p1), val0);
139        assertNotEquals(getter.invoke(p1), val1);
140        hashCodes.add(p1.hashCode());
141        setter.invoke(p1, val1);
142        assertNotEquals("Field " + field + " is missing from equals()", p1, p2);
143        assertNotEquals(getter.invoke(p1), getter.invoke(p2));
144        assertNotEquals(getter.invoke(p1), val0);
145        assertEquals(getter.invoke(p1), val1);
146        setter.invoke(p1, val0);
147        assertEquals("Field " + field + " setter might have side effects", p1, p2);
148        assertEquals(p1.hashCode(), p2.hashCode());
149        assertEquals(getter.invoke(p1), getter.invoke(p2));
150        setter.invoke(p1, val1);
151        setter.invoke(p2, val1);
152        assertEquals(p1, p2);
153        assertEquals(p1.hashCode(), p2.hashCode());
154        assertEquals(getter.invoke(p1), getter.invoke(p2));
155        setter.invoke(p1, val2);
156        setter.invoke(p1, val1);
157        assertEquals("Field " + field + " setter might have side effects", p1, p2);
158        assertEquals(p1.hashCode(), p2.hashCode());
159        assertEquals(getter.invoke(p1), getter.invoke(p2));
160        hashCodes.add(p1.hashCode());
161
162        // Check for clone behavior
163        DecimalFormatProperties copy = p1.clone();
164        assertEquals("Field " + field + " did not get copied in clone", p1, copy);
165        assertEquals(p1.hashCode(), copy.hashCode());
166        assertEquals(getter.invoke(p1), getter.invoke(copy));
167
168        // Check for copyFrom behavior
169        setter.invoke(p1, val0);
170        assertNotEquals(p1, p2);
171        assertNotEquals(getter.invoke(p1), getter.invoke(p2));
172        p2.copyFrom(p1);
173        assertEquals("Field " + field + " is missing from copyFrom()", p1, p2);
174        assertEquals(p1.hashCode(), p2.hashCode());
175        assertEquals(getter.invoke(p1), getter.invoke(p2));
176
177        // Load values into p3 and p4 for clear() behavior test
178        setter.invoke(p3, getSampleValueForType(field.getType(), 3));
179        hashCodes.add(p3.hashCode());
180        setter.invoke(p4, getSampleValueForType(field.getType(), 4));
181        hashCodes.add(p4.hashCode());
182      } catch (IllegalAccessException e) {
183        fail("Could not access method for field " + field);
184      } catch (IllegalArgumentException e) {
185        fail("Could call method for field " + field);
186      } catch (InvocationTargetException e) {
187        fail("Could invoke method on target for field " + field);
188      }
189    }
190
191    // Check for clear() behavior
192    assertNotEquals(p3, p4);
193    p3.clear();
194    p4.clear();
195    assertEquals("A field is missing from the clear() function", p3, p4);
196
197    // A good hashCode() implementation should produce very few collisions.  We added at most
198    // 4*fields.length codes to the set.  We'll say the implementation is good if we had at least
199    // fields.length unique values.
200    // TODO: Should the requirement be stronger than this?
201    assertTrue(
202        "Too many hash code collisions: " + hashCodes.size() + " out of " + (fields.length * 4),
203        hashCodes.size() >= fields.length);
204  }
205
206  /**
207   * Creates a valid sample instance of the given type. Used to simulate getters and setters.
208   *
209   * @param type The type to generate.
210   * @param seed An integer seed, guaranteed to be positive. The same seed should generate two
211   *     instances that are equal. A different seed should in general generate two instances that
212   *     are not equal; this might not always be possible, such as with booleans or enums where
213   *     there are limited possible values.
214   * @return An instance of the specified type.
215   */
216  Object getSampleValueForType(Class<?> type, int seed) {
217    if (type == Integer.TYPE) {
218      return seed * 1000001;
219
220    } else if (type == Boolean.TYPE) {
221      return (seed % 2) == 0;
222
223    } else if (type == BigDecimal.class) {
224      if (seed == 0) return null;
225      return new BigDecimal(seed * 1000002);
226
227    } else if (type == String.class) {
228      if (seed == 0) return null;
229      return BigInteger.valueOf(seed * 1000003).toString(32);
230
231    } else if (type == CompactStyle.class) {
232      if (seed == 0) return null;
233      CompactStyle[] values = CompactStyle.values();
234      return values[seed % values.length];
235
236    } else if (type == Currency.class) {
237      if (seed == 0) return null;
238      Object[] currencies = Currency.getAvailableCurrencies().toArray();
239      return currencies[seed % currencies.length];
240
241    } else if (type == CurrencyPluralInfo.class) {
242      if (seed == 0) return null;
243      ULocale[] locales = ULocale.getAvailableLocales();
244      return CurrencyPluralInfo.getInstance(locales[seed % locales.length]);
245
246    } else if (type == CurrencyUsage.class) {
247      if (seed == 0) return null;
248      CurrencyUsage[] values = CurrencyUsage.values();
249      return values[seed % values.length];
250
251    } else if (type == GroupingMode.class) {
252      if (seed == 0) return null;
253      GroupingMode[] values = GroupingMode.values();
254      return values[seed % values.length];
255
256    } else if (type == FormatWidth.class) {
257      if (seed == 0) return null;
258      FormatWidth[] values = FormatWidth.values();
259      return values[seed % values.length];
260
261    } else if (type == Map.class) {
262      // Map<String,Map<String,String>> for compactCustomData property
263      if (seed == 0) return null;
264      Map<String, Map<String, String>> outer = new HashMap<String, Map<String, String>>();
265      Map<String, String> inner = new HashMap<String, String>();
266      inner.put("one", "0 thousand");
267      StringBuilder magnitudeKey = new StringBuilder();
268      magnitudeKey.append("1000");
269      for (int i = 0; i < seed % 9; i++) {
270        magnitudeKey.append("0");
271      }
272      outer.put(magnitudeKey.toString(), inner);
273      return outer;
274
275    } else if (type == MathContext.class) {
276      if (seed == 0) return null;
277      RoundingMode[] modes = RoundingMode.values();
278      return new MathContext(seed, modes[seed % modes.length]);
279
280    } else if (type == MeasureUnit.class) {
281      if (seed == 0) return null;
282      Object[] units = MeasureUnit.getAvailable().toArray();
283      return units[seed % units.length];
284
285    } else if (type == PadPosition.class) {
286      if (seed == 0) return null;
287      PadPosition[] values = PadPosition.values();
288      return values[seed % values.length];
289
290    } else if (type == ParseMode.class) {
291      if (seed == 0) return null;
292      ParseMode[] values = ParseMode.values();
293      return values[seed % values.length];
294
295    } else if (type == PluralRules.class) {
296      if (seed == 0) return null;
297      ULocale[] locales = PluralRules.getAvailableULocales();
298      return PluralRules.forLocale(locales[seed % locales.length]);
299
300    } else if (type == RoundingMode.class) {
301      if (seed == 0) return null;
302      RoundingMode[] values = RoundingMode.values();
303      return values[seed % values.length];
304
305    } else {
306      fail("Don't know how to handle type " + type + ". Please add it to getSampleValueForType().");
307      return null;
308    }
309  }
310
311  @Test
312  public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
313    DecimalFormatProperties props0 = new DecimalFormatProperties();
314
315    // Write values to some of the fields
316    PatternStringParser.parseToExistingProperties("A-**####,#00.00#b¤", props0);
317
318    // Write to byte stream
319    ByteArrayOutputStream baos = new ByteArrayOutputStream();
320    ObjectOutputStream oos = new ObjectOutputStream(baos);
321    oos.writeObject(props0);
322    oos.flush();
323    baos.close();
324    byte[] bytes = baos.toByteArray();
325
326    // Read from byte stream
327    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
328    Object obj = ois.readObject();
329    ois.close();
330    DecimalFormatProperties props1 = (DecimalFormatProperties) obj;
331
332    // Test equality
333    assertEquals("Did not round-trip through serialization", props0, props1);
334  }
335
336  /** Handler for serialization compatibility test suite. */
337  public static class PropertiesHandler implements SerializableTestUtility.Handler {
338
339    @Override
340    public Object[] getTestObjects() {
341      return new Object[] {
342        new DecimalFormatProperties(),
343        PatternStringParser.parseToProperties("x#,##0.00%"),
344        new DecimalFormatProperties().setCompactStyle(CompactStyle.LONG).setMinimumExponentDigits(2)
345      };
346    }
347
348    @Override
349    public boolean hasSameBehavior(Object a, Object b) {
350      return a.equals(b);
351    }
352  }
353
354  /** Handler for the ICU 59 class named "Properties" before it was renamed to "DecimalFormatProperties". */
355  public static class ICU59PropertiesHandler implements SerializableTestUtility.Handler {
356
357    @Override
358    public Object[] getTestObjects() {
359      return new Object[] {
360        new android.icu.impl.number.Properties()
361      };
362    }
363
364    @Override
365    public boolean hasSameBehavior(Object a, Object b) {
366      return true;
367    }
368  }
369}
370