graphql.schema.diffing.EditorialCostForMapping Maven / Gradle / Ivy
package graphql.schema.diffing;
import graphql.Internal;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
@Internal
public class EditorialCostForMapping {
/**
* @param mapping the mapping
* @param sourceGraph the source graph
* @param targetGraph the target graph
*
* @return the editorial cost
*
* @see #baseEditorialCostForMapping(Mapping, SchemaGraph, SchemaGraph, List)
*/
public static int baseEditorialCostForMapping(Mapping mapping, // can be a partial mapping
SchemaGraph sourceGraph, // the whole graph
SchemaGraph targetGraph // the whole graph
) {
return baseEditorialCostForMapping(mapping, sourceGraph, targetGraph, new ArrayList<>());
}
/**
* Gets the "editorial cost for mapping" for the base mapping.
*
* Use this is as base cost when invoking
* {@link #editorialCostForMapping(int, Mapping, SchemaGraph, SchemaGraph)}
* as it heavily speeds up performance.
*
* @param mapping the mapping
* @param sourceGraph the source graph
* @param targetGraph the target graph
* @param editOperationsResult the list of edit operations
*
* @return the editorial cost
*/
public static int baseEditorialCostForMapping(Mapping mapping, // can be a partial mapping
SchemaGraph sourceGraph, // the whole graph
SchemaGraph targetGraph, // the whole graph
List editOperationsResult) {
int cost = 0;
for (int i = 0; i < mapping.size(); i++) {
Vertex sourceVertex = mapping.getSource(i);
Vertex targetVertex = mapping.getTarget(i);
// Vertex changing (relabeling)
boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties());
if (!equalNodes) {
if (sourceVertex.isIsolated()) {
editOperationsResult.add(EditOperation.insertVertex("Insert" + targetVertex, sourceVertex, targetVertex));
} else if (targetVertex.isIsolated()) {
editOperationsResult.add(EditOperation.deleteVertex("Delete " + sourceVertex, sourceVertex, targetVertex));
} else {
editOperationsResult.add(EditOperation.changeVertex("Change " + sourceVertex + " to " + targetVertex, sourceVertex, targetVertex));
}
cost++;
}
}
// edge deletion or relabeling
for (Edge sourceEdge : sourceGraph.getEdges()) {
// only edges relevant to the subgraph
if (!mapping.containsSource(sourceEdge.getFrom()) || !mapping.containsSource(sourceEdge.getTo())) {
continue;
}
Vertex targetFrom = mapping.getTarget(sourceEdge.getFrom());
Vertex targetTo = mapping.getTarget(sourceEdge.getTo());
Edge targetEdge = targetGraph.getEdge(targetFrom, targetTo);
if (targetEdge == null) {
editOperationsResult.add(EditOperation.deleteEdge("Delete edge " + sourceEdge, sourceEdge));
cost++;
} else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) {
editOperationsResult.add(EditOperation.changeEdge("Change " + sourceEdge + " to " + targetEdge, sourceEdge, targetEdge));
cost++;
}
}
for (Edge targetEdge : targetGraph.getEdges()) {
// only subgraph edges
if (!mapping.containsTarget(targetEdge.getFrom()) || !mapping.containsTarget(targetEdge.getTo())) {
continue;
}
Vertex sourceFrom = mapping.getSource(targetEdge.getFrom());
Vertex sourceTo = mapping.getSource(targetEdge.getTo());
if (sourceGraph.getEdge(sourceFrom, sourceTo) == null) {
editOperationsResult.add(EditOperation.insertEdge("Insert edge " + targetEdge, targetEdge));
cost++;
}
}
return cost;
}
/**
* Calculates the "editorial cost for mapping" for the non-fixed targets in a {@link Mapping}.
*
* The {@code baseCost} argument should be the cost for the fixed mapping from
* {@link #baseEditorialCostForMapping(Mapping, SchemaGraph, SchemaGraph)}.
*
* The sum of the non-fixed costs and the fixed costs is total editorial cost for mapping.
*
* @param baseCost the starting base cost
* @param mapping the mapping
* @param sourceGraph the source graph
* @param targetGraph the target graph
*
* @return the editorial cost
*/
public static int editorialCostForMapping(int baseCost,
Mapping mapping, // can be a partial mapping
SchemaGraph sourceGraph, // the whole graph
SchemaGraph targetGraph // the whole graph
) {
AtomicInteger cost = new AtomicInteger(baseCost);
Set seenEdges = new LinkedHashSet<>();
// Tells us whether the edge should be visited. We need to avoid counting edges more than once
Predicate visitEdge = (data) -> {
if (seenEdges.contains(data)) {
return false;
} else {
seenEdges.add(data);
return true;
}
};
// Look through
mapping.forEachNonFixedSourceAndTarget((sourceVertex, targetVertex) -> {
// Vertex changing (relabeling)
boolean equalNodes = sourceVertex.getType().equals(targetVertex.getType()) && sourceVertex.getProperties().equals(targetVertex.getProperties());
if (!equalNodes) {
cost.getAndIncrement();
}
for (Edge sourceEdge : sourceGraph.getAdjacentEdgesAndInverseNonCopy(sourceVertex)) {
if (!visitEdge.test(sourceEdge)) {
continue;
}
// only edges relevant to the subgraph
if (!mapping.containsSource(sourceEdge.getFrom()) || !mapping.containsSource(sourceEdge.getTo())) {
continue;
}
Vertex targetFrom = mapping.getTarget(sourceEdge.getFrom());
Vertex targetTo = mapping.getTarget(sourceEdge.getTo());
Edge targetEdge = targetGraph.getEdge(targetFrom, targetTo);
if (targetEdge == null) {
cost.getAndIncrement();
} else if (!sourceEdge.getLabel().equals(targetEdge.getLabel())) {
cost.getAndIncrement();
}
}
for (Edge targetEdge : targetGraph.getAdjacentEdgesAndInverseNonCopy(targetVertex)) {
if (!visitEdge.test(targetEdge)) {
continue;
}
// only edges relevant to the subgraph
if (!mapping.containsTarget(targetEdge.getFrom()) || !mapping.containsTarget(targetEdge.getTo())) {
continue;
}
Vertex sourceFrom = mapping.getSource(targetEdge.getFrom());
Vertex sourceTo = mapping.getSource(targetEdge.getTo());
Edge sourceEdge = sourceGraph.getEdge(sourceFrom, sourceTo);
if (sourceEdge == null) {
cost.getAndIncrement();
}
}
});
return cost.get();
}
}