View Javadoc
1   /*
2      Copyright 2011 Atlassian
3   
4      Licensed under the Apache License, Version 2.0 (the "License");
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7   
8          http://www.apache.org/licenses/LICENSE-2.0
9   
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15   */
16  package io.atlassian.fugue.extras;
17  
18  import com.google.common.collect.ImmutableMap;
19  import com.google.common.collect.Maps;
20  import io.atlassian.fugue.Functions;
21  import io.atlassian.fugue.Option;
22  import org.junit.Test;
23  
24  import javax.annotation.Nullable;
25  import java.util.Arrays;
26  import java.util.Map;
27  import java.util.function.BiFunction;
28  import java.util.function.Function;
29  
30  import static com.google.common.collect.Maps.immutableEntry;
31  import static org.hamcrest.Matchers.equalTo;
32  import static org.junit.Assert.assertThat;
33  
34  public class ImmutableMapsTest {
35  
36    @Test public void mapEntry() {
37      BiFunction<String, Integer, Map.Entry<String, Integer>> mapEntryFunction = ImmutableMaps.mapEntry();
38      Map.Entry<String, Integer> entry = mapEntryFunction.apply("abc", 1);
39      assertThat(entry.getKey(), equalTo("abc"));
40      assertThat(entry.getValue(), equalTo(1));
41    }
42  
43    @Test public void toMap() {
44      Iterable<Map.Entry<String, Integer>> source = list(immutableEntry("a", 1), immutableEntry("b", 2), immutableEntry("c", 3));
45  
46      assertThat(ImmutableMaps.toMap(source), equalTo(ImmutableMap.of("a", 1, "b", 2, "c", 3)));
47    }
48  
49    @Test public void toMapContainingNull() {
50      assertThat(ImmutableMaps.toMap(list(immutableEntry("a", 1), null, immutableEntry("c", 3))), equalTo(ImmutableMap.of("a", 1, "c", 3)));
51    }
52  
53    @Test public void toMapWithNullKey() {
54      assertThat(ImmutableMaps.toMap(list(immutableEntry("a", 1), Maps.<String, Integer> immutableEntry(null, 2), immutableEntry("c", 3))),
55        equalTo(ImmutableMap.of("a", 1, "c", 3)));
56    }
57  
58    @Test public void toMapWithNullValue() {
59      assertThat(ImmutableMaps.toMap(list(immutableEntry("a", 1), Maps.<String, Integer> immutableEntry("b", null), immutableEntry("c", 3))),
60        equalTo(ImmutableMap.of("a", 1, "c", 3)));
61    }
62  
63    @Test public void toMapFunction() {
64      assertThat(ImmutableMaps.toMap(list(1, 2, 3), Functions.<Integer> identity(), Object::toString), equalTo(ImmutableMap.of(1, "1", 2, "2", 3, "3")));
65    }
66  
67    @Test public void toMapFunctionVariance() {
68      Function<GrandParent, Child> f = input -> new Child(input.num());
69      assertThat(ImmutableMaps.<Parent, Parent, Parent> toMap(list(new Parent(1), new Parent(2), new Parent(3)), f, f),
70        equalTo(ImmutableMap.<Parent, Parent> of(new Child(1), new Child(1), new Child(2), new Child(2), new Child(3), new Child(3))));
71    }
72  
73    // Allow override instead of throwing exceptions?
74    @Test(expected = IllegalArgumentException.class) public void toMapWithDuplicateKey() {
75      ImmutableMaps.toMap(list(immutableEntry("a", 1), immutableEntry("b", 2), immutableEntry("b", 3)));
76    }
77  
78    @Test public void transformIterablesToMap() {
79      assertThat(ImmutableMaps.toMap(list(1, 2, 3), new Function<Integer, String>() {
80        @Override public String apply(Integer input) {
81          return "-" + input;
82        }
83      }, new Function<Integer, Integer>() {
84        @Override public Integer apply(Integer input) {
85          return input * -1;
86        }
87      }), equalTo(ImmutableMap.of("-1", -1, "-2", -2, "-3", -3)));
88    }
89  
90    @Test public void transformIterablesToMapContainingNull() {
91      assertThat(ImmutableMaps.toMap(list(1, null, 3), new Function<Integer, String>() {
92        @Override public String apply(Integer input) {
93          return "-" + input;
94        }
95      }, new Function<Integer, Integer>() {
96        @Override public Integer apply(Integer input) {
97          return input == null ? 0 : input * -1;
98        }
99      }), equalTo(ImmutableMap.of("-1", -1, "-null", 0, "-3", -3)));
100   }
101 
102   @Test public void transformIterablesToMapGeneratingNullKey() {
103     assertThat(ImmutableMaps.toMap(list(1, null, 3), new Function<Integer, String>() {
104       @Override public String apply(Integer input) {
105         return input == null ? null : "-" + input;
106       }
107     }, new Function<Integer, Integer>() {
108       @Override public Integer apply(Integer input) {
109         return input == null ? 0 : input * -1;
110       }
111     }), equalTo(ImmutableMap.of("-1", -1, "-3", -3)));
112   }
113 
114   @Test public void transformIterablesToMapGeneratingNullValue() {
115     assertThat(ImmutableMaps.toMap(list(1, null, 3), new Function<Integer, String>() {
116       @Override public String apply(Integer input) {
117         return "-" + input;
118       }
119     }, new Function<Integer, Integer>() {
120       @Override public Integer apply(Integer input) {
121         return input == null ? null : input * -1;
122       }
123     }), equalTo(ImmutableMap.of("-1", -1, "-3", -3)));
124   }
125 
126   @Test(expected = IllegalArgumentException.class) public void transformIterablesToMapGeneratingDuplicateKey() {
127     ImmutableMaps.toMap(list(1, 2, 3), new Function<Integer, String>() {
128       @Override public String apply(Integer input) {
129         return String.valueOf(input % 2);
130       }
131     }, new Function<Integer, Integer>() {
132       @Override public Integer apply(Integer input) {
133         return input * -1;
134       }
135     });
136   }
137 
138   @Test public void mapBy() {
139     assertThat(ImmutableMaps.mapBy(list(1, 2, 3), new Function<Integer, String>() {
140       @Override public String apply(Integer input) {
141         return "+" + input;
142       }
143     }), equalTo(ImmutableMap.of("+1", 1, "+2", 2, "+3", 3)));
144   }
145 
146   @Test public void mapByContravariantKeyFunction() {
147     assertThat(ImmutableMaps.mapBy(list(new Child(1), new Child(2), new Child(3)), new Function<Parent, String>() {
148       @Override public String apply(Parent input) {
149         return "+" + input.num();
150       }
151     }), equalTo(ImmutableMap.of("+1", new Child(1), "+2", new Child(2), "+3", new Child(3))));
152   }
153 
154   @Test public void mapByContainingNull() {
155     assertThat(ImmutableMaps.mapBy(list(1, null, 3), new Function<Integer, String>() {
156       @Override public String apply(Integer input) {
157         return "+" + input;
158       }
159     }), equalTo(ImmutableMap.of("+1", 1, "+3", 3)));
160   }
161 
162   @Test(expected = IllegalArgumentException.class) public void mapByDuplicateKey() {
163     ImmutableMaps.mapBy(list(1, 2, 3), new Function<Integer, String>() {
164       @Override public String apply(Integer input) {
165         return "+" + (input % 2);
166       }
167     });
168   }
169 
170   @Test public void mapTo() {
171     assertThat(ImmutableMaps.mapTo(list(1, 2, 3), new Function<Integer, String>() {
172       @Override public String apply(Integer input) {
173         return "+" + input;
174       }
175     }), equalTo(ImmutableMap.of(1, "+1", 2, "+2", 3, "+3")));
176   }
177 
178   @Test public void mapToContainingNull() {
179     assertThat(ImmutableMaps.mapTo(list(1, null, 3), new Function<Integer, String>() {
180       @Override public String apply(Integer input) {
181         return "+" + input;
182       }
183     }), equalTo(ImmutableMap.of(1, "+1", 3, "+3")));
184   }
185 
186   @Test(expected = IllegalArgumentException.class) public void mapToContainingDuplicates() {
187     ImmutableMaps.mapTo(list(1, 2, 1), new Function<Integer, String>() {
188       @Override public String apply(Integer input) {
189         return "+" + input;
190       }
191     });
192   }
193 
194   @Test public void mapToVariance() {
195     assertThat(ImmutableMaps.<Integer, Parent> mapTo(list(1, 2, 3), new Function<Number, Child>() {
196       @Override public Child apply(Number input) {
197         return new Child((Integer) input);
198       }
199     }), equalTo(ImmutableMap.of(1, new Parent(1), 2, new Parent(2), 3, new Parent(3))));
200   }
201 
202   @Test public void transformEntries() {
203     assertThat(
204       ImmutableMaps.transform(ImmutableMap.of("a", 1, "b", 2, "c", 3), new Function<Map.Entry<String, Integer>, Map.Entry<Integer, String>>() {
205         @Override public Map.Entry<Integer, String> apply(@Nullable Map.Entry<String, Integer> input) {
206           return immutableEntry(input.getValue() * 2, input.getKey() + input.getKey());
207         }
208       }), equalTo(ImmutableMap.of(2, "aa", 4, "bb", 6, "cc")));
209   }
210 
211   @Test public void transformEntriesContainingNullKey() {
212     Map<String, Integer> source = Maps.newHashMap();
213     source.put("a", 1);
214     source.put(null, 2);
215     source.put("c", 3);
216 
217     assertThat(ImmutableMaps.transform(source, new Function<Map.Entry<String, Integer>, Map.Entry<Integer, String>>() {
218       @Override public Map.Entry<Integer, String> apply(@Nullable Map.Entry<String, Integer> input) {
219         return immutableEntry(input.getValue() * 2, input.getKey() + input.getKey());
220       }
221     }), equalTo(ImmutableMap.of(2, "aa", 4, "nullnull", 6, "cc")));
222   }
223 
224   @Test public void transformEntriesContainingNullValue() {
225     Map<Integer, String> source = Maps.newHashMap();
226     source.put(1, "a");
227     source.put(2, null);
228     source.put(3, "c");
229 
230     assertThat(ImmutableMaps.transform(source, new Function<Map.Entry<Integer, String>, Map.Entry<String, Integer>>() {
231       @Override public Map.Entry<String, Integer> apply(@Nullable Map.Entry<Integer, String> input) {
232         return immutableEntry(input.getValue() + input.getValue(), input.getKey() * 2);
233       }
234     }), equalTo(ImmutableMap.of("aa", 2, "nullnull", 4, "cc", 6)));
235   }
236 
237   @Test public void transformEntriesReturningNull() {
238     assertThat(
239       ImmutableMaps.transform(ImmutableMap.of(1, "a", 2, "b", 3, "c"), new Function<Map.Entry<Integer, String>, Map.Entry<String, Integer>>() {
240         @Override public Map.Entry<String, Integer> apply(@Nullable Map.Entry<Integer, String> input) {
241           return input.getKey() % 2 == 0 ? null : immutableEntry(input.getValue() + input.getValue(), input.getKey() * 2);
242         }
243       }), equalTo(ImmutableMap.of("aa", 2, "cc", 6)));
244   }
245 
246   @Test public void transformEntriesReturningNullKey() {
247     assertThat(
248       ImmutableMaps.transform(ImmutableMap.of(1, "a", 2, "b", 3, "c"), new Function<Map.Entry<Integer, String>, Map.Entry<String, Integer>>() {
249         @Override public Map.Entry<String, Integer> apply(@Nullable Map.Entry<Integer, String> input) {
250           return immutableEntry(input.getKey() % 2 == 0 ? null : input.getValue() + input.getValue(), input.getKey() * 2);
251         }
252       }), equalTo(ImmutableMap.of("aa", 2, "cc", 6)));
253   }
254 
255   @Test public void transformEntriesReturningNullValue() {
256     assertThat(
257       ImmutableMaps.transform(ImmutableMap.of(1, "a", 2, "b", 3, "c"), new Function<Map.Entry<Integer, String>, Map.Entry<String, Integer>>() {
258         @Override public Map.Entry<String, Integer> apply(@Nullable Map.Entry<Integer, String> input) {
259           return immutableEntry(input.getValue() + input.getValue(), input.getKey() % 2 == 0 ? null : input.getKey() * 2);
260         }
261       }), equalTo(ImmutableMap.of("aa", 2, "cc", 6)));
262   }
263 
264   @Test(expected = IllegalArgumentException.class) public void transformEntriesReturningDuplicateKey() {
265     ImmutableMaps.transform(ImmutableMap.of("a", 1, "b", 2, "c", 3), new Function<Map.Entry<String, Integer>, Map.Entry<Integer, String>>() {
266       @Override public Map.Entry<Integer, String> apply(@Nullable Map.Entry<String, Integer> input) {
267         return immutableEntry(input.getValue() % 2, input.getKey() + input.getKey());
268       }
269     });
270   }
271 
272   @Test public void transformKeysAndValues() {
273     assertThat(ImmutableMaps.transform(ImmutableMap.of("a", 1, "bb", 2, "ccc", 3), new Function<String, Integer>() {
274       @Override public Integer apply(@Nullable String input) {
275         return input == null ? 0 : input.length();
276       }
277     }, new Function<Integer, Boolean>() {
278       @Override public Boolean apply(@Nullable Integer input) {
279         return input != null && input % 2 != 0;
280       }
281     }), equalTo(ImmutableMap.of(1, true, 2, false, 3, true)));
282   }
283 
284   @Test public void transformKeysAndValuesContainingNullKey() {
285     Map<String, Integer> source = Maps.newHashMap();
286     source.put("a", 1);
287     source.put(null, 2);
288     source.put("ccc", 3);
289 
290     assertThat(ImmutableMaps.transform(source, new Function<String, Integer>() {
291       @Override public Integer apply(@Nullable String input) {
292         return input == null ? 0 : input.length();
293       }
294     }, new Function<Integer, Boolean>() {
295       @Override public Boolean apply(@Nullable Integer input) {
296         return input != null && input % 2 != 0;
297       }
298     }), equalTo(ImmutableMap.of(1, true, 0, false, 3, true)));
299   }
300 
301   @Test public void transformKeysAndValuesContainingNullValue() {
302     Map<Integer, String> source = Maps.newHashMap();
303     source.put(1, "a");
304     source.put(2, null);
305     source.put(3, "ccc");
306 
307     assertThat(ImmutableMaps.transform(source, new Function<Integer, String>() {
308       @Override public String apply(@Nullable Integer input) {
309         return "(" + input + ")";
310       }
311     }, new Function<String, Integer>() {
312       @Override public Integer apply(@Nullable String input) {
313         return input == null ? 0 : input.length();
314       }
315     }), equalTo(ImmutableMap.of("(1)", 1, "(2)", 0, "(3)", 3)));
316   }
317 
318   @Test public void transformKeysAndValuesReturningNullKey() {
319     Map<String, Integer> source = Maps.newHashMap();
320     source.put("a", 1);
321     source.put(null, 2);
322     source.put("ccc", 3);
323 
324     assertThat(ImmutableMaps.transform(source, new Function<String, Integer>() {
325       @Override public Integer apply(@Nullable String input) {
326         return input == null ? null : input.length();
327       }
328     }, new Function<Integer, Boolean>() {
329       @Override public Boolean apply(@Nullable Integer input) {
330         return input != null && input % 2 != 0;
331       }
332     }), equalTo(ImmutableMap.of(1, true, 3, true)));
333   }
334 
335   @Test public void transformKeysAndValuesReturningNullValue() {
336     Map<Integer, String> source = Maps.newHashMap();
337     source.put(1, "a");
338     source.put(2, null);
339     source.put(3, "ccc");
340 
341     assertThat(ImmutableMaps.transform(source, new Function<Integer, String>() {
342       @Override public String apply(@Nullable Integer input) {
343         return "(" + input + ")";
344       }
345     }, new Function<String, Integer>() {
346       @Override public Integer apply(@Nullable String input) {
347         return input == null ? null : input.length();
348       }
349     }), equalTo(ImmutableMap.of("(1)", 1, "(3)", 3)));
350   }
351 
352   @Test(expected = IllegalArgumentException.class) public void transformKeysAndValuesReturningDuplicateKey() {
353     ImmutableMaps.transform(ImmutableMap.of(1, "a", 2, "b", 3, "c"), new Function<Integer, Boolean>() {
354       @Override public Boolean apply(@Nullable Integer input) {
355         return input != null && input % 2 != 0;
356       }
357     }, new Function<String, Integer>() {
358       @Override public Integer apply(@Nullable String input) {
359         return input == null ? 0 : input.length();
360       }
361     });
362   }
363 
364   @Test public void transformKey() {
365     assertThat(ImmutableMaps.transformKey(ImmutableMap.of("a", 10, "bb", 20, "ccc", 30), new Function<String, Integer>() {
366       @Override public Integer apply(@Nullable String input) {
367         return input == null ? 0 : input.length();
368       }
369     }), equalTo(ImmutableMap.of(1, 10, 2, 20, 3, 30)));
370   }
371 
372   @Test public void transformKeyContainingNullKey() {
373     Map<String, Integer> source = Maps.newHashMap();
374     source.put("a", 10);
375     source.put(null, 20);
376     source.put("ccc", 30);
377 
378     assertThat(ImmutableMaps.transformKey(source, new Function<String, Integer>() {
379       @Override public Integer apply(@Nullable String input) {
380         return input == null ? 0 : input.length();
381       }
382     }), equalTo(ImmutableMap.of(1, 10, 0, 20, 3, 30)));
383   }
384 
385   @Test public void transformKeyContainingNullValue() {
386     Map<Integer, String> source = Maps.newHashMap();
387     source.put(1, "a");
388     source.put(2, null);
389     source.put(3, "ccc");
390 
391     assertThat(ImmutableMaps.transformKey(source, new Function<Integer, String>() {
392       @Override public String apply(@Nullable Integer input) {
393         return "(" + input + ")";
394       }
395     }), equalTo(ImmutableMap.of("(1)", "a", "(3)", "ccc")));
396   }
397 
398   @Test public void transformKeyReturningNullKey() {
399     Map<String, Integer> source = Maps.newHashMap();
400     source.put("a", 11);
401     source.put(null, 12);
402     source.put("ccc", 13);
403 
404     assertThat(ImmutableMaps.transformKey(source, new Function<String, Integer>() {
405       @Override public Integer apply(@Nullable String input) {
406         return input == null ? null : input.length();
407       }
408     }), equalTo(ImmutableMap.of(1, 11, 3, 13)));
409   }
410 
411   @Test(expected = IllegalArgumentException.class) public void transformKeyReturningDuplicateKey() {
412     ImmutableMaps.transformKey(ImmutableMap.of(1, "a", 2, "b", 3, "c"), new Function<Integer, Boolean>() {
413       @Override public Boolean apply(@Nullable Integer input) {
414         return input != null && input % 2 != 0;
415       }
416     });
417   }
418 
419   @Test public void transformValue() {
420     assertThat(ImmutableMaps.transformValue(ImmutableMap.of("a", 1, "bb", 2, "ccc", 3), new Function<Integer, Boolean>() {
421       @Override public Boolean apply(@Nullable Integer input) {
422         return input != null && input % 2 != 0;
423       }
424     }), equalTo(ImmutableMap.of("a", true, "bb", false, "ccc", true)));
425   }
426 
427   @Test public void transformValueContainingNullKey() {
428     Map<String, Integer> source = Maps.newHashMap();
429     source.put("a", 1);
430     source.put(null, 2);
431     source.put("ccc", 3);
432 
433     assertThat(ImmutableMaps.transformValue(source, new Function<Integer, Boolean>() {
434       @Override public Boolean apply(@Nullable Integer input) {
435         return input != null && input % 2 != 0;
436       }
437     }), equalTo(ImmutableMap.of("a", true, "ccc", true)));
438   }
439 
440   @Test public void transformValueContainingNullValue() {
441     Map<Integer, String> source = Maps.newHashMap();
442     source.put(11, "a");
443     source.put(12, null);
444     source.put(13, "ccc");
445 
446     assertThat(ImmutableMaps.transformValue(source, new Function<String, Integer>() {
447       @Override public Integer apply(@Nullable String input) {
448         return input == null ? 0 : input.length();
449       }
450     }), equalTo(ImmutableMap.of(11, 1, 12, 0, 13, 3)));
451   }
452 
453   @Test public void transformValueReturningNullValue() {
454     Map<Integer, String> source = Maps.newHashMap();
455     source.put(11, "a");
456     source.put(12, null);
457     source.put(13, "ccc");
458 
459     assertThat(ImmutableMaps.transformValue(source, new Function<String, Integer>() {
460       @Override public Integer apply(@Nullable String input) {
461         return input == null ? null : input.length();
462       }
463     }), equalTo(ImmutableMap.of(11, 1, 13, 3)));
464   }
465 
466   @Test public void collectEntries() {
467     assertThat(ImmutableMaps.collect(ImmutableMap.of(1, "a", 2, "bb", 3, "ccc"),
468       new Function<Map.Entry<Integer, String>, Option<Map.Entry<String, Integer>>>() {
469         @Override public Option<Map.Entry<String, Integer>> apply(@Nullable Map.Entry<Integer, String> input) {
470           if (input == null || input.getKey() == null || input.getKey() % 2 == 0 || input.getValue() == null) {
471             return Option.none();
472           }
473           return Option.some(immutableEntry(String.valueOf(input.getKey() * 2), input.getValue().length()));
474         }
475       }), equalTo(ImmutableMap.of("2", 1, "6", 3)));
476   }
477 
478   @Test public void collectKeysAndValuesWithEitherReturningNone() {
479     assertThat(ImmutableMaps.collect(ImmutableMap.of(1, "a", 2, "bbb", 3, "cc"), new Function<Integer, Option<String>>() {
480       @Override public Option<String> apply(@Nullable Integer input) {
481         return input != null && input % 2 > 0 ? Option.some(String.valueOf(input * 2)) : Option.none(String.class);
482       }
483     }, new Function<String, Option<Integer>>() {
484       @Override public Option<Integer> apply(@Nullable String input) {
485         return input != null && input.length() % 2 > 0 ? Option.some(input.length()) : Option.none(Integer.class);
486       }
487     }), equalTo(ImmutableMap.of("2", 1)));
488   }
489 
490   @Test public void collectByKey() {
491     assertThat(ImmutableMaps.collectByKey(ImmutableMap.of(1, "a", 2, "bb", 3, "cccc"), new Function<Integer, Option<String>>() {
492       @Override public Option<String> apply(@Nullable Integer input) {
493         return input != null && input % 2 > 0 ? Option.some(String.valueOf(input * 2)) : Option.none(String.class);
494       }
495     }), equalTo(ImmutableMap.of("2", "a", "6", "cccc")));
496   }
497 
498   @Test public void collectByKeyContravariantKeyFunction() {
499     assertThat(ImmutableMaps.collectByKey(ImmutableMap.of(new Child(2), "a", new Child(3), "bb", new Child(6), "cccc"),
500       new Function<Parent, Option<String>>() {
501         @Override public Option<String> apply(@Nullable Parent input) {
502           return input.num() % 2 == 0 ? Option.some(String.valueOf(input.num())) : Option.<String> none();
503         }
504       }), equalTo(ImmutableMap.of("2", "a", "6", "cccc")));
505   }
506 
507   @Test public void collectByValue() {
508     assertThat(ImmutableMaps.collectByValue(ImmutableMap.of(1, "a", 2, "bb", 3, "ccccc"), new Function<String, Option<Integer>>() {
509       @Override public Option<Integer> apply(@Nullable String input) {
510         return input != null && input.length() % 2 > 0 ? Option.some(input.length()) : Option.none(Integer.class);
511       }
512     }), equalTo(ImmutableMap.of(1, 1, 3, 5)));
513   }
514 
515   @Test public void collectByValueContravariantKeyFunction() {
516     assertThat(
517       ImmutableMaps.collectByValue(ImmutableMap.of(2, new Child(2), 3, new Child(3), 6, new Child(6)), new Function<Parent, Option<String>>() {
518         @Override public Option<String> apply(@Nullable Parent input) {
519           return input.num() % 2 == 0 ? Option.some(String.valueOf(input.num())) : Option.<String> none();
520         }
521       }), equalTo(ImmutableMap.of(2, "2", 6, "6")));
522   }
523 
524   static <A> Iterable<A> list(A a) {
525     return Arrays.asList(a);
526   }
527 
528   static <A> Iterable<A> list(A a1, A a2) {
529     return Arrays.asList(a1, a2);
530   }
531 
532   static <A> Iterable<A> list(A a1, A a2, A a3) {
533     return Arrays.asList(a1, a2, a3);
534   }
535 
536   static class GrandParent {
537     int num() {
538       return Integer.MIN_VALUE;
539     }
540 
541     @Override public int hashCode() {
542       return num();
543     }
544 
545     @Override public boolean equals(Object obj) {
546       return num() == ((GrandParent) obj).num();
547     }
548   }
549 
550   static class Parent extends GrandParent {
551     final int num;
552 
553     Parent(int num) {
554       this.num = num;
555     }
556 
557     Parent() {
558       this.num = -1;
559     }
560 
561     int num() {
562       return num;
563     }
564   }
565 
566   static class Child extends Parent {
567     Child(int num) {
568       super(num);
569     }
570   }
571 }