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