1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.collections4.keyvalue;
18
19 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertNotEquals;
22 import static org.junit.jupiter.api.Assertions.assertNotNull;
23 import static org.junit.jupiter.api.Assertions.assertNotSame;
24 import static org.junit.jupiter.api.Assertions.assertSame;
25 import static org.junit.jupiter.api.Assertions.assertThrows;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27 import static org.junit.jupiter.api.DynamicTest.dynamicTest;
28
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.io.ObjectInputStream;
33 import java.io.ObjectOutputStream;
34 import java.io.Serializable;
35 import java.util.Arrays;
36 import java.util.Collection;
37 import java.util.HashMap;
38 import java.util.Map;
39
40 import org.apache.commons.lang3.StringUtils;
41 import org.junit.jupiter.api.DynamicTest;
42 import org.junit.jupiter.api.Test;
43 import org.junit.jupiter.api.TestFactory;
44
45
46
47
48 public class MultiKeyTest {
49
50 static class DerivedMultiKey<T> extends MultiKey<T> {
51
52 private static final long serialVersionUID = 1928896152249821416L;
53
54 DerivedMultiKey(final T key1, final T key2) {
55 super(key1, key2);
56 }
57
58 public T getFirst() {
59 return getKey(0);
60 }
61
62 public T getSecond() {
63 return getKey(1);
64 }
65
66 }
67
68 static class SystemHashCodeSimulatingKey implements Serializable {
69
70 private static final long serialVersionUID = -1736147315703444603L;
71 private final String name;
72 private int hashCode = 1;
73
74 SystemHashCodeSimulatingKey(final String name) {
75 this.name = name;
76 }
77
78 @Override
79 public boolean equals(final Object obj) {
80 return obj instanceof SystemHashCodeSimulatingKey
81 && name.equals(((SystemHashCodeSimulatingKey) obj).name);
82 }
83
84 @Override
85 public int hashCode() {
86 return hashCode;
87 }
88
89 private Object readResolve() {
90 hashCode=2;
91 return this;
92 }
93 }
94
95 Integer ONE = Integer.valueOf(1);
96
97 Integer TWO = Integer.valueOf(2);
98 Integer THREE = Integer.valueOf(3);
99 Integer FOUR = Integer.valueOf(4);
100 Integer FIVE = Integer.valueOf(5);
101
102 @Test
103 public void testConstructors() throws Exception {
104 MultiKey<Integer> mk;
105 mk = new MultiKey<>(ONE, TWO);
106 assertArrayEquals(new Object[]{ONE, TWO}, mk.getKeys());
107
108 mk = new MultiKey<>(ONE, TWO, THREE);
109 assertArrayEquals(new Object[]{ONE, TWO, THREE}, mk.getKeys());
110
111 mk = new MultiKey<>(ONE, TWO, THREE, FOUR);
112 assertArrayEquals(new Object[]{ONE, TWO, THREE, FOUR}, mk.getKeys());
113
114 mk = new MultiKey<>(ONE, TWO, THREE, FOUR, FIVE);
115 assertArrayEquals(new Object[]{ONE, TWO, THREE, FOUR, FIVE}, mk.getKeys());
116
117 mk = new MultiKey<>(new Integer[] { THREE, FOUR, ONE, TWO }, false);
118 assertArrayEquals(new Object[]{THREE, FOUR, ONE, TWO}, mk.getKeys());
119 }
120
121 @Test
122 public void testConstructorsByArray() throws Exception {
123 MultiKey<Integer> mk;
124 Integer[] keys = { THREE, FOUR, ONE, TWO };
125 mk = new MultiKey<>(keys);
126 assertArrayEquals(new Object[]{THREE, FOUR, ONE, TWO}, mk.getKeys());
127 keys[3] = FIVE;
128 assertArrayEquals(new Object[]{THREE, FOUR, ONE, TWO}, mk.getKeys());
129
130 keys = new Integer[] {};
131 mk = new MultiKey<>(keys);
132 assertArrayEquals(new Object[]{}, mk.getKeys());
133
134 keys = new Integer[] { THREE, FOUR, ONE, TWO };
135 mk = new MultiKey<>(keys, true);
136 assertArrayEquals(new Object[]{THREE, FOUR, ONE, TWO}, mk.getKeys());
137 keys[3] = FIVE;
138 assertArrayEquals(new Object[]{THREE, FOUR, ONE, TWO}, mk.getKeys());
139
140 keys = new Integer[] { THREE, FOUR, ONE, TWO };
141 mk = new MultiKey<>(keys, false);
142 assertArrayEquals(new Object[]{THREE, FOUR, ONE, TWO}, mk.getKeys());
143
144
145 keys[3] = FIVE;
146 assertArrayEquals(new Object[]{THREE, FOUR, ONE, FIVE}, mk.getKeys());
147 }
148
149 @TestFactory
150 public Collection<DynamicTest> testConstructorsByArrayNull() {
151 final Integer[] keys = null;
152 return Arrays.asList(
153 dynamicTest("Integer[] null", () -> {
154 assertThrows(NullPointerException.class, () -> new MultiKey<>(keys));
155 }),
156 dynamicTest("Integer[] null + makeClone true", () -> {
157 assertThrows(NullPointerException.class, () -> new MultiKey<>(keys, true));
158 }),
159 dynamicTest("Integer[] null + makeClone false", () -> {
160 assertThrows(NullPointerException.class, () -> new MultiKey<>(keys, false));
161 })
162 );
163 }
164
165 @Test
166 public void testEquals() {
167 final MultiKey<Integer> mk1 = new MultiKey<>(ONE, TWO);
168 final MultiKey<Integer> mk2 = new MultiKey<>(ONE, TWO);
169 final MultiKey<Object> mk3 = new MultiKey<>(ONE, "TWO");
170
171 assertEquals(mk1, mk1);
172 assertEquals(mk1, mk2);
173 assertNotEquals(mk1, mk3);
174 assertNotEquals(StringUtils.EMPTY, mk1);
175 assertNotEquals(null, mk1);
176 }
177
178 @Test
179 public void testEqualsAfterSerialization() throws IOException, ClassNotFoundException {
180 SystemHashCodeSimulatingKey sysKey = new SystemHashCodeSimulatingKey("test");
181 final MultiKey<?> mk = new MultiKey<Object>(ONE, sysKey);
182 final Map<MultiKey<?>, Integer> map = new HashMap<>();
183 map.put(mk, TWO);
184
185
186 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
187 final ObjectOutputStream out = new ObjectOutputStream(baos);
188 out.writeObject(sysKey);
189 out.writeObject(map);
190 out.close();
191
192
193 final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
194 final ObjectInputStream in = new ObjectInputStream(bais);
195 sysKey = (SystemHashCodeSimulatingKey) in.readObject();
196 final Map<?, ?> map2 = (Map<?, ?>) in.readObject();
197 in.close();
198
199 assertEquals(2, sysKey.hashCode());
200
201 final MultiKey<?> mk2 = new MultiKey<Object>(ONE, sysKey);
202 assertEquals(TWO, map2.get(mk2));
203 }
204
205 @Test
206 public void testEqualsAfterSerializationOfDerivedClass() throws IOException, ClassNotFoundException {
207 final DerivedMultiKey<?> mk = new DerivedMultiKey<>("A", "B");
208
209
210 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
211 final ObjectOutputStream out = new ObjectOutputStream(baos);
212 out.writeObject(mk);
213 out.close();
214
215
216 final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
217 final ObjectInputStream in = new ObjectInputStream(bais);
218 final DerivedMultiKey<?> mk2 = (DerivedMultiKey<?>) in.readObject();
219 in.close();
220
221 assertEquals(mk.hashCode(), mk2.hashCode());
222 }
223
224 @TestFactory
225 public Collection<DynamicTest> testGetIndexed() {
226 final MultiKey<Integer> mk = new MultiKey<>(ONE, TWO);
227 return Arrays.asList(
228 dynamicTest("0", () -> {
229 assertSame(ONE, mk.getKey(0));
230 }),
231 dynamicTest("1", () -> {
232 assertSame(TWO, mk.getKey(1));
233 }),
234 dynamicTest("-1", () -> {
235 assertThrows(IndexOutOfBoundsException.class, () -> mk.getKey(-1));
236 }),
237 dynamicTest("2", () -> {
238 assertThrows(IndexOutOfBoundsException.class, () -> mk.getKey(2));
239 })
240 );
241 }
242
243 @Test
244 public void testGetKeysArrayConstructorCloned() {
245 final Integer[] keys = { ONE, TWO };
246 final MultiKey<Integer> mk = new MultiKey<>(keys, true);
247 final Object[] array = mk.getKeys();
248 assertNotSame(array, keys);
249 assertArrayEquals(array, keys);
250 assertSame(ONE, array[0]);
251 assertSame(TWO, array[1]);
252 assertEquals(2, array.length);
253 }
254
255 @Test
256 public void testGetKeysArrayConstructorNonCloned() {
257 final Integer[] keys = { ONE, TWO };
258 final MultiKey<Integer> mk = new MultiKey<>(keys, false);
259 final Object[] array = mk.getKeys();
260 assertNotSame(array, keys);
261 assertArrayEquals(array, keys);
262 assertSame(ONE, array[0]);
263 assertSame(TWO, array[1]);
264 assertEquals(2, array.length);
265 }
266
267 @Test
268 public void testGetKeysSimpleConstructor() {
269 final MultiKey<Integer> mk = new MultiKey<>(ONE, TWO);
270 final Object[] array = mk.getKeys();
271 assertSame(ONE, array[0]);
272 assertSame(TWO, array[1]);
273 assertEquals(2, array.length);
274 }
275
276 @Test
277 public void testHashCode() {
278 final MultiKey<Integer> mk1 = new MultiKey<>(ONE, TWO);
279 final MultiKey<Integer> mk2 = new MultiKey<>(ONE, TWO);
280 final MultiKey<Object> mk3 = new MultiKey<>(ONE, "TWO");
281
282 assertEquals(mk1.hashCode(), mk1.hashCode());
283 assertEquals(mk1.hashCode(), mk2.hashCode());
284 assertTrue(mk1.hashCode() != mk3.hashCode());
285
286 final int total = 0 ^ ONE.hashCode() ^ TWO.hashCode();
287 assertEquals(total, mk1.hashCode());
288 }
289
290 @Test
291 public void testSize() {
292 assertEquals(2, new MultiKey<>(ONE, TWO).size());
293 assertEquals(2, new MultiKey<>(null, null).size());
294 assertEquals(3, new MultiKey<>(ONE, TWO, THREE).size());
295 assertEquals(3, new MultiKey<>(null, null, null).size());
296 assertEquals(4, new MultiKey<>(ONE, TWO, THREE, FOUR).size());
297 assertEquals(4, new MultiKey<>(null, null, null, null).size());
298 assertEquals(5, new MultiKey<>(ONE, TWO, THREE, FOUR, FIVE).size());
299 assertEquals(5, new MultiKey<>(null, null, null, null, null).size());
300
301 assertEquals(0, new MultiKey<>(new Object[] {}).size());
302 assertEquals(1, new MultiKey<>(new Integer[] { ONE }).size());
303 assertEquals(2, new MultiKey<>(new Integer[] { ONE, TWO }).size());
304 assertEquals(7, new MultiKey<>(new Integer[] { ONE, TWO, ONE, TWO, ONE, TWO, ONE }).size());
305 }
306
307 @Test
308 public void testTwoArgCtor() {
309 final MultiKeyTest key1 = new MultiKeyTest();
310 final MultiKeyTest key2 = new MultiKeyTest();
311 final MultiKeyTest[] keys = new MultiKey<>(key1, key2).getKeys();
312 assertNotNull(keys);
313 }
314
315 }