graphql.normalized.ENFMerger Maven / Gradle / Ivy
package graphql.normalized;
import graphql.Internal;
import graphql.introspection.Introspection;
import graphql.language.Argument;
import graphql.language.AstComparator;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@Internal
public class ENFMerger {
public static void merge(
ExecutableNormalizedField parent,
List childrenWithSameResultKey,
GraphQLSchema schema,
boolean deferSupport
) {
// they have all the same result key
// we can only merge the fields if they have the same field name + arguments + all children are the same
List> possibleGroupsToMerge = new ArrayList<>();
for (ExecutableNormalizedField field : childrenWithSameResultKey) {
boolean addToGroup = false;
overPossibleGroups:
for (Set group : possibleGroupsToMerge) {
for (ExecutableNormalizedField fieldInGroup : group) {
if (field.getFieldName().equals(Introspection.TypeNameMetaFieldDef.getName())) {
addToGroup = true;
group.add(field);
continue overPossibleGroups;
}
if (field.getFieldName().equals(fieldInGroup.getFieldName()) &&
sameArguments(field.getAstArguments(), fieldInGroup.getAstArguments())
&& isFieldInSharedInterface(field, fieldInGroup, schema)
) {
addToGroup = true;
group.add(field);
continue overPossibleGroups;
}
}
}
if (!addToGroup) {
LinkedHashSet group = new LinkedHashSet<>();
group.add(field);
possibleGroupsToMerge.add(group);
}
}
for (Set groupOfFields : possibleGroupsToMerge) {
// for each group we check if it could be merged
List> listOfChildrenForGroup = new ArrayList<>();
for (ExecutableNormalizedField fieldInGroup : groupOfFields) {
Set childrenSets = new LinkedHashSet<>(fieldInGroup.getChildren());
listOfChildrenForGroup.add(childrenSets);
}
boolean mergeable = areFieldSetsTheSame(listOfChildrenForGroup);
if (mergeable) {
Set mergedObjects = new LinkedHashSet<>();
groupOfFields.forEach(f -> mergedObjects.addAll(f.getObjectTypeNames()));
// patching the first one to contain more objects, remove all others
Iterator iterator = groupOfFields.iterator();
ExecutableNormalizedField first = iterator.next();
while (iterator.hasNext()) {
ExecutableNormalizedField next = iterator.next();
parent.getChildren().remove(next);
if (deferSupport) {
// Move defer executions from removed field into the merged field's entry
first.addDeferredExecutions(next.getDeferredExecutions());
}
}
first.setObjectTypeNames(mergedObjects);
}
}
}
private static boolean isFieldInSharedInterface(ExecutableNormalizedField fieldOne, ExecutableNormalizedField fieldTwo, GraphQLSchema schema) {
/*
* we can get away with only checking one of the object names, because all object names in one ENF are guaranteed to be the same field.
* This comes from how the ENFs are created in the factory before.
*/
String firstObject = fieldOne.getSingleObjectTypeName();
String secondObject = fieldTwo.getSingleObjectTypeName();
// we know that the field names are the same, therefore we can just take the first one
String fieldName = fieldOne.getFieldName();
GraphQLObjectType objectTypeOne = schema.getObjectType(firstObject);
GraphQLObjectType objectTypeTwo = schema.getObjectType(secondObject);
List interfacesOne = (List) objectTypeOne.getInterfaces();
List interfacesTwo = (List) objectTypeTwo.getInterfaces();
Optional firstInterfaceFound = interfacesOne.stream().filter(singleInterface -> singleInterface.getFieldDefinition(fieldName) != null).findFirst();
Optional secondInterfaceFound = interfacesTwo.stream().filter(singleInterface -> singleInterface.getFieldDefinition(fieldName) != null).findFirst();
if (!firstInterfaceFound.isPresent() || !secondInterfaceFound.isPresent()) {
return false;
}
return firstInterfaceFound.get().getName().equals(secondInterfaceFound.get().getName());
}
private static boolean areFieldSetsTheSame(List> listOfSets) {
if (listOfSets.size() == 0 || listOfSets.size() == 1) {
return true;
}
Set first = listOfSets.get(0);
Iterator> iterator = listOfSets.iterator();
iterator.next();
while (iterator.hasNext()) {
Set set = iterator.next();
if (!compareTwoFieldSets(first, set)) {
return false;
}
}
List> nextLevel = new ArrayList<>();
for (Set set : listOfSets) {
for (ExecutableNormalizedField fieldInSet : set) {
nextLevel.add(new LinkedHashSet<>(fieldInSet.getChildren()));
}
}
return areFieldSetsTheSame(nextLevel);
}
private static boolean compareTwoFieldSets(Set setOne, Set setTwo) {
if (setOne.size() != setTwo.size()) {
return false;
}
for (ExecutableNormalizedField field : setOne) {
if (!isContained(field, setTwo)) {
return false;
}
}
return true;
}
private static boolean isContained(ExecutableNormalizedField searchFor, Set set) {
for (ExecutableNormalizedField field : set) {
if (compareWithoutChildren(searchFor, field)) {
return true;
}
}
return false;
}
private static boolean compareWithoutChildren(ExecutableNormalizedField one, ExecutableNormalizedField two) {
if (!one.getObjectTypeNames().equals(two.getObjectTypeNames())) {
return false;
}
if (!Objects.equals(one.getAlias(), two.getAlias())) {
return false;
}
if (!Objects.equals(one.getFieldName(), two.getFieldName())) {
return false;
}
if (!sameArguments(one.getAstArguments(), two.getAstArguments())) {
return false;
}
return true;
}
// copied from graphql.validation.rules.OverlappingFieldsCanBeMerged
private static boolean sameArguments(List arguments1, List arguments2) {
if (arguments1.size() != arguments2.size()) {
return false;
}
for (Argument argument : arguments1) {
Argument matchedArgument = findArgumentByName(argument.getName(), arguments2);
if (matchedArgument == null) {
return false;
}
if (!AstComparator.sameValue(argument.getValue(), matchedArgument.getValue())) {
return false;
}
}
return true;
}
private static Argument findArgumentByName(String name, List arguments) {
for (Argument argument : arguments) {
if (argument.getName().equals(name)) {
return argument;
}
}
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy