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) 2004-2016, International Business Machines Corporation and         *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9package com.ibm.icu.dev.test;
10
11import java.lang.reflect.Constructor;
12import java.lang.reflect.Method;
13import java.util.Collection;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Map;
18import java.util.Random;
19import java.util.Set;
20import java.util.TreeSet;
21
22import com.ibm.icu.text.UnicodeSet;
23
24/**
25 * To use, override the abstract and the protected methods as necessary.
26 * Tests boilerplate invariants:
27 * <br>a.equals(a)
28 * <br>!a.equals(null)
29 * <br>if a.equals(b) then
30 * <br>(1) a.hashCode() == b.hashCode  // note: the reverse is not necessarily true.
31 * <br>(2) a functions in all aspects as equivalent to b
32 * <br>(3) b.equals(a)
33 * <br>if b = clone(a)
34 * <br>(1) b.equals(a), and the above checks
35 * <br>(2) if mutable(a), then a.clone() != a // note: the reverse is not necessarily true.
36 * @author Davis
37 */
38public abstract class TestBoilerplate<T> extends TestFmwk {
39
40    protected static Random random = new Random(12345);
41
42    protected final void _test() throws Exception {
43        List<T> list = new LinkedList<T>();
44        while (_addTestObject(list)) {
45        }
46        T[] testArray = (T[]) list.toArray();
47        for (int i = 0; i < testArray.length; ++i) {
48            //logln("Testing " + i);
49            T a = testArray[i];
50            int aHash = a.hashCode();
51            if (a.equals(null)) {
52                errln("Equality/Null invariant fails: " + i);
53            }
54            if (!a.equals(a)) {
55                errln("Self-Equality invariant fails: " + i);
56            }
57            T b;
58            if (_canClone(a)) {
59                b = _clone(a);
60                if (b == a) {
61                    if (_isMutable(a)) {
62                        errln("Clone/Mutability invariant fails: " + i);
63                    }
64                } else {
65                    if (!a.equals(b)) {
66                        errln("Clone/Equality invariant fails: " + i);
67                    }
68                }
69                _checkEquals(i, -1, a, aHash, b);
70            }
71            for (int j = i; j < testArray.length; ++j) {
72                b = testArray[j];
73                if (a.equals(b)) _checkEquals(i, j, a, aHash, b);
74            }
75        }
76    }
77
78    private void _checkEquals(int i, int j, T a, int aHash, T b) {
79        int bHash = b.hashCode();
80        if (!b.equals(a)) errln("Equality/Symmetry",i, j);
81        if (aHash != bHash) errln("Equality/Hash",i, j);
82        if (a != b && !_hasSameBehavior(a,b)) {
83            errln("Equality/Equivalence",i, j);
84        }
85    }
86
87    private void errln(String title, int i, int j) {
88        if (j < 0) errln("Clone/" + title + "invariant fails: " + i);
89        else errln(title + "invariant fails: " + i + "," + j);
90    }
91
92    /**
93     * Must be overridden to check whether a and be behave the same
94     */
95    protected abstract boolean _hasSameBehavior(T a, T b);
96
97    /**
98     * This method will be called multiple times until false is returned.
99     * The results should be a mixture of different objects of the same
100     * type: some equal and most not equal.
101     * The subclasser controls how many are produced (recommend about
102     * 100, based on the size of the objects and how costly they are
103     * to run this test on. The running time grows with the square of the
104     * count.
105     * NOTE: this method will only be called if the objects test as equal.
106     */
107    protected abstract boolean _addTestObject(List<T> c);
108    /**
109     * Override if the tested objects are mutable.
110     * <br>Since Java doesn't tell us, we need a function to tell if so.
111     * The default is true, so must be overridden if not.
112     */
113    protected boolean _isMutable(T a) {
114        return true;
115    }
116    /**
117     * Override if the tested objects can be cloned.
118     */
119    protected boolean _canClone(T a) {
120        return true;
121    }
122    /**
123     * Produce a clone of the object. Tries two methods
124     * (a) clone
125     * (b) constructor
126     * Must be overridden if _canClone returns true and
127     * the above methods don't work.
128     * @param a
129     * @return clone
130     */
131    protected T _clone(T a) throws Exception {
132        Class aClass = a.getClass();
133        try {
134            Method cloner = aClass.getMethod("clone", (Class[])null);
135            return (T) cloner.invoke(a,(Object[])null);
136        } catch (NoSuchMethodException e) {
137            Constructor constructor = aClass.getConstructor(new Class[] {aClass});
138            return (T) constructor.newInstance(new Object[]{a});
139        }
140    }
141
142    /* Utilities */
143    public static boolean verifySetsIdentical(AbstractTestLog here, UnicodeSet set1, UnicodeSet set2) {
144        if (set1.equals(set2)) return true;
145        TestFmwk.errln("Sets differ:");
146        TestFmwk.errln("UnicodeMap - HashMap");
147        TestFmwk.errln(new UnicodeSet(set1).removeAll(set2).toPattern(true));
148        TestFmwk.errln("HashMap - UnicodeMap");
149        TestFmwk.errln(new UnicodeSet(set2).removeAll(set1).toPattern(true));
150        return false;
151    }
152
153    public static boolean verifySetsIdentical(AbstractTestLog here, Set values1, Set values2) {
154        if (values1.equals(values2)) return true;
155        Set temp;
156        TestFmwk.errln("Values differ:");
157        TestFmwk.errln("UnicodeMap - HashMap");
158        temp = new TreeSet(values1);
159        temp.removeAll(values2);
160        TestFmwk.errln(show(temp));
161        TestFmwk.errln("HashMap - UnicodeMap");
162        temp = new TreeSet(values2);
163        temp.removeAll(values1);
164        TestFmwk.errln(show(temp));
165        return false;
166    }
167
168    public static String show(Map m) {
169        StringBuilder buffer = new StringBuilder();
170        for (Iterator it = m.keySet().iterator(); it.hasNext();) {
171            Object key = it.next();
172            buffer.append(key + "=>" + m.get(key) + "\r\n");
173        }
174        return buffer.toString();
175    }
176
177    public static <T> UnicodeSet getSet(Map<Integer, T> m, T value) {
178        UnicodeSet result = new UnicodeSet();
179        for (Iterator<Integer> it = m.keySet().iterator(); it.hasNext();) {
180            Integer key = it.next();
181            T val = m.get(key);
182            if (!val.equals(value)) continue;
183            result.add(key.intValue());
184        }
185        return result;
186    }
187
188    public static String show(Collection c) {
189        StringBuilder buffer = new StringBuilder();
190        for (Iterator it = c.iterator(); it.hasNext();) {
191            buffer.append(it.next() + "\r\n");
192        }
193        return buffer.toString();
194    }
195}
196