All Downloads are FREE. Search and download functionalities are using the official Maven repository.

flatgraph.util.DiffTool Maven / Gradle / Ivy

There is a newer version: 0.0.91
Show newest version
package flatgraph.util;

import flatgraph.*;
import java.util.*;

import scala.collection.IterableOnce;
import scala.jdk.javaapi.CollectionConverters;

public class DiffTool {

  /** compare two graphs element by element
   * identity of nodes is given by their id, i.e. if the graphs have the same nodes/edges/properties, but use different
   * node ids, a lot of differences will be reported and the results will be useless
   */
  public static List compare(Graph graph1, Graph graph2) {
    final List diff = new ArrayList<>();
    if (graph1.nodeCount() != graph2.nodeCount()) {
      diff.add(String.format("node count differs: graph1=%d, graph2=%d", graph1.nodeCount(), graph2.nodeCount()));
    }
    
    SortedSet nodeIds = new TreeSet<>();
    graph1.allNodes().foreach(node -> nodeIds.add(node.id()));
    graph2.allNodes().foreach(node -> nodeIds.add(node.id()));

    nodeIds.forEach(nodeId -> {
      final GNode node1 = graph1.node(nodeId);
      final GNode node2 = graph2.node(nodeId);
      if (node1 == null) diff.add(String.format("node %s only exists in graph2", node2));
      else if (node2 == null) diff.add(String.format("node %s only exists in graph1", node1));
      else {
        if (!node1.label().equals(node2.label()))
          diff.add(String.format("different label for nodeId=%d; graph1=%s, graph2=%s ", nodeId, node1.label(), node2.label()));

        final String context = "nodeId=" + nodeId;
        compareProperties(properties(node1), properties(node2), diff, context);
        compareEdges(outEdges(node1), outEdges(node2), diff, context + ".outE");
      }
    });

    return diff;
  }
  
  private static Iterator outEdges(GNode node) {
    return CollectionConverters.asJava(Accessors.getEdgesOut(node).iterator());
  }

  private static Map properties(GNode node) {
    Map properties = new HashMap<>();
    CollectionConverters.asJava(Accessors.getNodeProperties(node).iterator())
            .forEachRemaining(keyValue -> properties.put(keyValue._1, keyValue._2));
    return properties;
  }

  private static void compareProperties(Map properties1, Map properties2, List diff, String context) {
    SortedSet propertyKeys = new TreeSet<>();
    propertyKeys.addAll(properties1.keySet());
    propertyKeys.addAll(properties2.keySet());

    propertyKeys.forEach(key ->
      compareProperty(key, properties1.get(key),properties2.get(key), diff, context)
    );
  }

  private static void compareProperty(String key, Object value1, Object value2, List diff, String context) {
    if (value1 != null && value2 != null) {
      if (value1 instanceof IterableOnce && value2 instanceof IterableOnce) {
        if (!iterablesEqual((IterableOnce) value1, (IterableOnce) value2)) {
          diff.add(String.format("%s; property '%s' has different values: graph1='%s', graph2='%s'", context, key, value1, value2));
        }
      } else if (value1.getClass().isArray() && value2.getClass().isArray()) { // both values are arrays
        if (!arraysEqual(value1, value2)) {
          diff.add(String.format("%s; property '%s' has different values: graph1='%s', graph2='%s'", context, key, value1, value2));
        }
      } else if (!value1.equals(value2)) { // not both values are arrays
        diff.add(String.format("%s; property '%s' has different values: graph1='%s', graph2='%s'", context, key, value1, value2));
      }
    } else if (value1 == null && value2 != null) {
      diff.add(String.format("%s; property '%s' -> '%s' only exists in graph2", context, key, value2));
    } else if (value1 != null && value2 == null) {
      diff.add(String.format("%s; property '%s' -> '%s' only exists in graph1", context, key, value1));
    } 
  }

