diff --git a/src/main/java/emissary/core/DiffCheckConfiguration.java b/src/main/java/emissary/core/DiffCheckConfiguration.java index bb4c55c947..1d1323d0e3 100644 --- a/src/main/java/emissary/core/DiffCheckConfiguration.java +++ b/src/main/java/emissary/core/DiffCheckConfiguration.java @@ -1,6 +1,7 @@ package emissary.core; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.Set; @@ -12,13 +13,13 @@ public class DiffCheckConfiguration { * Possible configuration options */ public enum DiffCheckOptions { - DATA, TIMESTAMP, INTERNAL_ID, TRANSFORM_HISTORY + DATA, TIMESTAMP, INTERNAL_ID, TRANSFORM_HISTORY, KEY_VALUE_PARAMETER_DIFF, DETAILED_PARAMETER_DIFF } /** * Stateful field of 'enabled' options */ - private final EnumSet enabled; + private final Set enabled; /** * Start building a new configuration @@ -74,6 +75,24 @@ public boolean checkTransformHistory() { return enabled.contains(DiffCheckOptions.TRANSFORM_HISTORY); } + /** + * Check if parameter diff should produce detailed output + * + * @return if performing a detailed parameter diff + */ + public boolean performDetailedParameterDiff() { + return enabled.contains(DiffCheckOptions.DETAILED_PARAMETER_DIFF); + } + + /** + * Check if parameter diff should produce non-matching key/value output + * + * @return if performing a key/value parameter diff + */ + public boolean performKeyValueParameterDiff() { + return enabled.contains(DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF); + } + /** * Accessor for enabled options * @@ -89,7 +108,7 @@ public Set getEnabled() { * @param enabled set of pre-configured options */ private DiffCheckConfiguration(final EnumSet enabled) { - this.enabled = enabled; + this.enabled = Collections.unmodifiableSet(enabled); } /** @@ -120,6 +139,11 @@ public DiffCheckConfiguration build() { public DiffCheckConfiguration explicit(final DiffCheckOptions... options) { reset(); building.addAll(Arrays.asList(options)); + + if (building.contains(DiffCheckOptions.DETAILED_PARAMETER_DIFF) && building.contains(DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF)) { + throw new IllegalArgumentException("Cannot contain DETAILED_PARAMETER_DIFF and KEY_VALUE_PARAMETER_DIFF!"); + } + return build(); } @@ -219,5 +243,47 @@ public DiffCheckBuilder disableTransformHistory() { building.remove(DiffCheckOptions.TRANSFORM_HISTORY); return this; } + + /** + * Enable transform history for diff checking + * + * @return the builder + */ + public DiffCheckBuilder enableKeyValueParameterDiff() { + building.add(DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF); + building.remove(DiffCheckOptions.DETAILED_PARAMETER_DIFF); + return this; + } + + /** + * Disable transform history for diff checking + * + * @return the builder + */ + public DiffCheckBuilder disableKeyValueParameterDiff() { + building.remove(DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF); + return this; + } + + /** + * Enable transform history for diff checking + * + * @return the builder + */ + public DiffCheckBuilder enableDetailedParameterDiff() { + building.add(DiffCheckOptions.DETAILED_PARAMETER_DIFF); + building.remove(DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF); + return this; + } + + /** + * Disable transform history for diff checking + * + * @return the builder + */ + public DiffCheckBuilder disableDetailedParameterDiff() { + building.remove(DiffCheckOptions.DETAILED_PARAMETER_DIFF); + return this; + } } } diff --git a/src/main/java/emissary/core/IBaseDataObjectDiffHelper.java b/src/main/java/emissary/core/IBaseDataObjectDiffHelper.java index d48bdf5b23..607f5072e8 100644 --- a/src/main/java/emissary/core/IBaseDataObjectDiffHelper.java +++ b/src/main/java/emissary/core/IBaseDataObjectDiffHelper.java @@ -12,10 +12,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; public class IBaseDataObjectDiffHelper { @@ -58,8 +63,15 @@ public static void diff(final IBaseDataObject ibdo1, final IBaseDataObject ibdo2 } diff(ibdo1.getFontEncoding(), ibdo2.getFontEncoding(), "fontEncoding", differences); - // TreeMap automatically sorts parameters by key - diff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), "parameters", differences); + + if (options.performDetailedParameterDiff()) { + diff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), "parameters", differences); + } else if (options.performKeyValueParameterDiff()) { + keyValueMapDiff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), "parameters", differences); + } else { + minimalMapDiff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), "parameters", differences); + } + diff(ibdo1.getNumChildren(), ibdo2.getNumChildren(), "numChildren", differences); diff(ibdo1.getNumSiblings(), ibdo2.getNumSiblings(), "numSiblings", differences); diff(ibdo1.getBirthOrder(), ibdo2.getBirthOrder(), "birthOrder", differences); @@ -211,7 +223,7 @@ public static void diff(final boolean boolean1, final boolean boolean2, final St * @param map1 the first map to compare. * @param map2 the second map to compare. * @param identifier an identifier to describe the context of this map comparison. - * @param differences the string list differences are to be added to. + * @param differences the string list of differences to be added to. */ public static void diff(final Map map1, final Map map2, final String identifier, final List differences) { @@ -226,6 +238,60 @@ public static void diff(final Map map1, final Map> parameter1, final Map> parameter2, + final String identifier, final List differences) { + final Set>> p1Entries = new HashSet<>(parameter1.entrySet()); + final Set>> p2Entries = new HashSet<>(parameter2.entrySet()); + final Map> p1 = new HashMap<>(parameter1); + final Map> p2 = new HashMap<>(parameter2); + + for (Entry> p1Entry : p1Entries) { + if (p2Entries.contains(p1Entry)) { + p1.remove(p1Entry.getKey()); + p2.remove(p1Entry.getKey()); + } + } + + if (!p1.isEmpty() || !p2.isEmpty()) { + differences.add(String.format("%s%s: %s : %s", identifier, ARE_NOT_EQUAL + "-Differing Keys/Values", p1, p2)); + } + } + + /** + * This method compares two maps and adds only the keys that differ to the provided string list. + * + * @param parameter1 the first map to compare. + * @param parameter2 the second map to compare. + * @param identifier an identifier to describe the context of this map comparison. + * @param differences the string list of differences to be added to. + */ + public static void minimalMapDiff(final Map> parameter1, final Map> parameter2, + final String identifier, final List differences) { + final Set>> p1Entries = new HashSet<>(parameter1.entrySet()); + final Set>> p2Entries = new HashSet<>(parameter2.entrySet()); + final Set p1Keys = new TreeSet<>(parameter1.keySet()); + final Set p2Keys = new TreeSet<>(parameter2.keySet()); + + for (Entry> p1Entry : p1Entries) { + if (p2Entries.contains(p1Entry)) { + p1Keys.remove(p1Entry.getKey()); + p2Keys.remove(p1Entry.getKey()); + } + } + + if (!p1Keys.isEmpty() || !p2Keys.isEmpty()) { + differences.add(String.format("%s%s: %s : %s", identifier, ARE_NOT_EQUAL + "-Differing Keys", p1Keys, p2Keys)); + } + } + /** * This method converts the IBDO parameter map of Object values to a map of String values for better comparison. * diff --git a/src/test/java/emissary/core/DiffCheckConfigurationTest.java b/src/test/java/emissary/core/DiffCheckConfigurationTest.java new file mode 100644 index 0000000000..60740af65e --- /dev/null +++ b/src/test/java/emissary/core/DiffCheckConfigurationTest.java @@ -0,0 +1,134 @@ +package emissary.core; + +import emissary.core.DiffCheckConfiguration.DiffCheckBuilder; +import emissary.core.DiffCheckConfiguration.DiffCheckOptions; +import emissary.test.core.junit5.UnitTest; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DiffCheckConfigurationTest extends UnitTest { + @Test + void testNonParameter() { + final DiffCheckBuilder diffCheckBuilder = DiffCheckConfiguration.configure(); + final DiffCheckConfiguration emptyConfiguration = diffCheckBuilder.build(); + + assertEquals(0, emptyConfiguration.getEnabled().size(), "Configuration should be empty!"); + + diffCheckBuilder.enableData(); + diffCheckBuilder.enableInternalId(); + diffCheckBuilder.enableTimestamp(); + diffCheckBuilder.enableTransformHistory(); + + final DiffCheckConfiguration nonParameterConfiguration = diffCheckBuilder.build(); + + assertTrue(nonParameterConfiguration.checkData()); + assertTrue(nonParameterConfiguration.checkInternalId()); + assertTrue(nonParameterConfiguration.checkTimestamp()); + assertTrue(nonParameterConfiguration.checkTransformHistory()); + assertFalse(nonParameterConfiguration.performDetailedParameterDiff()); + assertFalse(nonParameterConfiguration.performKeyValueParameterDiff()); + + diffCheckBuilder.disableData(); + diffCheckBuilder.disableInternalId(); + diffCheckBuilder.disableTimestamp(); + diffCheckBuilder.disableTransformHistory(); + + assertEquals(0, diffCheckBuilder.build().getEnabled().size(), "Configuration should be empty!"); + } + + @Test + void testKeyValueDetailed() { + final DiffCheckBuilder diffCheckBuilder = DiffCheckConfiguration.configure(); + + diffCheckBuilder.enableDetailedParameterDiff(); + + final DiffCheckConfiguration detailedParameterConfiguration = diffCheckBuilder.build(); + + assertTrue(detailedParameterConfiguration.performDetailedParameterDiff()); + assertFalse(detailedParameterConfiguration.performKeyValueParameterDiff()); + + diffCheckBuilder.enableKeyValueParameterDiff(); + + final DiffCheckConfiguration keyValueParameterConfiguration = diffCheckBuilder.build(); + + assertFalse(keyValueParameterConfiguration.performDetailedParameterDiff()); + assertTrue(keyValueParameterConfiguration.performKeyValueParameterDiff()); + + diffCheckBuilder.disableKeyValueParameterDiff(); + + assertEquals(0, diffCheckBuilder.build().getEnabled().size(), "Configuration should be empty!"); + + diffCheckBuilder.enableKeyValueParameterDiff(); + + final DiffCheckConfiguration KeyValueParameterConfiguration2 = diffCheckBuilder.build(); + + assertFalse(KeyValueParameterConfiguration2.performDetailedParameterDiff()); + assertTrue(KeyValueParameterConfiguration2.performKeyValueParameterDiff()); + + diffCheckBuilder.enableDetailedParameterDiff(); + + final DiffCheckConfiguration detailedParameterConfiguration2 = diffCheckBuilder.build(); + + assertTrue(detailedParameterConfiguration2.performDetailedParameterDiff()); + assertFalse(detailedParameterConfiguration2.performKeyValueParameterDiff()); + + diffCheckBuilder.disableDetailedParameterDiff(); + + assertEquals(0, diffCheckBuilder.build().getEnabled().size(), "Configuration should be empty!"); + } + + @Test + void checkReset() { + final DiffCheckBuilder diffCheckBuilder = DiffCheckConfiguration.configure(); + + diffCheckBuilder.enableData(); + diffCheckBuilder.enableInternalId(); + diffCheckBuilder.enableTimestamp(); + diffCheckBuilder.enableTransformHistory(); + diffCheckBuilder.enableDetailedParameterDiff(); + diffCheckBuilder.reset(); + + assertEquals(0, diffCheckBuilder.build().getEnabled().size(), "Configuration should be empty!"); + } + + @Test + void checkExplicit() { + final DiffCheckBuilder diffCheckBuilder = DiffCheckConfiguration.configure(); + final DiffCheckConfiguration explicitDetailedConfiguration = diffCheckBuilder.explicit( + DiffCheckOptions.DATA, + DiffCheckOptions.DETAILED_PARAMETER_DIFF, + DiffCheckOptions.INTERNAL_ID, + DiffCheckOptions.TIMESTAMP, + DiffCheckOptions.TRANSFORM_HISTORY); + + assertTrue(explicitDetailedConfiguration.checkData()); + assertTrue(explicitDetailedConfiguration.checkInternalId()); + assertTrue(explicitDetailedConfiguration.checkTimestamp()); + assertTrue(explicitDetailedConfiguration.checkTransformHistory()); + assertTrue(explicitDetailedConfiguration.performDetailedParameterDiff()); + assertFalse(explicitDetailedConfiguration.performKeyValueParameterDiff()); + + final DiffCheckConfiguration explicitKeyValueConfiguration = diffCheckBuilder.explicit( + DiffCheckOptions.DATA, + DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF, + DiffCheckOptions.INTERNAL_ID, + DiffCheckOptions.TIMESTAMP, + DiffCheckOptions.TRANSFORM_HISTORY); + + assertTrue(explicitKeyValueConfiguration.checkData()); + assertTrue(explicitKeyValueConfiguration.checkInternalId()); + assertTrue(explicitKeyValueConfiguration.checkTimestamp()); + assertTrue(explicitKeyValueConfiguration.checkTransformHistory()); + assertFalse(explicitKeyValueConfiguration.performDetailedParameterDiff()); + assertTrue(explicitKeyValueConfiguration.performKeyValueParameterDiff()); + + assertThrows(IllegalArgumentException.class, () -> diffCheckBuilder.explicit( + DiffCheckOptions.DETAILED_PARAMETER_DIFF, + DiffCheckOptions.KEY_VALUE_PARAMETER_DIFF)); + } +} diff --git a/src/test/java/emissary/core/IBaseDataObjectDiffHelperTest.java b/src/test/java/emissary/core/IBaseDataObjectDiffHelperTest.java index f68080dcdc..a32a642ca3 100644 --- a/src/test/java/emissary/core/IBaseDataObjectDiffHelperTest.java +++ b/src/test/java/emissary/core/IBaseDataObjectDiffHelperTest.java @@ -151,9 +151,44 @@ void testDiffHistory() { @Test void testDiffParameters() { + final DiffCheckConfiguration checkKeyValueDiffParameter = DiffCheckConfiguration.configure().enableKeyValueParameterDiff().build(); + final DiffCheckConfiguration checkDetailedDiffParameter = DiffCheckConfiguration.configure().enableDetailedParameterDiff().build(); + ibdo1.putParameter("STRING", "string"); ibdo1.putParameter("LIST", Arrays.asList("first", "second", "third")); + verifyDiff(1); + verifyDiff(1, checkKeyValueDiffParameter); + verifyDiff(1, checkDetailedDiffParameter); + + ibdo1.clearParameters(); + ibdo2.clearParameters(); + ibdo1.putParameter("STRING", "string"); + ibdo1.putParameter("Integer", Integer.valueOf(1)); + ibdo2.putParameter("STRING", "string"); + + verifyDiff(1); + verifyDiff(1, checkKeyValueDiffParameter); + verifyDiff(1, checkDetailedDiffParameter); + + ibdo1.clearParameters(); + ibdo2.clearParameters(); + ibdo1.putParameter("STRING", "string"); + ibdo2.putParameter("STRING", "string"); + ibdo2.putParameter("Integer", Integer.valueOf(1)); + + verifyDiff(1); + verifyDiff(1, checkKeyValueDiffParameter); + verifyDiff(1, checkDetailedDiffParameter); + + ibdo1.clearParameters(); + ibdo2.clearParameters(); + ibdo1.putParameter("STRING", "string"); + ibdo2.putParameter("STRING", "string"); + + verifyDiff(0); + verifyDiff(0, checkKeyValueDiffParameter); + verifyDiff(0, checkDetailedDiffParameter); } @Test