View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Unit tests for {@link org.apache.commons.collections4.keyvalue.MultiKey}.
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; // simulate different hashCode after deserialization in another process
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;  // no effect
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;  // no effect
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         // change key - don't do this!
144         // the hash code of the MultiKey is now broken
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         // serialize
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         // deserialize
193         final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
194         final ObjectInputStream in = new ObjectInputStream(bais);
195         sysKey = (SystemHashCodeSimulatingKey) in.readObject(); // simulate deserialization in another process
196         final Map<?, ?> map2 = (Map<?, ?>) in.readObject();
197         in.close();
198 
199         assertEquals(2, sysKey.hashCode()); // different hashCode now
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         // serialize
210         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
211         final ObjectOutputStream out = new ObjectOutputStream(baos);
212         out.writeObject(mk);
213         out.close();
214 
215         // deserialize
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);  // still not equal
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 }