  private static boolean iterablesEqual(IterableOnce values1, IterableOnce values2) {
    scala.collection.Iterator iterator1 = values1.iterator();
    scala.collection.Iterator iterator2 = values2.iterator();
    while (iterator1.hasNext() || iterator2.hasNext()) {
      if ((iterator1.hasNext() && !iterator2.hasNext()) || (!iterator1.hasNext() && iterator2.hasNext()) ) {
        // different sizes
        return false;
      }

      Object value1 = iterator1.next();
      Object value2 = iterator2.next();
      if (value1 instanceof GNode && value2 instanceof GNode) {
        // special equality handling for GNodes: we don't want to override GNode.equals, which by default should take 
        // the graph into account by default - yet here we explicitly need to ignore it...
        GNode node1 = (GNode) value1;
        GNode node2 = (GNode) value2;
        if ((node1.nodeKind != node2.nodeKind) || node1.seq() != node2.seq()) {
          return false;
        }
      } else if (!value1.equals(value2)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Compare given objects, assuming they are both arrays of the same type.
   * This is required because arrays don't support `.equals`, and is quite lengthy because java has one array type for each data type
   */
  public static boolean arraysEqual(Object value1, Object value2) {
    // need to check all array types unfortunately
    if (value1 instanceof Object[] && value2 instanceof Object[]) {
      return Arrays.deepEquals((Object[]) value1, (Object[]) value2);
    } else if (value1 instanceof boolean[] && value2 instanceof int[]) {
      return Arrays.equals((boolean[]) value1, (boolean[]) value2);
    } else if (value1 instanceof byte[] && value2 instanceof byte[]) {
      return Arrays.equals((byte[]) value1, (byte[]) value2);
    } else if (value1 instanceof char[] && value2 instanceof char[]) {
      return Arrays.equals((char[]) value1, (char[]) value2);
    } else if (value1 instanceof short[] && value2 instanceof short[]) {
      return Arrays.equals((short[]) value1, (short[]) value2);
    } else if (value1 instanceof int[] && value2 instanceof int[]) {
      return Arrays.equals((int[]) value1, (int[]) value2);
    } else if (value1 instanceof long[] && value2 instanceof long[]) {
      return Arrays.equals((long[]) value1, (long[]) value2);
    } else if (value1 instanceof float[] && value2 instanceof float[]) {
      return Arrays.equals((float[]) value1, (float[]) value2);
    } else if (value1 instanceof double[] && value2 instanceof double[]) {
      return Arrays.equals((double[]) value1, (double[]) value2);
    } else {
      throw new AssertionError(String.format(
        "unable to compare given objects (%s of type %s; %s of type %s)",
        value1, value1.getClass(), value2, value2.getClass()));
    }
  }

  private static void compareEdges(Iterator edges1, Iterator edges2, List diff, String context) {
    List edges1Sorted = sort(edges1);
    List edges2Sorted = sort(edges2);

    if (edges1Sorted.size() != edges2Sorted.size()) {
      diff.add(String.format("%s; different number of edges: graph1=%d, graph2=%d", context, edges1Sorted.size(), edges2Sorted.size()));
    } else {
      Iterator edges1SortedIter = edges1Sorted.iterator();
      Iterator edges2SortedIter = edges2Sorted.iterator();
      while (edges1SortedIter.hasNext()) {
        Edge edge1 = edges1SortedIter.next();
        Edge edge2 = edges2SortedIter.next();

        if (!edge1.label().equals(edge2.label()))
          diff.add(String.format("%s; different label for sorted edges; graph1=%s, graph2=%s ", context, edge1.label(), edge2.label()));
        else {
          compareProperty("EDGE_PROPERTY", edge1.property(), edge2.property(), diff, String.format("%s; edge label = %s", context, edge1.label()));
        }
      }
    }
  }

  private static List sort(Iterator edges) {
    List edgesSorted = new LinkedList();
    edges.forEachRemaining(edgesSorted::add);
    edgesSorted.sort(Comparator.comparing(edge ->
      String.format("%s %s", edge.label(), edge.property())
    ));
    return edgesSorted;
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy