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

graphql.schema.DataFetchingFieldSelectionSetImpl Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.schema;

import com.google.common.collect.ImmutableList;
import graphql.Internal;
import graphql.normalized.NormalizedField;

import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static graphql.Assert.assertNotNull;
import static graphql.util.FpKit.newList;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

@Internal
public class DataFetchingFieldSelectionSetImpl implements DataFetchingFieldSelectionSet {

    private final static String SEP = "/";

    private final static DataFetchingFieldSelectionSet NOOP = new DataFetchingFieldSelectionSet() {

        @Override
        public boolean contains(String fieldGlobPattern) {
            return false;
        }

        @Override
        public boolean containsAnyOf(String fieldGlobPattern, String... fieldGlobPatterns) {
            return false;
        }

        @Override
        public boolean containsAllOf(String fieldGlobPattern, String... fieldGlobPatterns) {
            return false;
        }


        @Override
        public List getFields() {
            return emptyList();
        }

        @Override
        public List getImmediateFields() {
            return emptyList();
        }

        @Override
        public List getFields(String fieldGlobPattern, String... fieldGlobPatterns) {
            return Collections.emptyList();
        }

        @Override
        public Map> getFieldsGroupedByResultKey() {
            return Collections.emptyMap();
        }

        @Override
        public Map> getFieldsGroupedByResultKey(String fieldGlobPattern, String... fieldGlobPatterns) {
            return Collections.emptyMap();
        }
    };

    public static DataFetchingFieldSelectionSet newCollector(GraphQLOutputType fieldType, Supplier normalizedFieldSupplier) {
        if (!GraphQLTypeUtil.isLeaf(fieldType)) {
            return new DataFetchingFieldSelectionSetImpl(normalizedFieldSupplier);
        } else {
            // we can only collect fields on object types and interfaces and unions.
            return NOOP;
        }
    }

    private final Supplier normalizedFieldSupplier;

    private volatile boolean computedValues;
    private List immediateFields;
    private Map> normalisedSelectionSetFields;
    private Set flattenedFieldsForGlobSearching;

    private DataFetchingFieldSelectionSetImpl(Supplier normalizedFieldSupplier) {
        this.normalizedFieldSupplier = normalizedFieldSupplier;
        this.computedValues = false;
    }

