de.hbz.lobid.helper.CompareJsonMaps.java Source code

Java tutorial

Introduction

Here is the source code for de.hbz.lobid.helper.CompareJsonMaps.java

Source

/* Copyright 2015  hbz, Pascal Christoph.
 * Licensed under the Eclipse Public License 1.0 */
package de.hbz.lobid.helper;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;

/**
 * Builds a map with json paths as keys with aggregated values, thus comparing
 * to JsonNodes becomes easy. If a key ends with "Order" it is assumed that the
 * values must be in the given order, so this tests also ordered lists.
 * Successfully compared elements will be removed from the actual map, thus a
 * successful comparison leads to an empty 'actual' map (see last line).
 * 
 * @author Pascal Christoph (dr0i)
 * @author Jan Schnasse
 *
 */
@SuppressWarnings("javadoc")
public final class CompareJsonMaps {
    final static Logger logger = LoggerFactory.getLogger(CompareJsonMaps.class);
    Stack<String> stack = new Stack<>();
    static final String JSON_LD_CONTEXT = "[@context";
    private static boolean IGNORE_CONTEXT = true;
    private static String filename1;
    private static String filename2;

    public static void main(String... args) {
        if (args.length < 2)
            CompareJsonMaps.logger
                    .info("Usage: <filename1> <filename2> {<false> if @context should be taken into acount}");
        filename1 = args[0];
        filename2 = args[1];
        CompareJsonMaps.logger.info("\n" + filename1 + " may be referenced in the logs as 'actual'\n" + filename2
                + " may be referenced as 'expected'");
        if (args.length >= 3 && args[2].equals("false"))
            IGNORE_CONTEXT = false;
        try {
            if (new CompareJsonMaps().writeFileAndTestJson(
                    new ObjectMapper().readValue(new File(args[0]), JsonNode.class),
                    new ObjectMapper().readValue(new File(args[1]), JsonNode.class)))
                CompareJsonMaps.logger.info("OK. The content is equal.");
            else
                CompareJsonMaps.logger.info("Sadeness. The content differs.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean writeFileAndTestJson(final JsonNode actual, final JsonNode expected) {
        // generated data to map
        final HashMap<String, String> actualMap = new HashMap<>();
        extractFlatMapFromJsonNode(actual, actualMap);
        // expected data to map
        final HashMap<String, String> expectedMap = new HashMap<>();
        extractFlatMapFromJsonNode(expected, expectedMap);
        CompareJsonMaps.logger.debug("\n##### remove good entries ###");
        Iterator<String> it = actualMap.keySet().iterator();
        removeContext(it);
        it = expectedMap.keySet().iterator();
        removeContext(it);
        for (final Entry<String, String> e : expectedMap.entrySet()) {
            CompareJsonMaps.logger.debug("Trying to remove " + e.getKey() + "...");
            if (!actualMap.containsKey(e.getKey())) {
                CompareJsonMaps.logger.warn("At least this element is missing in actual: " + e.getKey());
                return false;
            }
            if (e.getKey().endsWith("Order]")) {
                handleOrderedValues(actualMap, e);
            } else {
                handleUnorderedValues(actualMap, e);
            }
        }
        if (!actualMap.isEmpty()) {
            CompareJsonMaps.logger.warn("Fail - no Equality! These keys/values were NOT expected:");
            actualMap.forEach((key, val) -> CompareJsonMaps.logger.warn("KEY=" + key + " VALUE=" + val));
        } else
            CompareJsonMaps.logger.info("Succeeded - resources are equal");
        return actualMap.size() == 0;
    }

    private static void removeContext(Iterator<String> it) {
        while (it.hasNext()) {
            String se = it.next();
            if (IGNORE_CONTEXT && se.startsWith(JSON_LD_CONTEXT))
                it.remove();
        }
    }

    private static void handleUnorderedValues(final HashMap<String, String> actualMap,
            final Entry<String, String> e) {
        if (checkIfAllValuesAreContainedUnordered(actualMap.get(e.getKey()), e.getValue())) {
            actualMap.remove(e.getKey());
            CompareJsonMaps.logger.debug("Removed " + e.getKey());
        } else {
            CompareJsonMaps.logger.debug("Missing/wrong: " + e.getKey() + ", will fail");
        }
    }

    private static void handleOrderedValues(final HashMap<String, String> actualMap,
            final Entry<String, String> e) {
        CompareJsonMaps.logger.debug("Test if proper order for: " + e.getKey());
        if (actualMap.containsKey(e.getKey())) {
            CompareJsonMaps.logger.trace("Existing as expected: " + e.getKey());
            if (e.getValue().equals(actualMap.get(e.getKey()))) {
                CompareJsonMaps.logger.trace("Equality:\n" + e.getValue() + "\n" + actualMap.get(e.getKey()));
                actualMap.remove(e.getKey());
            } else
                CompareJsonMaps.logger.debug("...but not equal! Will fail");
        } else {
            CompareJsonMaps.logger.warn("Missing: " + e.getKey() + " , will fail");
        }
    }

    /**
     * Construct a map with json paths as keys with aggregated values form json
     * nodes.
     * 
     * @param jnode the JsonNode which should be transformed into a map
     * @param map the map constructed out of the JsonNode
     */
    public void extractFlatMapFromJsonNode(final JsonNode jnode, final HashMap<String, String> map) {
        if (jnode.getNodeType().equals(JsonNodeType.OBJECT)) {
            final Iterator<Map.Entry<String, JsonNode>> it = jnode.fields();
            while (it.hasNext()) {
                final Map.Entry<String, JsonNode> entry = it.next();
                stack.push(entry.getKey());
                extractFlatMapFromJsonNode(entry.getValue(), map);
                stack.pop();
            }
        } else if (jnode.isArray()) {
            final Iterator<JsonNode> it = jnode.iterator();
            while (it.hasNext()) {
                extractFlatMapFromJsonNode(it.next(), map);
            }
        } else if (jnode.isValueNode()) {
            String value = jnode.toString();
            if (map.containsKey(stack.toString()))
                value = map.get(stack.toString()).concat("," + jnode.toString());
            map.put(stack.toString(), value);
            CompareJsonMaps.logger.trace("Stored this path as key into map:" + stack.toString(), value);
        }
    }

    /*
     * Values may be an unorderd set.
     */
    private static boolean checkIfAllValuesAreContainedUnordered(final String actual, final String expected) {
        CompareJsonMaps.logger.trace("\nActual   value: " + actual + "\nExpected value: " + expected);
        return valuesToList(actual).containsAll(valuesToList(expected));
    }

    private static List<String> valuesToList(final String values) {
        return Arrays.asList(values.substring(1, values.length() - 1).split("\",\""));
    }
}