public CollectionMerger build() { return new CollectionMerger(this); } }
public Map<?, ?> merge(Map<?, ?> map1, Map<?, ?> map2) { checkNotNull(map1, "map1"); checkNotNull(map2, "map2"); return (Map<?,?>) mergeImpl(Maybe.of(map1), Maybe.of(map2), depth, new Visited()); }
private Iterable<?> mergeIterablesImpl(Iterable<?> val1, Iterable<?> val2, int depthRemaining, Visited visited) { if (depthRemaining < 1) { return val1; } if (val1 instanceof Set) { return mergeSetsImpl((Set<?>)val1, MutableSet.copyOf(val2), depthRemaining, visited); } else { return mergeListsImpl(MutableList.copyOf(val1), val2, depthRemaining, visited); } }
Map<?,?> map1 = (Map<?, ?>) val1.get(); if (val2.get() instanceof Map) { return mergeMapsImpl(map1, (Map<?, ?>) val2.get(), depthRemaining, visited); } else { return mergeIterablesImpl(iter1, (Iterable<?>) val2.get(), depthRemaining, visited); } else {
@Test public void testMergeMapsSimple() { Map<?, ?> val1 = ImmutableMap.of("key1", "val1a", "key2", "val2a"); Map<?, ?> val2 = ImmutableMap.of("key1", "val1b", "key3", "val3b"); Map<?, ?> resultDeep = CollectionMerger.builder().build().merge(val1, val2); Map<?, ?> resultShallow = CollectionMerger.builder().deep(false).build().merge(val1, val2); assertEquals(resultDeep, ImmutableMap.of("key1", "val1a", "key2", "val2a", "key3", "val3b")); assertEquals(resultShallow, ImmutableMap.of("key1", "val1a", "key2", "val2a", "key3", "val3b")); }
private Map<?, ?> mergeMapsImpl(Map<?, ?> val1, Map<?, ?> val2, int depthRemaining, Visited visited) { if (depthRemaining < 1) { return val1; } MutableMap<Object, Object> result = MutableMap.of(); for (Object key : Sets.union(val1.keySet(), val2.keySet())) { Maybe<?> sub1 = val1.containsKey(key) ? Maybe.of(val1.get(key)) : Maybe.absent(); Maybe<?> sub2 = val2.containsKey(key) ? Maybe.of(val2.get(key)) : Maybe.absent(); result.put(key, mergeImpl(sub1, sub2, depthRemaining-1, visited)); } return result; }
@Test public void testMergeMapsDefaultsToOverridingSubLists() { Map<?, ?> val1 = ImmutableMap.of("key1", ImmutableList.of("val1a")); Map<?, ?> val2 = ImmutableMap.of("key1", ImmutableList.of("val1b")); Map<?, ?> resultDepth1 = CollectionMerger.builder().depth(1).build().merge(val1, val2); Map<?, ?> resultDepth2 = CollectionMerger.builder().depth(2).build().merge(val1, val2); assertEquals(resultDepth1, ImmutableMap.of("key1", ImmutableList.of("val1a"))); assertEquals(resultDepth2, resultDepth1); }
@Test public void testMergeMapsWithMergingSubListsRespectsTypes() { Map<?, ?> val1 = ImmutableMap.of("key1", ImmutableList.of("val1a")); Map<?, ?> val2 = ImmutableMap.of("key1", ImmutableList.of("val1b")); Map<?, ?> result = CollectionMerger.builder().mergeNestedLists(true).build().merge(val1, val2); assertEquals(result, ImmutableMap.of("key1", ImmutableList.of("val1a", "val1b"))); }
@Test public void testMergeMapsWithMergingSubSetsRespectsTypes() { Map<?, ?> val1 = ImmutableMap.of("key1", ImmutableSet.of("val1a")); Map<?, ?> val2 = ImmutableMap.of("key1", ImmutableSet.of("val1b")); Map<?, ?> result = CollectionMerger.builder().mergeNestedLists(true).build().merge(val1, val2); assertEquals(result, ImmutableMap.of("key1", ImmutableSet.of("val1a", "val1b"))); }
@Test public void testMergeMapsWithNullOverridesOther() { // Expect "key2:" to have a null value (rather than just empty). String yaml1 = Joiner.on("\n").join( "key1: val1", "key2:"); String yaml2 = Joiner.on("\n").join( "key1: override-ignored", "key1b: val1b", "key2:", " key2.1b: val2.1b"); Map<?, ?> val1 = (Map<?, ?>) Iterables.getOnlyElement(parseYaml(yaml1)); Map<?, ?> val2 = (Map<?, ?>) Iterables.getOnlyElement(parseYaml(yaml2)); Map<?, ?> resultDepth1 = CollectionMerger.builder().depth(1).build().merge(val1, val2); Map<?, ?> resultDepth2 = CollectionMerger.builder().depth(2).build().merge(val1, val2); assertEquals(resultDepth1, Iterables.getOnlyElement(parseYaml(Joiner.on("\n").join( "key1: val1", "key1b: val1b", "key2:")))); assertEquals(resultDepth2, resultDepth1); }
private static <T> Maybe<? extends T> deepMerge(Maybe<? extends T> val1, Maybe<? extends T> val2) { if (val2.isAbsent() || val2.isNull()) { return val1; } else if (val1.isAbsent()) { return val2; } else if (val1.isNull()) { return val1; // an explicit null means an override; don't merge } else if (val1.get() instanceof Map && val2.get() instanceof Map) { @SuppressWarnings({ "unchecked", "rawtypes" }) Maybe<T> result = (Maybe)Maybe.of(CollectionMerger.builder().build().merge((Map<?,?>)val1.get(), (Map<?,?>)val2.get())); return result; } else { // cannot merge; just return val1 return val1; } }
@Test public void testMergeMapsWithEmptyIsMerged() { // Expect "key2:" to have a null value (rather than just empty). String yaml1 = Joiner.on("\n").join( "key1: val1", "key2: {}"); String yaml2 = Joiner.on("\n").join( "key1: override-ignored", "key1b: val1b", "key2:", " key2.1b: val2.1b"); Map<?, ?> val1 = (Map<?, ?>) Iterables.getOnlyElement(parseYaml(yaml1)); Map<?, ?> val2 = (Map<?, ?>) Iterables.getOnlyElement(parseYaml(yaml2)); Map<?, ?> resultDepth1 = CollectionMerger.builder().depth(1).build().merge(val1, val2); Map<?, ?> resultDepth2 = CollectionMerger.builder().depth(2).build().merge(val1, val2); assertEquals(resultDepth1, Iterables.getOnlyElement(parseYaml(Joiner.on("\n").join( "key1: val1", "key1b: val1b", "key2: {}")))); assertEquals(resultDepth2, Iterables.getOnlyElement(parseYaml(Joiner.on("\n").join( "key1: val1", "key1b: val1b", "key2:", " key2.1b: val2.1b")))); }
@Test public void testAvoidInfiniteLoop() { { Map<Object, Object> val1 = MutableMap.<Object, Object>of("key1", "val1a"); val1.put("key2", val1); Map<Object, Object> val2 = MutableMap.<Object, Object>of("key3", "val3a"); try { CollectionMerger.builder().build().merge(val1, val2); Asserts.shouldHaveFailedPreviously(); } catch (IllegalStateException e) { Asserts.expectedFailureContains(e, "Recursive self-reference"); } } { Map<Object, Object> val1 = MutableMap.<Object, Object>of("key1", "val1a"); Map<Object, Object> val2 = MutableMap.<Object, Object>of("key3", "val3a"); val1.put("key4", val2); try { CollectionMerger.builder().build().merge(val1, val2); Asserts.shouldHaveFailedPreviously(); } catch (IllegalStateException e) { Asserts.expectedFailureContains(e, "Recursive self-reference"); } } }
Map<?, ?> val2 = (Map<?, ?>) Iterables.getOnlyElement(parseYaml(yaml2)); Map<?, ?> resultDepth1 = CollectionMerger.builder().depth(1).build().merge(val1, val2); Map<?, ?> resultDepth2 = CollectionMerger.builder().depth(2).build().merge(val1, val2); Map<?, ?> resultDepth3 = CollectionMerger.builder().depth(3).build().merge(val1, val2); Map<?, ?> resultDepth4 = CollectionMerger.builder().depth(4).build().merge(val1, val2); Map<?, ?> resultDepth5 = CollectionMerger.builder().depth(5).build().merge(val1, val2); Map<?, ?> resultShallow = CollectionMerger.builder().deep(false).build().merge(val1, val2); Map<?, ?> resultDeep = CollectionMerger.builder().build().merge(val1, val2);
private static <T> ReferenceWithError<Maybe<? extends T>> deepMerge(Maybe<? extends T> val1, Maybe<? extends T> val2) { if (val2.isAbsent() || val2.isNull()) { return ReferenceWithError.newInstanceWithoutError(val1); } else if (val1.isAbsent()) { return ReferenceWithError.newInstanceWithoutError(val2); } else if (val1.isNull()) { return ReferenceWithError.newInstanceWithoutError(val1); // an explicit null means an override; don't merge } else if (val1.get() instanceof Map && val2.get() instanceof Map) { @SuppressWarnings({ "unchecked", "rawtypes" }) Maybe<T> result = (Maybe)Maybe.of(CollectionMerger.builder().build().merge((Map<?,?>)val1.get(), (Map<?,?>)val2.get())); return ReferenceWithError.newInstanceWithoutError(result); } else { // cannot merge; just return val1 return ReferenceWithError.newInstanceThrowingError(val1, new IllegalArgumentException("Cannot merge '"+val1.get()+"' and '"+val2.get()+"'")); } }
Map<?, ?> val2 = (Map<?, ?>) Iterables.getOnlyElement(parseYaml(yaml2)); Map<?, ?> resultDepth1 = CollectionMerger.builder().mergeNestedLists(true).depth(1).build().merge(val1, val2); Map<?, ?> resultDepth2 = CollectionMerger.builder().mergeNestedLists(true).depth(2).build().merge(val1, val2); Map<?, ?> resultDepth3 = CollectionMerger.builder().mergeNestedLists(true).depth(3).build().merge(val1, val2); Map<?, ?> resultDepth4 = CollectionMerger.builder().mergeNestedLists(true).depth(4).build().merge(val1, val2); Map<?, ?> resultDepth5 = CollectionMerger.builder().mergeNestedLists(true).depth(5).build().merge(val1, val2); Map<?, ?> resultShallow = CollectionMerger.builder().mergeNestedLists(true).deep(false).build().merge(val1, val2); Map<?, ?> resultDeep = CollectionMerger.builder().mergeNestedLists(true).build().merge(val1, val2);