graphql.schema.diffing.PossibleMappingsCalculator Maven / Gradle / Ivy
package graphql.schema.diffing;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import graphql.Assert;
import graphql.Internal;
import graphql.util.FpKit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static graphql.schema.diffing.SchemaGraph.APPLIED_ARGUMENT;
import static graphql.schema.diffing.SchemaGraph.APPLIED_DIRECTIVE;
import static graphql.schema.diffing.SchemaGraph.ARGUMENT;
import static graphql.schema.diffing.SchemaGraph.DIRECTIVE;
import static graphql.schema.diffing.SchemaGraph.ENUM;
import static graphql.schema.diffing.SchemaGraph.ENUM_VALUE;
import static graphql.schema.diffing.SchemaGraph.FIELD;
import static graphql.schema.diffing.SchemaGraph.INPUT_FIELD;
import static graphql.schema.diffing.SchemaGraph.INPUT_OBJECT;
import static graphql.schema.diffing.SchemaGraph.INTERFACE;
import static graphql.schema.diffing.SchemaGraph.OBJECT;
import static graphql.schema.diffing.SchemaGraph.SCALAR;
import static graphql.schema.diffing.SchemaGraph.SCHEMA;
import static graphql.schema.diffing.SchemaGraph.UNION;
import static graphql.util.FpKit.concat;
import static java.util.Collections.singletonList;
/**
* We don't want to allow arbitrary schema changes. For example changing an Object type into a Scalar
* is not something we want to consider.
*
* We do this to make SchemaDiffings better understandable, but also to improve the overall runtime of
* the algorithm. By restricting the possible mappings the Schema diffing algo is actually able to
* finish in a reasonable time for real life inputs.
*
*
* We restrict the algo by calculating which mappings are possible for given vertex. This is later used in
* {@link DiffImpl#calcLowerBoundMappingCost}.
* While doing this we need to also ensure that there are the same amount of vertices in the same "context":
* for example if the source graph has 3 Objects, the target graph needs to have 3 Objects. We achieve this by
* adding "isolated vertices" as needed.
*/
@Internal
public class PossibleMappingsCalculator {
private final SchemaDiffingRunningCheck runningCheck;
private final SchemaGraph sourceGraph;
private final SchemaGraph targetGraph;
private final PossibleMappings possibleMappings;
private static final Map> typeContexts = new LinkedHashMap<>();
static {
typeContexts.put(SCHEMA, schemaContext());
typeContexts.put(FIELD, fieldContext());
typeContexts.put(ARGUMENT, argumentsContexts());
typeContexts.put(INPUT_FIELD, inputFieldContexts());
typeContexts.put(OBJECT, objectContext());
typeContexts.put(INTERFACE, interfaceContext());
typeContexts.put(UNION, unionContext());
typeContexts.put(INPUT_OBJECT, inputObjectContext());
typeContexts.put(SCALAR, scalarContext());
typeContexts.put(ENUM, enumContext());
typeContexts.put(ENUM_VALUE, enumValueContext());
typeContexts.put(APPLIED_DIRECTIVE, appliedDirectiveContext());
typeContexts.put(APPLIED_ARGUMENT, appliedArgumentContext());
typeContexts.put(DIRECTIVE, directiveContext());
}
private static List inputFieldContexts() {
VertexContextSegment inputFieldType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return INPUT_FIELD.equals(vertex.getType());
}
};
VertexContextSegment inputObjectContext = new VertexContextSegment() {
@Override
public String idForVertex(Vertex inputField, SchemaGraph schemaGraph) {
Vertex inputObject = schemaGraph.getInputObjectForInputField(inputField);
return inputObject.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment inputFieldName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex inputField, SchemaGraph schemaGraph) {
return inputField.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(inputFieldType, inputObjectContext, inputFieldName);
return contexts;
}
private static List scalarContext() {
VertexContextSegment scalar = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return SCALAR.equals(vertex.getType());
}
};
VertexContextSegment scalarName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(scalar, scalarName);
return contexts;
}
private static List inputObjectContext() {
VertexContextSegment inputObject = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return INPUT_OBJECT.equals(vertex.getType());
}
};
VertexContextSegment inputObjectName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex inputObject, SchemaGraph schemaGraph) {
return inputObject.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(inputObject, inputObjectName);
return contexts;
}
private static List objectContext() {
VertexContextSegment objectType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return OBJECT.equals(vertex.getType());
}
};
VertexContextSegment objectName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex object, SchemaGraph schemaGraph) {
return object.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(objectType, objectName);
return contexts;
}
private static List enumContext() {
VertexContextSegment enumCtxType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return ENUM.equals(vertex.getType());
}
};
VertexContextSegment enumName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex enumVertex, SchemaGraph schemaGraph) {
return enumVertex.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(enumCtxType, enumName);
return contexts;
}
private static List enumValueContext() {
VertexContextSegment enumValueType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return ENUM_VALUE.equals(vertex.getType());
}
};
VertexContextSegment enumName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex enumValue, SchemaGraph schemaGraph) {
return schemaGraph.getEnumForEnumValue(enumValue).getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment enumValueName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex enumValue, SchemaGraph schemaGraph) {
return enumValue.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(enumValueType, enumName, enumValueName);
return contexts;
}
private static List interfaceContext() {
VertexContextSegment interfaceType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return INTERFACE.equals(vertex.getType());
}
};
VertexContextSegment interfaceName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex interfaceVertex, SchemaGraph schemaGraph) {
return interfaceVertex.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(interfaceType, interfaceName);
return contexts;
}
private static List unionContext() {
VertexContextSegment unionType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return UNION.equals(vertex.getType());
}
};
VertexContextSegment unionName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex union, SchemaGraph schemaGraph) {
return union.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(unionType, unionName);
return contexts;
}
private static List directiveContext() {
VertexContextSegment directiveType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return DIRECTIVE.equals(vertex.getType());
}
};
VertexContextSegment directiveName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex directive, SchemaGraph schemaGraph) {
return directive.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(directiveType, directiveName);
return contexts;
}
private static List appliedDirectiveContext() {
VertexContextSegment appliedDirectiveType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return APPLIED_DIRECTIVE.equals(vertex.getType());
}
};
VertexContextSegment appliedDirectiveIndex = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) {
int appliedDirectiveIndex = schemaGraph.getAppliedDirectiveIndex(appliedDirective);
return Integer.toString(appliedDirectiveIndex);
}
};
VertexContextSegment appliedDirectiveName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) {
return appliedDirective.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment appliedDirectiveContainer = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) {
Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective);
return appliedDirectiveContainer.getType() + "." + appliedDirectiveContainer.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment parentOfContainer = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) {
Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective);
switch (container.getType()) {
case SCHEMA:
return SCHEMA;
case FIELD:
Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container);
return fieldsContainer.getType() + "." + fieldsContainer.getName();
case OBJECT:
return OBJECT;
case INTERFACE:
return INTERFACE;
case INPUT_FIELD:
Vertex inputObject = schemaGraph.getInputObjectForInputField(container);
return inputObject.getType() + "." + inputObject.getName();
case ARGUMENT:
Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container);
return fieldOrDirective.getType() + "." + fieldOrDirective.getName();
case INPUT_OBJECT:
return INPUT_OBJECT;
case ENUM:
return ENUM;
case UNION:
return UNION;
case SCALAR:
return SCALAR;
case ENUM_VALUE:
Vertex enumVertex = schemaGraph.getEnumForEnumValue(container);
return enumVertex.getType() + "." + enumVertex.getName();
default:
throw new IllegalStateException("Unexpected directive container type " + container.getType());
}
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment parentOfParentOfContainer = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedDirective, SchemaGraph schemaGraph) {
Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective);
switch (container.getType()) {
case SCHEMA:
case FIELD:
case OBJECT:
case INTERFACE:
case INPUT_FIELD:
case INPUT_OBJECT:
case ENUM:
case ENUM_VALUE:
case UNION:
case SCALAR:
return "";
case ARGUMENT:
Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container);
switch (fieldOrDirective.getType()) {
case FIELD:
Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrDirective);
return fieldsContainer.getType() + "." + fieldsContainer.getName();
case DIRECTIVE:
return "";
default:
return Assert.assertShouldNeverHappen();
}
default:
throw new IllegalStateException("Unexpected directive container type " + container.getType());
}
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment vertexContextSegment = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return parentOfParentOfContainer.idForVertex(vertex, schemaGraph) + "." +
parentOfContainer.idForVertex(vertex, schemaGraph) + "." +
appliedDirectiveContainer.idForVertex(vertex, schemaGraph) + "." +
appliedDirectiveName.idForVertex(vertex, schemaGraph);
}
};
List contexts = Arrays.asList(appliedDirectiveType, vertexContextSegment, appliedDirectiveIndex);
return contexts;
}
private static List appliedArgumentContext() {
VertexContextSegment appliedArgumentType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return APPLIED_ARGUMENT.equals(vertex.getType());
}
};
VertexContextSegment appliedDirective = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) {
Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument);
int appliedDirectiveIndex = schemaGraph.getAppliedDirectiveIndex(appliedDirective);
return appliedDirectiveIndex + ":" + appliedDirective.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment appliedDirectiveContainer = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) {
Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument);
Vertex appliedDirectiveContainer = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective);
return appliedDirectiveContainer.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment parentOfContainer = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) {
Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument);
Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective);
switch (container.getType()) {
case SCHEMA:
return SCHEMA;
case FIELD:
Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(container);
return fieldsContainer.getType() + "." + fieldsContainer.getName();
case OBJECT:
return OBJECT;
case INTERFACE:
return INTERFACE;
case INPUT_FIELD:
Vertex inputObject = schemaGraph.getInputObjectForInputField(container);
return inputObject.getType() + "." + inputObject.getName();
case ARGUMENT:
Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container);
return fieldOrDirective.getType() + "." + fieldOrDirective.getName();
case INPUT_OBJECT:
return INPUT_OBJECT;
case ENUM:
return ENUM;
case UNION:
return UNION;
case SCALAR:
return SCALAR;
case ENUM_VALUE:
Vertex enumVertex = schemaGraph.getEnumForEnumValue(container);
return enumVertex.getType() + "." + enumVertex.getName();
default:
throw new IllegalStateException("Unexpected directive container type " + container.getType());
}
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment parentOfParentOfContainer = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) {
Vertex appliedDirective = schemaGraph.getAppliedDirectiveForAppliedArgument(appliedArgument);
Vertex container = schemaGraph.getAppliedDirectiveContainerForAppliedDirective(appliedDirective);
switch (container.getType()) {
case SCHEMA:
case FIELD:
case OBJECT:
case INTERFACE:
case INPUT_FIELD:
case INPUT_OBJECT:
case ENUM:
case ENUM_VALUE:
case UNION:
case SCALAR:
return "";
case ARGUMENT:
Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(container);
switch (fieldOrDirective.getType()) {
case FIELD:
Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrDirective);
return fieldsContainer.getType() + "." + fieldsContainer.getName();
case DIRECTIVE:
return "";
default:
return Assert.assertShouldNeverHappen();
}
default:
throw new IllegalStateException("Unexpected directive container type " + container.getType());
}
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment appliedArgumentName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex appliedArgument, SchemaGraph schemaGraph) {
return appliedArgument.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment combined = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return parentOfContainer.idForVertex(vertex, schemaGraph) + "." +
parentOfContainer.idForVertex(vertex, schemaGraph) + "." +
appliedDirectiveContainer.idForVertex(vertex, schemaGraph) + "." +
appliedDirective.idForVertex(vertex, schemaGraph) + "." +
appliedArgumentName.idForVertex(vertex, schemaGraph);
}
};
List contexts = Arrays.asList(appliedArgumentType, combined);
return contexts;
}
private static List schemaContext() {
VertexContextSegment schema = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return SCHEMA.equals(vertex.getType());
}
};
return singletonList(schema);
}
private static List fieldContext() {
VertexContextSegment field = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return FIELD.equals(vertex.getType());
}
};
VertexContextSegment container = new VertexContextSegment() {
@Override
public String idForVertex(Vertex field, SchemaGraph schemaGraph) {
Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(field);
return fieldsContainer.getType() + "." + fieldsContainer.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment fieldName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex field, SchemaGraph schemaGraph) {
return field.getName();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(field, container, fieldName);
return contexts;
}
private static List argumentsContexts() {
VertexContextSegment argumentType = new VertexContextSegment() {
@Override
public String idForVertex(Vertex vertex, SchemaGraph schemaGraph) {
return vertex.getType();
}
@Override
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return ARGUMENT.equals(vertex.getType());
}
};
VertexContextSegment fieldOrDirective = new VertexContextSegment() {
@Override
public String idForVertex(Vertex argument, SchemaGraph schemaGraph) {
Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument);
return fieldOrDirective.getType() + "." + fieldOrDirective.getName();
}
@Override
public boolean filter(Vertex argument, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment containerOrDirectiveHolder = new VertexContextSegment() {
@Override
public String idForVertex(Vertex argument, SchemaGraph schemaGraph) {
Vertex fieldOrDirective = schemaGraph.getFieldOrDirectiveForArgument(argument);
if (fieldOrDirective.getType().equals(FIELD)) {
Vertex fieldsContainer = schemaGraph.getFieldsContainerForField(fieldOrDirective);
// can be Interface or Object
return fieldsContainer.getType() + "." + fieldsContainer.getName();
} else {
// a directive doesn't have further context
return "";
}
}
@Override
public boolean filter(Vertex argument, SchemaGraph schemaGraph) {
return true;
}
};
VertexContextSegment argumentName = new VertexContextSegment() {
@Override
public String idForVertex(Vertex argument, SchemaGraph schemaGraph) {
return argument.getName();
}
@Override
public boolean filter(Vertex argument, SchemaGraph schemaGraph) {
return true;
}
};
List contexts = Arrays.asList(argumentType, containerOrDirectiveHolder, fieldOrDirective, argumentName);
return contexts;
}
public PossibleMappingsCalculator(SchemaGraph sourceGraph, SchemaGraph targetGraph, SchemaDiffingRunningCheck runningCheck) {
this.runningCheck = runningCheck;
this.sourceGraph = sourceGraph;
this.targetGraph = targetGraph;
this.possibleMappings = new PossibleMappings();
}
public PossibleMappings calculate() {
calcPossibleMappings(typeContexts.get(SCHEMA), SCHEMA);
calcPossibleMappings(typeContexts.get(FIELD), FIELD);
calcPossibleMappings(typeContexts.get(ARGUMENT), ARGUMENT);
calcPossibleMappings(typeContexts.get(INPUT_FIELD), INPUT_FIELD);
calcPossibleMappings(typeContexts.get(OBJECT), OBJECT);
calcPossibleMappings(typeContexts.get(INTERFACE), INTERFACE);
calcPossibleMappings(typeContexts.get(UNION), UNION);
calcPossibleMappings(typeContexts.get(INPUT_OBJECT), INPUT_OBJECT);
calcPossibleMappings(typeContexts.get(SCALAR), SCALAR);
calcPossibleMappings(typeContexts.get(ENUM), ENUM);
calcPossibleMappings(typeContexts.get(ENUM_VALUE), ENUM_VALUE);
calcPossibleMappings(typeContexts.get(APPLIED_DIRECTIVE), APPLIED_DIRECTIVE);
calcPossibleMappings(typeContexts.get(APPLIED_ARGUMENT), APPLIED_ARGUMENT);
calcPossibleMappings(typeContexts.get(DIRECTIVE), DIRECTIVE);
sourceGraph.addVertices(possibleMappings.allIsolatedSource);
targetGraph.addVertices(possibleMappings.allIsolatedTarget);
Assert.assertTrue(sourceGraph.size() == targetGraph.size());
Set vertices = possibleMappings.possibleMappings.keySet();
for (Vertex vertex : vertices) {
if (possibleMappings.possibleMappings.get(vertex).size() > 1) {
// System.out.println("vertex with possible mappings: " + possibleMappings.possibleMappings.get(vertex).size());
// System.out.println("vertex " + vertex);
// System.out.println("-------------");
}
}
return possibleMappings;
}
public abstract static class VertexContextSegment {
public VertexContextSegment() {
}
public abstract String idForVertex(Vertex vertex, SchemaGraph schemaGraph);
public boolean filter(Vertex vertex, SchemaGraph schemaGraph) {
return true;
}
}
public class PossibleMappings {
public Set allIsolatedSource = new LinkedHashSet<>();
public Set allIsolatedTarget = new LinkedHashSet<>();
public Table, Set, Set> contexts = HashBasedTable.create();
public Multimap possibleMappings = HashMultimap.create();
public BiMap fixedOneToOneMappings = HashBiMap.create();
public List fixedOneToOneSources = new ArrayList<>();
public List fixedOneToOneTargets = new ArrayList<>();
public void putPossibleMappings(List contextId,
Collection sourceVertices,
Collection targetVertices,
String typeName) {
if (sourceVertices.isEmpty() && targetVertices.isEmpty()) {
return;
}
if (sourceVertices.size() == 1 && targetVertices.size() == 1) {
Vertex sourceVertex = sourceVertices.iterator().next();
Vertex targetVertex = targetVertices.iterator().next();
fixedOneToOneMappings.put(sourceVertex, targetVertex);
fixedOneToOneSources.add(sourceVertex);
fixedOneToOneTargets.add(targetVertex);
return;
}
if (APPLIED_DIRECTIVE.equals(typeName) || APPLIED_ARGUMENT.equals(typeName)) {
for (Vertex sourceVertex : sourceVertices) {
Vertex isolatedTarget = Vertex.newIsolatedNode("target-isolated-" + typeName);
allIsolatedTarget.add(isolatedTarget);
fixedOneToOneMappings.put(sourceVertex, isolatedTarget);
fixedOneToOneSources.add(sourceVertex);
fixedOneToOneTargets.add(isolatedTarget);
}
for (Vertex targetVertex : targetVertices) {
Vertex isolatedSource = Vertex.newIsolatedNode("source-isolated-" + typeName);
allIsolatedSource.add(isolatedSource);
fixedOneToOneMappings.put(isolatedSource, targetVertex);
fixedOneToOneSources.add(isolatedSource);
fixedOneToOneTargets.add(targetVertex);
}
return;
}
Set newIsolatedSource = Collections.emptySet();
Set newIsolatedTarget = Collections.emptySet();
if (sourceVertices.size() > targetVertices.size()) {
newIsolatedTarget = Vertex.newIsolatedNodes(sourceVertices.size() - targetVertices.size(), "target-isolated-" + typeName + "-");
} else if (targetVertices.size() > sourceVertices.size()) {
newIsolatedSource = Vertex.newIsolatedNodes(targetVertices.size() - sourceVertices.size(), "source-isolated-" + typeName + "-");
}
this.allIsolatedSource.addAll(newIsolatedSource);
this.allIsolatedTarget.addAll(newIsolatedTarget);
if (sourceVertices.size() == 0) {
Iterator iterator = newIsolatedSource.iterator();
for (Vertex targetVertex : targetVertices) {
Vertex isolatedSourceVertex = iterator.next();
fixedOneToOneMappings.put(isolatedSourceVertex, targetVertex);
fixedOneToOneSources.add(isolatedSourceVertex);
fixedOneToOneTargets.add(targetVertex);
}
return;
}
if (targetVertices.size() == 0) {
Iterator iterator = newIsolatedTarget.iterator();
for (Vertex sourceVertex : sourceVertices) {
Vertex isolatedTargetVertex = iterator.next();
fixedOneToOneMappings.put(sourceVertex, isolatedTargetVertex);
fixedOneToOneSources.add(sourceVertex);
fixedOneToOneTargets.add(isolatedTargetVertex);
}
return;
}
// System.out.println("multiple mappings for context" + contextId + " overall size: " + (sourceVertices.size() + newIsolatedSource.size()));
// List vertexContextSegments = typeContexts.get(typeName);
// System.out.println("source ids: " + sourceVertices.size());
// for (Vertex sourceVertex : sourceVertices) {
// List id = vertexContextSegments.stream().map(vertexContextSegment -> vertexContextSegment.idForVertex(sourceVertex, sourceGraph))
// .collect(Collectors.toList());
// System.out.println("id: " + id);
// }
// System.out.println("target ids ==================: " + targetVertices.size());
// for (Vertex targetVertex : targetVertices) {
// List id = vertexContextSegments.stream().map(vertexContextSegment -> vertexContextSegment.idForVertex(targetVertex, targetGraph))
// .collect(Collectors.toList());
// System.out.println("id: " + id);
// }
// System.out.println("-------------------");
// System.out.println("-------------------");
Assert.assertFalse(contexts.containsRow(contextId));
Set allSource = new LinkedHashSet<>();
allSource.addAll(sourceVertices);
allSource.addAll(newIsolatedSource);
Set allTarget = new LinkedHashSet<>();
allTarget.addAll(targetVertices);
allTarget.addAll(newIsolatedTarget);
contexts.put(contextId, allSource, allTarget);
for (Vertex sourceVertex : sourceVertices) {
possibleMappings.putAll(sourceVertex, targetVertices);
possibleMappings.putAll(sourceVertex, newIsolatedTarget);
}
for (Vertex sourceIsolatedVertex : newIsolatedSource) {
possibleMappings.putAll(sourceIsolatedVertex, targetVertices);
possibleMappings.putAll(sourceIsolatedVertex, newIsolatedTarget);
}
}
//
public boolean mappingPossible(Vertex sourceVertex, Vertex targetVertex) {
return possibleMappings.containsEntry(sourceVertex, targetVertex);
}
}
public void calcPossibleMappings(List contexts, String typeNameForDebug) {
Collection currentSourceVertices = sourceGraph.getVertices();
Collection currentTargetVertices = targetGraph.getVertices();
calcPossibleMappingImpl(currentSourceVertices,
currentTargetVertices,
Collections.emptyList(),
0,
contexts,
new LinkedHashSet<>(),
new LinkedHashSet<>(),
typeNameForDebug);
}
/**
* calc for the provided context
*/
private void calcPossibleMappingImpl(
Collection currentSourceVertices,
Collection currentTargetVertices,
List contextId,
int contextIx,
List contexts,
Set usedSourceVertices,
Set usedTargetVertices,
String typeNameForDebug) {
runningCheck.check();
VertexContextSegment finalCurrentContext = contexts.get(contextIx);
Map> sourceGroups = FpKit.filterAndGroupingBy(currentSourceVertices,
v -> finalCurrentContext.filter(v, sourceGraph),
v -> finalCurrentContext.idForVertex(v, sourceGraph));
Map> targetGroups = FpKit.filterAndGroupingBy(currentTargetVertices,
v -> finalCurrentContext.filter(v, targetGraph),
v -> finalCurrentContext.idForVertex(v, targetGraph));
List deletedContexts = new ArrayList<>();
List insertedContexts = new ArrayList<>();
List sameContexts = new ArrayList<>();
Util.diffNamedList(sourceGroups.keySet(), targetGroups.keySet(), deletedContexts, insertedContexts, sameContexts);
// for each unchanged context we descend recursively into
for (String sameContext : sameContexts) {
ImmutableList sourceVerticesInContext = sourceGroups.get(sameContext);
ImmutableList targetVerticesInContext = targetGroups.get(sameContext);
List currentContextId = concat(contextId, sameContext);
if (contexts.size() > contextIx + 1) {
calcPossibleMappingImpl(sourceVerticesInContext, targetVerticesInContext, currentContextId, contextIx + 1, contexts, usedSourceVertices, usedTargetVertices, typeNameForDebug);
}
/**
* Either there was no context segment left or not all vertices were relevant for
* Either way: fill up with isolated vertices and record as possible mapping
*/
Set notUsedSource = new LinkedHashSet<>(sourceVerticesInContext);
notUsedSource.removeAll(usedSourceVertices);
Set notUsedTarget = new LinkedHashSet<>(targetVerticesInContext);
notUsedTarget.removeAll(usedTargetVertices);
possibleMappings.putPossibleMappings(currentContextId, notUsedSource, notUsedTarget, typeNameForDebug);
usedSourceVertices.addAll(notUsedSource);
usedTargetVertices.addAll(notUsedTarget);
}
/**
* update the used vertices with the deleted and inserted contexts
*/
Set possibleSourceVertices = new LinkedHashSet<>();
for (String deletedContext : deletedContexts) {
ImmutableList vertices = sourceGroups.get(deletedContext);
for (Vertex sourceVertex : vertices) {
if (!usedSourceVertices.contains(sourceVertex)) {
possibleSourceVertices.add(sourceVertex);
}
}
usedSourceVertices.addAll(vertices);
}
Set possibleTargetVertices = new LinkedHashSet<>();
for (String insertedContext : insertedContexts) {
ImmutableList vertices = targetGroups.get(insertedContext);
for (Vertex targetVertex : vertices) {
if (!usedTargetVertices.contains(targetVertex)) {
possibleTargetVertices.add(targetVertex);
}
}
usedTargetVertices.addAll(vertices);
}
if (contextId.size() == 0) {
contextId = singletonList(typeNameForDebug);
}
possibleMappings.putPossibleMappings(contextId, possibleSourceVertices, possibleTargetVertices, typeNameForDebug);
}
public Map getFixedParentRestrictions() {
return getFixedParentRestrictions(
sourceGraph,
possibleMappings.fixedOneToOneSources,
possibleMappings.fixedOneToOneMappings
);
}
public Map getFixedParentRestrictionsInverse(Map fixedOneToOneMappingsInverted) {
return getFixedParentRestrictions(
targetGraph,
possibleMappings.fixedOneToOneTargets,
fixedOneToOneMappingsInverted
);
}
/**
* This computes the initial set of parent restrictions based on the fixed portion of the mapping.
*
* See {@link Mapping} for definition of fixed vs non-fixed.
*
* If a {@link Vertex} is present in the output {@link Map} then the value is the parent the
* vertex MUST map to.
*
* e.g. for an output {collar: Dog} then the collar vertex must be a child of Dog in the mapping.
*
* @return Map where key is any vertex, and the value is the parent that vertex must map to
*/
private Map getFixedParentRestrictions(SchemaGraph sourceGraph,
List fixedSourceVertices,
Map fixedOneToOneMappings) {
Assert.assertFalse(fixedOneToOneMappings.isEmpty());
List needsFixing = new ArrayList<>(sourceGraph.getVertices());
needsFixing.removeAll(fixedSourceVertices);
Map restrictions = new LinkedHashMap<>();
for (Vertex vertex : needsFixing) {
if (hasParentRestrictions(vertex)) {
Vertex sourceParent = sourceGraph.getSingleAdjacentInverseVertex(vertex);
Vertex fixedTargetParent = fixedOneToOneMappings.get(sourceParent);
if (fixedTargetParent != null) {
for (Edge edge : sourceGraph.getAdjacentEdgesNonCopy(sourceParent)) {
Vertex sibling = edge.getTo();
if (hasParentRestrictions(sibling)) {
restrictions.put(sibling, fixedTargetParent);
}
}
}
}
}
return restrictions;
}
/**
* This computes the initial set of parent restrictions based on the given non-fixed mapping.
*
* i.e. this introduces restrictions as the {@link Mapping} is being built, as decisions
* can have knock on effects on other vertices' possible mappings.
*
* See {@link Mapping} for definition of fixed vs non-fixed.
*
* If a {@link Vertex} is present in the output {@link Map} then the value is the parent the
* vertex MUST map to.
*
* e.g. for an output {collar: Dog} then the collar vertex must be a child of Dog in the mapping.
*
* @param mapping the mapping to get non-fixed parent restrictions for
* @param sourceGraph the source graph
* @param targetGraph the target graph
* @return Map where key is any vertex, and the value is the parent that vertex must map to
*/
public Map getNonFixedParentRestrictions(SchemaGraph sourceGraph,
SchemaGraph targetGraph,
Mapping mapping) {
Map restrictions = new LinkedHashMap<>();
mapping.forEachNonFixedSourceAndTarget((source, target) -> {
if (hasChildrenRestrictions(source) && hasChildrenRestrictions(target)) {
for (Edge edge : sourceGraph.getAdjacentEdgesNonCopy(source)) {
Vertex child = edge.getTo();
if (hasParentRestrictions(child)) {
restrictions.put(child, target);
}
}
} else if (hasParentRestrictions(source) && hasParentRestrictions(target)) {
Vertex sourceParent = sourceGraph.getSingleAdjacentInverseVertex(source);
Vertex targetParent = targetGraph.getSingleAdjacentInverseVertex(target);
for (Edge edge : sourceGraph.getAdjacentEdgesNonCopy(sourceParent)) {
Vertex sibling = edge.getTo();
if (hasParentRestrictions(sibling)) {
restrictions.put(sibling, targetParent);
}
}
}
});
return restrictions;
}
public static boolean hasParentRestrictions(Vertex vertex) {
return vertex.isOfType(SchemaGraph.FIELD)
|| vertex.isOfType(SchemaGraph.INPUT_FIELD)
|| vertex.isOfType(SchemaGraph.ENUM_VALUE)
|| vertex.isOfType(SchemaGraph.ARGUMENT);
}
public static boolean hasChildrenRestrictions(Vertex vertex) {
return vertex.isOfType(SchemaGraph.INPUT_OBJECT)
|| vertex.isOfType(SchemaGraph.OBJECT)
|| vertex.isOfType(SchemaGraph.ENUM);
}
}