IBaseDataObjectDiffHelper.java
package emissary.core;
import emissary.core.channels.SeekableByteChannelFactory;
import emissary.core.constants.IbdoXmlElementNames;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
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 {
private static final String DIFF_OUTPUT_FORMAT = "%s%s: %s : %s";
private static final String DIFF_NOT_NULL_MSG = "Required: differences not null";
private static final String ID_NOT_NULL_MSG = "Required: identifier not null";
private static final String ARE_NOT_EQUAL = " are not equal";
private static final String SHORT_NAME = "shortName";
private static final String INTERNAL_ID = "internalId";
private static final String TRANSFORM_HISTORY = "transformHistory";
private static final String FILE_TYPE_EMPTY = "fileTypeEmpty";
private static final String CREATION_TIMESTAMP = "creationTimestamp";
private static final String EXTRACTED_RECORDS = "extractedRecords";
private IBaseDataObjectDiffHelper() {}
/**
* This method compares two IBaseDataObject's and adds any differences to the provided string list.
*
* @param ibdo1 the first IBaseDataObject to compare.
* @param ibdo2 the second IBaseDataObject to compare.
* @param differences the string list differences are to be added to.
* @param options {@link DiffCheckConfiguration} containing config to specify whether to check data etc.
*/
public static void diff(final IBaseDataObject ibdo1, final IBaseDataObject ibdo2,
final List<String> differences, final DiffCheckConfiguration options) {
Validate.notNull(ibdo1, "Required: ibdo1 not null");
Validate.notNull(ibdo2, "Required: ibdo2 not null");
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
if (options.checkData()) {
final SeekableByteChannelFactory sbcf1 = ibdo1.getChannelFactory();
final SeekableByteChannelFactory sbcf2 = ibdo2.getChannelFactory();
diff(sbcf1, sbcf2, IbdoXmlElementNames.DATA, differences);
}
diff(ibdo1.getFilename(), ibdo2.getFilename(), IbdoXmlElementNames.FILENAME, differences);
diff(ibdo1.shortName(), ibdo2.shortName(), SHORT_NAME, differences);
if (options.checkInternalId()) {
diff(ibdo1.getInternalId(), ibdo2.getInternalId(), INTERNAL_ID, differences);
}
diff(ibdo1.currentForm(), ibdo2.currentForm(), IbdoXmlElementNames.CURRENT_FORM, differences);
diff(ibdo1.getProcessingError(), ibdo2.getProcessingError(), IbdoXmlElementNames.PROCESSING_ERROR, differences);
if (options.checkTransformHistory()) {
diff(ibdo1.transformHistory(), ibdo2.transformHistory(), TRANSFORM_HISTORY, differences);
}
diff(ibdo1.getFontEncoding(), ibdo2.getFontEncoding(), IbdoXmlElementNames.FONT_ENCODING, differences);
if (options.performDetailedParameterDiff()) {
diff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), IbdoXmlElementNames.PARAMETER, differences);
} else if (options.performKeyValueParameterDiff()) {
keyValueMapDiff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), IbdoXmlElementNames.PARAMETER, differences);
} else {
minimalMapDiff(convertMap(ibdo1.getParameters()), convertMap(ibdo2.getParameters()), IbdoXmlElementNames.PARAMETER, differences);
}
diff(ibdo1.getNumChildren(), ibdo2.getNumChildren(), IbdoXmlElementNames.NUM_CHILDREN, differences);
diff(ibdo1.getNumSiblings(), ibdo2.getNumSiblings(), IbdoXmlElementNames.NUM_SIBLINGS, differences);
diff(ibdo1.getBirthOrder(), ibdo2.getBirthOrder(), IbdoXmlElementNames.BIRTH_ORDER, differences);
diff(ibdo1.getAlternateViews(), ibdo2.getAlternateViews(), IbdoXmlElementNames.VIEW, differences);
diff(ibdo1.header(), ibdo2.header(), IbdoXmlElementNames.HEADER, differences);
diff(ibdo1.footer(), ibdo2.footer(), IbdoXmlElementNames.FOOTER, differences);
diff(ibdo1.getHeaderEncoding(), ibdo2.getHeaderEncoding(), IbdoXmlElementNames.HEADER_ENCODING, differences);
diff(ibdo1.getClassification(), ibdo2.getClassification(), IbdoXmlElementNames.CLASSIFICATION, differences);
diff(ibdo1.isBroken(), ibdo2.isBroken(), IbdoXmlElementNames.BROKEN, differences);
diff(ibdo1.isFileTypeEmpty(), ibdo2.isFileTypeEmpty(), FILE_TYPE_EMPTY, differences);
diff(ibdo1.getPriority(), ibdo2.getPriority(), IbdoXmlElementNames.PRIORITY, differences);
if (options.checkTimestamp()) {
diff(ibdo1.getCreationTimestamp(), ibdo2.getCreationTimestamp(), CREATION_TIMESTAMP, differences);
}
diff(ibdo1.isOutputable(), ibdo2.isOutputable(), IbdoXmlElementNames.OUTPUTABLE, differences);
diff(ibdo1.getId(), ibdo2.getId(), IbdoXmlElementNames.ID, differences);
diff(ibdo1.getWorkBundleId(), ibdo2.getWorkBundleId(), IbdoXmlElementNames.WORK_BUNDLE_ID, differences);
diff(ibdo1.getTransactionId(), ibdo2.getTransactionId(), IbdoXmlElementNames.TRANSACTION_ID, differences);
// Special case - pass through DiffCheckConfiguration options. This also ensures the right method is called (Object vs
// List<IBDO>)
diff(ibdo1.getExtractedRecords(), ibdo2.getExtractedRecords(), EXTRACTED_RECORDS, differences, options);
}
/**
* This method compares two lists of IBaseDataObject's and adds any differences to the provided string list.
*
* @param ibdoList1 the first list of IBaseDataObjects to compare.
* @param ibdoList2 the second list of IBaseDataObjects to compare.
* @param identifier a string that helps identify the context of comparing these two list of IBaseDataObjects.
* @param differences the string list differences are to be added to.
* @param options {@link DiffCheckConfiguration} containing config to specify whether to check data etc.
*/
public static void diff(final List<IBaseDataObject> ibdoList1, final List<IBaseDataObject> ibdoList2,
final String identifier, final List<String> differences, final DiffCheckConfiguration options) {
Validate.notNull(identifier, ID_NOT_NULL_MSG);
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
final int ibdoList1Size = (ibdoList1 == null) ? 0 : ibdoList1.size();
final int ibdoList2Size = (ibdoList2 == null) ? 0 : ibdoList2.size();
if (ibdoList1Size != ibdoList2Size) {
differences.add(String.format("%s%s: 1.s=%s 2.s=%s", identifier, ARE_NOT_EQUAL, ibdoList1Size, ibdoList2Size));
} else if (ibdoList1 != null && ibdoList2 != null) {
final List<String> childDifferences = new ArrayList<>();
for (int i = 0; i < ibdoList1.size(); i++) {
childDifferences.clear();
diff(ibdoList1.get(i), ibdoList2.get(i), childDifferences, options);
final String prefix = identifier + " : " + i + " : ";
while (!childDifferences.isEmpty()) {
differences.add(prefix + childDifferences.remove(0)); // NOSONAR Used correctly
}
}
}
}
/**
* This method compares two {@link SeekableByteChannelFactory} (SBCF) objects and adds any differences to the provided
* string list.
*
* @param sbcf1 the first SBCF to compare.
* @param sbcf2 the second SBCF to compare.
* @param identifier an identifier to describe the context of this SBCF comparison.
* @param differences the string list differences are to be added to.
*/
public static void diff(final SeekableByteChannelFactory sbcf1, final SeekableByteChannelFactory sbcf2,
final String identifier, final List<String> differences) {
Validate.notNull(identifier, ID_NOT_NULL_MSG);
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
if (sbcf1 != null && sbcf2 != null) {
try (SeekableByteChannel sbc1 = sbcf1.create();
SeekableByteChannel sbc2 = sbcf2.create();
InputStream is1 = Channels.newInputStream(sbc1);
InputStream is2 = Channels.newInputStream(sbc2)) {
if (!IOUtils.contentEquals(is1, is2)) {
differences.add(String.format("%s not equal. 1.cs=%s 2.cs=%s",
identifier, sbc1.size(), sbc2.size()));
}
} catch (IOException e) {
differences.add(String.format("Failed to compare %s: %s", identifier, e.getMessage()));
}
} else if (sbcf1 == null && sbcf2 == null) {
// Do nothing as they are considered equal.
} else {
differences.add(String.format("%s not equal. sbcf1=%s sbcf2=%s", identifier, sbcf1, sbcf2));
}
}
/**
* This method compares two Objects and adds any differences to the provided string list.
*
* @param object1 the first Object to compare.
* @param object2 the second Object to compare.
* @param identifier an identifier to describe the context of this Object comparison.
* @param differences the string list differences are to be added to.
*/
public static void diff(final Object object1, final Object object2, final String identifier,
final List<String> differences) {
Validate.notNull(identifier, ID_NOT_NULL_MSG);
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
if (!Objects.deepEquals(object1, object2)) {
differences.add(String.format(DIFF_OUTPUT_FORMAT, identifier, ARE_NOT_EQUAL, object1, object2));
}
}
/**
* This method compares two integers and adds any differences to the provided string list.
*
* @param integer1 the first integer to compare.
* @param integer2 the second integer to compare.
* @param identifier an identifier to describe the context of this integer comparison.
* @param differences the string list differences are to be added to.
*/
public static void diff(final int integer1, final int integer2, final String identifier,
final List<String> differences) {
Validate.notNull(identifier, ID_NOT_NULL_MSG);
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
if (integer1 != integer2) {
differences.add(identifier + ARE_NOT_EQUAL);
}
}
/**
* This method compares two booleans and adds any differences to the provided string list.
*
* @param boolean1 the first boolean to compare.
* @param boolean2 the second boolean to compare.
* @param identifier an identifier to describe the context of this boolean comparison.
* @param differences the string list differences are to be added to.
*/
public static void diff(final boolean boolean1, final boolean boolean2, final String identifier,
final List<String> differences) {
Validate.notNull(identifier, ID_NOT_NULL_MSG);
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
if (boolean1 != boolean2) {
differences.add(identifier + ARE_NOT_EQUAL);
}
}
/**
* This method compares two maps and adds any differences to the provided string list.
*
* @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 of differences to be added to.
*/
public static void diff(final Map<String, byte[]> map1, final Map<String, byte[]> map2, final String identifier,
final List<String> differences) {
Validate.notNull(map1, "Required: map1 not null!");
Validate.notNull(map2, "Required: map2 not null!");
Validate.notNull(identifier, ID_NOT_NULL_MSG);
Validate.notNull(differences, DIFF_NOT_NULL_MSG);
if (map1.size() != map2.size() ||
!map1.entrySet().stream().allMatch(e -> Arrays.equals(e.getValue(), map2.get(e.getKey())))) {
differences.add(identifier + ARE_NOT_EQUAL);
}
}
/**
* This method compares two maps and adds only the key/value pairs 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 keyValueMapDiff(final Map<String, Collection<String>> parameter1, final Map<String, Collection<String>> parameter2,
final String identifier, final List<String> differences) {
final Set<Entry<String, Collection<String>>> p1Entries = new HashSet<>(parameter1.entrySet());
final Set<Entry<String, Collection<String>>> p2Entries = new HashSet<>(parameter2.entrySet());
final Map<String, Collection<String>> p1 = new HashMap<>(parameter1);
final Map<String, Collection<String>> p2 = new HashMap<>(parameter2);
for (Entry<String, Collection<String>> p1Entry : p1Entries) {
if (p2Entries.contains(p1Entry)) {
p1.remove(p1Entry.getKey());
p2.remove(p1Entry.getKey());
}
}
if (!p1.isEmpty() || !p2.isEmpty()) {
differences.add(String.format(DIFF_OUTPUT_FORMAT, 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<String, Collection<String>> parameter1, final Map<String, Collection<String>> parameter2,
final String identifier, final List<String> differences) {
final Set<Entry<String, Collection<String>>> p1Entries = new HashSet<>(parameter1.entrySet());
final Set<Entry<String, Collection<String>>> p2Entries = new HashSet<>(parameter2.entrySet());
final Set<String> p1Keys = new TreeSet<>(parameter1.keySet());
final Set<String> p2Keys = new TreeSet<>(parameter2.keySet());
for (Entry<String, Collection<String>> p1Entry : p1Entries) {
if (p2Entries.contains(p1Entry)) {
p1Keys.remove(p1Entry.getKey());
p2Keys.remove(p1Entry.getKey());
}
}
if (!p1Keys.isEmpty() || !p2Keys.isEmpty()) {
differences.add(String.format(DIFF_OUTPUT_FORMAT, 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.
*
* @param map IBDO parameter map
* @return a map that has only Strings as it values.
*/
public static Map<String, Collection<String>> convertMap(final Map<String, Collection<Object>> map) {
Map<String, Collection<String>> newMap = new TreeMap<>();
for (Map.Entry<String, Collection<Object>> e : map.entrySet()) {
final List<String> list = new ArrayList<>();
for (Object o : e.getValue()) {
list.add(o.toString());
}
newMap.put(e.getKey(), list);
}
return newMap;
}
}