@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 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")); }
@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"))); }
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);
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);
@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"); } } }
@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> 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()+"'")); } }
public static Builder builder() { return new Builder(); }
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 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); }
public Builder deep(boolean val) { return depth(val ? Integer.MAX_VALUE : 1); } /**
@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 testMergeMapsEmpty() { Map<String, String> val1 = ImmutableMap.of(); Map<String, Object> val2 = ImmutableMap.of(); Map<?, ?> result = CollectionMerger.builder().build().merge(val1, val2); assertEquals(result, ImmutableMap.of()); }