1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 2006-2011, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9
10package com.ibm.icu.tests;
11
12import java.io.ByteArrayInputStream;
13import java.io.ByteArrayOutputStream;
14import java.io.Externalizable;
15import java.io.IOException;
16import java.io.ObjectInputStream;
17import java.io.ObjectOutputStream;
18import java.io.Serializable;
19import java.lang.reflect.Array;
20import java.lang.reflect.InvocationTargetException;
21import java.lang.reflect.Method;
22import java.util.Locale;
23
24import com.ibm.icu.util.TimeZone;
25import com.ibm.icu.util.ULocale;
26
27import junit.framework.TestCase;
28
29/**
30 * Implement boilerplate tests.
31 * Currently there is only one method, testEHCS, which tests equals, hashCode,
32 * clone, and serialization.
33 */
34public abstract class ICUTestCase extends TestCase {
35    private static final Object[] EMPTY_ARGS = {};
36	private static final Class<?>[] EMPTY_CLASSES = {};
37
38    private static final Locale oldLocale = Locale.getDefault();
39    private static final ULocale oldULocale = ULocale.getDefault();
40    private static final java.util.TimeZone oldJTimeZone = java.util.TimeZone.getDefault();
41    private static final TimeZone oldITimeZone = TimeZone.getDefault();
42
43    // TODO: what's the best way to check this?
44    public static final boolean testingWrapper = true;
45
46    protected void setUp() throws Exception {
47        super.setUp();
48        Locale.setDefault(Locale.US);
49        ULocale.setDefault(ULocale.US);
50        java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("PST"));
51        TimeZone.setDefault(TimeZone.getTimeZone("PST"));
52    }
53
54    protected void tearDown() throws Exception {
55        ULocale.setDefault(oldULocale);
56        Locale.setDefault(oldLocale);
57        TimeZone.setDefault(oldITimeZone);
58        java.util.TimeZone.setDefault(oldJTimeZone);
59        super.tearDown();
60    }
61
62    private static final Object test = new Object();
63
64    /**
65     * Assert that two objects are _not_ equal.  Curiously missing from Assert.
66     * @param lhs an object to test, may be null
67     * @param rhs an object to test, may be null
68     */
69    public static void assertNotEqual(Object lhs, Object rhs) {
70        if (lhs == null) {
71            if (rhs == null) fail("null equals null");
72        } else {
73            if (lhs.equals(rhs)) {
74                fail(lhs.toString() + " equals " + rhs);
75            }
76        }
77    }
78
79    public static void assertNotEqual(long lhs, long rhs) {
80        if (lhs == rhs) {
81            fail("values are equal: " + lhs);
82        }
83    }
84
85    /**
86     * Test whether equality, hashCode, clone, and serialization work as expected.
87     * Equals(Object) is assumed to return false (not throw an exception) if passed
88     * null or an object of an incompatible class.
89     * Hashcodes must be equal iff the two objects compare equal.  No attempt is made to
90     * evaluate the quality of the hashcode distribution, so (in particular) degenerate
91     * hashcode implementations will pass this test.
92     * Clone will be tested if the method "clone" is public on the class of obj.
93     * It is assumed to return an object that compares equal to obj.
94     * Serialization will be tested if object implements Serializable or Externalizable.
95     * It is assumed the serialized/deserialized object compares equal to obj.
96     * @param obj the object to test
97     * @param eq an object that should compare equal to, but is not the same as, obj.
98     *     it should be assignable to the class of obj.
99     * @param neq a non-null object that should not compare equal to obj.
100     *     it should be assignable to the class of obj.
101     */
102	public static void testEHCS(Object obj, Object eq, Object neq) {
103        if (obj == null || eq == null || neq == null) {
104            throw new NullPointerException();
105        }
106        Class<? extends Object> cls = obj.getClass();
107        if (!(cls.isAssignableFrom(eq.getClass()) && cls.isAssignableFrom(neq.getClass()))) {
108            throw new IllegalArgumentException("unassignable classes");
109        }
110
111        // reflexive
112        assertEquals(obj, obj);
113
114        // should return false, not throw exception
115        assertNotEqual(obj, test);
116        assertNotEqual(obj, null);
117
118        // commutative
119        assertEquals(obj, eq);
120        assertEquals(eq, obj);
121
122        assertNotEqual(obj, neq);
123        assertNotEqual(neq, obj);
124
125        // equal objects MUST have equal hashes, unequal objects MAY have equal hashes
126        assertEquals(obj.hashCode(), eq.hashCode());
127
128        Object clone = null;
129        try {
130            // look for public clone method and call it if available
131            Method method_clone = cls.getMethod("clone", EMPTY_CLASSES);
132            clone = method_clone.invoke(obj, EMPTY_ARGS);
133            assertNotNull(clone);
134        }
135        catch(NoSuchMethodException e) {
136            // ok
137        }
138        catch(InvocationTargetException e) {
139            // ok
140        }
141        catch(IllegalAccessException e) {
142            // ok
143        }
144
145        if (clone != null) {
146            assertEquals(obj, clone);
147            assertEquals(clone, obj);
148        }
149
150        if (obj instanceof Serializable || obj instanceof Externalizable) {
151            Object ser = null;
152            try {
153                ByteArrayOutputStream bos = new ByteArrayOutputStream();
154                ObjectOutputStream oos = new ObjectOutputStream(bos);
155                oos.writeObject(clone);
156                oos.close();
157
158                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
159                ObjectInputStream ois = new ObjectInputStream(bis);
160                ser = ois.readObject();
161                ois.close();
162            }
163            catch(IOException e) {
164                System.err.println(e.getMessage());
165                throw new RuntimeException(e);
166            }
167            catch(ClassNotFoundException e) {
168                System.err.println(e.getMessage());
169                throw new RuntimeException(e);
170            }
171
172            if (ser != null) {
173                assertEquals(obj, ser);
174                assertEquals(ser, obj);
175                assertEquals(obj.hashCode(), ser.hashCode());
176            }
177        }
178    }
179
180    /**
181     * Fail if the arrays are not equal.  To be equal, the arrays must
182     * be the same length, and each element in the left array must compare
183     * equal to the corresponding element of the right array.
184     * Also fails if one of the objects is not an array.
185     * @param lhs the left array
186     * @param rhs the right array
187     */
188    public static void assertArraysEqual(Object lhs, Object rhs) {
189        Class<? extends Object> lcls = lhs.getClass();
190        Class<? extends Object> rcls = rhs.getClass();
191        if (!(lcls.isArray() && rcls.isArray())) {
192            fail("objects are not arrays");
193        }
194        String result = arraysAreEqual(lhs, rhs);
195        if (result != null) {
196            fail(result);
197        }
198    }
199
200    /**
201     * Fail if the arrays are equal.  Also fails if one or the other
202     * argument is not an array.
203     * @param lhs the left array
204     * @param rhs the right array
205     */
206    public static void assertArraysNotEqual(Object lhs, Object rhs) {
207        Class<? extends Object> lcls = lhs.getClass();
208        Class<? extends Object> rcls = rhs.getClass();
209        if (!(lcls.isArray() && rcls.isArray())) {
210            fail("objects are not arrays");
211        }
212        String result = arraysAreEqual(lhs, rhs);
213        if (result == null) {
214            fail("arrays are equal");
215        }
216    }
217
218    // slow but general
219    private static String arraysAreEqual(Object lhsa, Object rhsa) {
220        int lhsl = Array.getLength(lhsa);
221        int rhsl = Array.getLength(rhsa);
222        if (lhsl != rhsl) {
223            return "length " + lhsl + " != " + rhsl;
224        }
225        boolean lhsaA = lhsa.getClass().getComponentType().isArray();
226        boolean rhsaA = rhsa.getClass().getComponentType().isArray();
227        if (lhsaA != rhsaA) {
228            return (lhsaA ? "" : "non-") + "array != " + (rhsaA ? "" : "non-") + "array";
229        }
230        for (int i = 0; i < lhsl; ++i) {
231            Object lhse = Array.get(lhsa, i);
232            Object rhse = Array.get(rhsa, i);
233            if (lhse == null) {
234                if (rhse != null) {
235                    return "null != " + rhse;
236                }
237            } else {
238                if (lhsaA) {
239                    String result = arraysAreEqual(lhse, rhse);
240                    if (result != null) {
241                        if (result.charAt(0) != '[') {
242                            result = " " + result;
243                        }
244                        return "[" + i + "]" + result;
245                    }
246                } else {
247                    if (!lhse.equals(rhse)) {
248                        return lhse.toString() + " != " + rhse;
249                    }
250                }
251            }
252        }
253        return null;
254    }
255
256    // much more painful and slow than it should be... partly because of the
257    // oddness of clone, partly because arrays don't provide a Method for
258    // 'clone' despite the fact that they implement it and make it public.
259    public static Object cloneComplex(Object obj) {
260        Object result = null;
261        if (obj != null) {
262            Class<? extends Object> cls = obj.getClass();
263            if (cls.isArray()) {
264                int len = Array.getLength(obj);
265                Class<?> typ = cls.getComponentType();
266                result = Array.newInstance(typ, len);
267                boolean prim = typ.isPrimitive();
268                for (int i = 0; i < len; ++i) {
269                    Object elem = Array.get(obj, i);
270                    Array.set(result, i, prim ? elem : cloneComplex(elem));
271                }
272            } else {
273                result = obj; // default
274                try {
275                    Method cloneM = cls.getMethod("clone", EMPTY_CLASSES);
276                    result = cloneM.invoke(obj, EMPTY_ARGS);
277                }
278                catch (NoSuchMethodException e) {
279                }
280                catch (IllegalAccessException e) {
281                }
282                catch (InvocationTargetException e) {
283                }
284            }
285        }
286        return result;
287    }
288}
289