    @Override
    public boolean contains(String fieldGlobPattern) {
        if (fieldGlobPattern == null || fieldGlobPattern.isEmpty()) {
            return false;
        }
        computeValuesLazily();
        fieldGlobPattern = removeLeadingSlash(fieldGlobPattern);
        PathMatcher globMatcher = globMatcher(fieldGlobPattern);
        for (String flattenedField : flattenedFieldsForGlobSearching) {
            Path path = Paths.get(flattenedField);
            if (globMatcher.matches(path)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean containsAnyOf(String fieldGlobPattern, String... fieldGlobPatterns) {
        assertNotNull(fieldGlobPattern);
        assertNotNull(fieldGlobPatterns);
        for (String globPattern : mkIterable(fieldGlobPattern, fieldGlobPatterns)) {
            if (contains(globPattern)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean containsAllOf(String fieldGlobPattern, String... fieldGlobPatterns) {
        assertNotNull(fieldGlobPattern);
        assertNotNull(fieldGlobPatterns);
        for (String globPattern : mkIterable(fieldGlobPattern, fieldGlobPatterns)) {
            if (!contains(globPattern)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public List getFields(String fieldGlobPattern, String... fieldGlobPatterns) {
        if (fieldGlobPattern == null || fieldGlobPattern.isEmpty()) {
            return emptyList();
        }
        computeValuesLazily();

        List targetNames = new ArrayList<>();
        for (String flattenedField : flattenedFieldsForGlobSearching) {
            for (String globPattern : mkIterable(fieldGlobPattern, fieldGlobPatterns)) {
                PathMatcher globMatcher = globMatcher(globPattern);
                Path path = Paths.get(flattenedField);
                if (globMatcher.matches(path)) {
                    targetNames.add(flattenedField);
                }
            }
        }

        return targetNames.stream()
                .flatMap(name -> normalisedSelectionSetFields.getOrDefault(name, Collections.emptyList()).stream())
                .collect(ImmutableList.toImmutableList());
    }

    @Override
    public List getFields() {
        computeValuesLazily();
        return normalisedSelectionSetFields.values().stream()
                .flatMap(Collection::stream)
                .collect(toList());
    }

    @Override
    public List getImmediateFields() {
        computeValuesLazily();
        return immediateFields;
    }

    @Override
    public Map> getFieldsGroupedByResultKey() {
        return getFields().stream().collect(Collectors.groupingBy(SelectedField::getResultKey));
    }

    @Override
    public Map> getFieldsGroupedByResultKey(String fieldGlobPattern, String... fieldGlobPatterns) {
        return getFields(fieldGlobPattern, fieldGlobPatterns).stream().collect(Collectors.groupingBy(SelectedField::getResultKey));
    }

    private void computeValuesLazily() {
        if (computedValues) {
            return;
        }
        // this supplier is a once only thread synced call - so do it outside this lock
        // if only to have only 1 lock in action at a time
        NormalizedField currentNormalisedField = normalizedFieldSupplier.get();
        synchronized (this) {
            if (computedValues) {
                return;
            }
            flattenedFieldsForGlobSearching = new LinkedHashSet<>();
            normalisedSelectionSetFields = new LinkedHashMap<>();
            immediateFields = new ArrayList<>();
            traverseSubSelectedFields(currentNormalisedField, "", "", true);
            computedValues = true;
        }
    }


    private void traverseSubSelectedFields(NormalizedField currentNormalisedField, String qualifiedFieldPrefix, String simpleFieldPrefix, boolean firstLevel) {
        List children = currentNormalisedField.getChildren();
        for (NormalizedField normalizedSubSelectedField : children) {

            String typeQualifiedName = mkTypeQualifiedName(normalizedSubSelectedField);
            String simpleName = normalizedSubSelectedField.getName();

            String globQualifiedName = mkFieldGlobName(qualifiedFieldPrefix, typeQualifiedName);
            String globSimpleName = mkFieldGlobName(simpleFieldPrefix, simpleName);

            flattenedFieldsForGlobSearching.add(globQualifiedName);
            // put in entries for the simple names - eg `Invoice.payments/Payment.amount` becomes `payments/amount`
            flattenedFieldsForGlobSearching.add(globSimpleName);

            SelectedFieldImpl selectedField = new SelectedFieldImpl(globSimpleName, globQualifiedName, normalizedSubSelectedField);
            if (firstLevel) {
                immediateFields.add(selectedField);
            }
            normalisedSelectionSetFields.computeIfAbsent(globQualifiedName, newList()).add(selectedField);
            normalisedSelectionSetFields.computeIfAbsent(globSimpleName, newList()).add(selectedField);

            GraphQLFieldDefinition fieldDefinition = normalizedSubSelectedField.getFieldDefinition();
            GraphQLType unwrappedType = GraphQLTypeUtil.unwrapAll(fieldDefinition.getType());
            if (!GraphQLTypeUtil.isLeaf(unwrappedType)) {
                traverseSubSelectedFields(normalizedSubSelectedField, globQualifiedName, globSimpleName, false);
            }
        }
    }


    private String removeLeadingSlash(String fieldGlobPattern) {
        if (fieldGlobPattern.startsWith(SEP)) {
            fieldGlobPattern = fieldGlobPattern.substring(1);
        }
        return fieldGlobPattern;
    }

    private static String mkTypeQualifiedName(NormalizedField normalizedField) {
        return normalizedField.getObjectType().getName() + "." + normalizedField.getName();
    }

    private static String mkFieldGlobName(String fieldPrefix, String fieldName) {
        return (!fieldPrefix.isEmpty() ? fieldPrefix + SEP : "") + fieldName;
    }

    private static PathMatcher globMatcher(String fieldGlobPattern) {
        return FileSystems.getDefault().getPathMatcher("glob:" + fieldGlobPattern);
    }

    private List mkIterable(String fieldGlobPattern, String[] fieldGlobPatterns) {
        List l = new ArrayList<>();
        l.add(fieldGlobPattern);
        Collections.addAll(l, fieldGlobPatterns);
        return l;
    }

    @Override
    public String toString() {
        if (!computedValues) {
            return "notComputed";
        }
        return String.join("\n", flattenedFieldsForGlobSearching);
    }

    private static class SelectedFieldImpl implements SelectedField {

        private final String qualifiedName;
        private final String fullyQualifiedName;
        private final DataFetchingFieldSelectionSet selectionSet;
        private final NormalizedField normalizedField;

        private SelectedFieldImpl(String simpleQualifiedName, String fullyQualifiedName, NormalizedField normalizedField) {
            this.qualifiedName = simpleQualifiedName;
            this.fullyQualifiedName = fullyQualifiedName;
            this.normalizedField = normalizedField;
            this.selectionSet = new DataFetchingFieldSelectionSetImpl(() -> normalizedField);
        }

        private SelectedField mkParent(NormalizedField normalizedField) {
            String parentSimpleQualifiedName = beforeLastSlash(qualifiedName);
            String parentFullyQualifiedName = beforeLastSlash(fullyQualifiedName);
            return normalizedField.getParent() == null ? null :
                    new SelectedFieldImpl(parentSimpleQualifiedName, parentFullyQualifiedName, normalizedField.getParent());
        }

        private String beforeLastSlash(String name) {
            int index = name.lastIndexOf("/");
            if (index > 0) {
                return name.substring(0, index);
            }
            return "";
        }

        @Override
        public String getName() {
            return normalizedField.getName();
        }

        @Override
        public String getQualifiedName() {
            return qualifiedName;
        }

        @Override
        public String getFullyQualifiedName() {
            return fullyQualifiedName;
        }

        @Override
        public GraphQLObjectType getObjectType() {
            return normalizedField.getObjectType();
        }

        @Override
        public GraphQLFieldDefinition getFieldDefinition() {
            return normalizedField.getFieldDefinition();
        }

        @Override
        public Map getArguments() {
            return normalizedField.getArguments();
        }

        @Override
        public int getLevel() {
            return normalizedField.getLevel();
        }

        @Override
        public boolean isConditional() {
            return normalizedField.isConditional();
        }

        @Override
        public String getAlias() {
            return normalizedField.getAlias();
        }

        @Override
        public String getResultKey() {
            return normalizedField.getResultKey();
        }

        @Override
        public SelectedField getParentField() {
            // lazy
            return mkParent(normalizedField);
        }

        @Override
        public DataFetchingFieldSelectionSet getSelectionSet() {
            return selectionSet;
        }

        @Override
        public String toString() {
            return getQualifiedName();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy