TypeDefinitionToDataModelMapper.java 9.2 KB
Newer Older
1 2
package com.kobylynskyi.graphql.codegen.mapper;

3
import com.kobylynskyi.graphql.codegen.model.MappingContext;
4
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
5
import com.kobylynskyi.graphql.codegen.model.ProjectionParameterDefinition;
6 7 8 9
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDocument;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedInterfaceTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedUnionTypeDefinition;
10
import com.kobylynskyi.graphql.codegen.utils.Utils;
11
import graphql.language.TypeName;
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

import java.util.*;
import java.util.stream.Collectors;

import static com.kobylynskyi.graphql.codegen.model.DataModelFields.*;

/**
 * Map type definition to a Freemarker data model
 *
 * @author kobylynskyi
 */
public class TypeDefinitionToDataModelMapper {

    /**
     * Map type definition to a Freemarker data model
     *
28 29
     * @param mappingContext Global mapping context
     * @param definition     Definition of object type including base definition and its extensions
30 31
     * @return Freemarker data model of the GraphQL type
     */
32 33 34 35
    public static Map<String, Object> map(MappingContext mappingContext,
                                          ExtendedObjectTypeDefinition definition) {
        ExtendedDocument document = mappingContext.getDocument();

36
        Map<String, Object> dataModel = new HashMap<>();
37
        // type/enum/input/interface/union classes do not require any imports
38 39
        dataModel.put(PACKAGE, MapperUtils.getModelPackageName(mappingContext));
        dataModel.put(CLASS_NAME, MapperUtils.getClassNameWithPrefixAndSuffix(mappingContext, definition));
40
        dataModel.put(JAVA_DOC, definition.getJavaDoc());
41 42 43 44 45 46
        dataModel.put(IMPLEMENTS, getInterfaces(mappingContext, definition));
        dataModel.put(FIELDS, getFields(mappingContext, definition, document));
        dataModel.put(BUILDER, mappingContext.getGenerateBuilder());
        dataModel.put(EQUALS_AND_HASH_CODE, mappingContext.getGenerateEqualsAndHashCode());
        dataModel.put(TO_STRING, mappingContext.getGenerateToString());
        dataModel.put(TO_STRING_FOR_REQUEST, mappingContext.getGenerateRequests());
47 48
        return dataModel;
    }
A
Alberto Valiña 已提交
49

50 51 52
    /**
     * Map type definition to a Freemarker data model of Response Projection.
     *
53
     * @param mappingContext Global mapping context
54 55 56
     * @param typeDefinition GraphQL type definition
     * @return Freemarker data model of the GraphQL Response Projection
     */
57 58
    public static Map<String, Object> mapResponseProjection(MappingContext mappingContext,
                                                            ExtendedObjectTypeDefinition typeDefinition) {
59
        Map<String, Object> dataModel = new HashMap<>();
60
        // ResponseProjection classes are sharing the package with the model classes, so no imports are needed
61 62
        dataModel.put(PACKAGE, MapperUtils.getModelPackageName(mappingContext));
        dataModel.put(CLASS_NAME, Utils.capitalize(typeDefinition.getName()) + mappingContext.getResponseProjectionSuffix());
63
        dataModel.put(JAVA_DOC, Collections.singletonList("Response projection for " + typeDefinition.getName()));
64 65 66
        dataModel.put(FIELDS, getProjectionFields(mappingContext, typeDefinition));
        dataModel.put(BUILDER, mappingContext.getGenerateBuilder());
        dataModel.put(EQUALS_AND_HASH_CODE, mappingContext.getGenerateEqualsAndHashCode());
67
        // dataModel.put(TO_STRING, mappingConfig.getGenerateToString()); always generated for serialization purposes
68 69 70
        return dataModel;
    }

71 72 73
    /**
     * Get merged attributes from the type and attributes from the interface.
     *
74
     * @param mappingContext Global mapping context
75 76 77 78
     * @param typeDefinition GraphQL type definition
     * @param document       Parent GraphQL document
     * @return Freemarker data model of the GraphQL type
     */
79
    private static Collection<ParameterDefinition> getFields(MappingContext mappingContext,
80 81 82 83 84 85
                                                             ExtendedObjectTypeDefinition typeDefinition,
                                                             ExtendedDocument document) {
        // using the map to exclude duplicate fields from the type and interfaces
        Map<String, ParameterDefinition> allParameters = new LinkedHashMap<>();

        // includes parameters from the base definition and extensions
86
        FieldDefinitionToParameterMapper.mapFields(mappingContext, typeDefinition.getFieldDefinitions(), typeDefinition.getName())
87 88 89
                .forEach(p -> allParameters.put(p.getName(), p));
        // includes parameters from the interface
        getInterfacesOfType(typeDefinition, document).stream()
90
                .map(i -> FieldDefinitionToParameterMapper.mapFields(mappingContext, i.getFieldDefinitions(), i.getName()))
91
                .flatMap(Collection::stream)
92 93 94
                .forEach(paramDef -> allParameters.merge(paramDef.getName(), paramDef, TypeDefinitionToDataModelMapper::merge));
        return allParameters.values();
    }
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
    /**
     * Merge parameter definition data from the type and interface
     * Annotations from the type have higher precedence
     *
     * @param typeDef      Definition of the same parameter from the type
     * @param interfaceDef Definition of the same parameter from the interface
     * @return merged parameter definition
     */
    private static ParameterDefinition merge(ParameterDefinition typeDef, ParameterDefinition interfaceDef) {
        if (Utils.isEmpty(typeDef.getAnnotations())) {
            typeDef.setAnnotations(interfaceDef.getAnnotations());
        }
        if (Utils.isEmpty(typeDef.getJavaDoc())) {
            typeDef.setJavaDoc(interfaceDef.getJavaDoc());
        }
        return typeDef;
112 113 114 115 116
    }

    /**
     * Get merged attributes from the type and attributes from the interface.
     *
117
     * @param mappingContext Global mapping context
118 119 120
     * @param typeDefinition GraphQL type definition
     * @return Freemarker data model of the GraphQL type
     */
121 122
    private static Collection<ProjectionParameterDefinition> getProjectionFields(MappingContext mappingContext,
                                                                                 ExtendedObjectTypeDefinition typeDefinition) {
123 124 125 126
        // using the map to exclude duplicate fields from the type and interfaces
        Map<String, ProjectionParameterDefinition> allParameters = new LinkedHashMap<>();

        // includes parameters from the base definition and extensions
127
        FieldDefinitionToParameterMapper.mapProjectionFields(mappingContext, typeDefinition.getFieldDefinitions())
128 129
                .forEach(p -> allParameters.put(p.getName(), p));
        // includes parameters from the interface
130 131
        getInterfacesOfType(typeDefinition, mappingContext.getDocument()).stream()
                .map(i -> FieldDefinitionToParameterMapper.mapProjectionFields(mappingContext, i.getFieldDefinitions()))
132
                .flatMap(Collection::stream)
133 134 135
                .filter(paramDef -> !allParameters.containsKey(paramDef.getName()))
                .forEach(paramDef -> allParameters.put(paramDef.getName(), paramDef));
        return allParameters.values();
136 137
    }

138 139 140
    /**
     * Scan document and return all interfaces that given type implements.
     *
141 142
     * @param definition GraphQL type definition
     * @param document   GraphQL document
143 144
     * @return all interfaces that given type implements.
     */
145 146
    private static List<ExtendedInterfaceTypeDefinition> getInterfacesOfType(ExtendedObjectTypeDefinition definition,
                                                                             ExtendedDocument document) {
147 148 149
        if (definition.getImplements().isEmpty()) {
            return Collections.emptyList();
        }
150 151 152 153 154
        Set<String> typeImplements = definition.getImplements()
                .stream()
                .filter(type -> TypeName.class.isAssignableFrom(type.getClass()))
                .map(TypeName.class::cast)
                .map(TypeName::getName)
155
                .collect(Collectors.toSet());
156 157
        return document.getInterfaceDefinitions()
                .stream()
158 159 160 161
                .filter(def -> typeImplements.contains(def.getName()))
                .collect(Collectors.toList());
    }

162 163 164
    private static Set<String> getInterfaces(MappingContext mappingContext,
                                             ExtendedObjectTypeDefinition definition) {
        List<String> unionsNames = mappingContext.getDocument().getUnionDefinitions()
165 166 167
                .stream()
                .filter(union -> union.isDefinitionPartOfUnion(definition))
                .map(ExtendedUnionTypeDefinition::getName)
168
                .map(unionName -> MapperUtils.getClassNameWithPrefixAndSuffix(mappingContext, unionName))
169 170 171
                .collect(Collectors.toList());
        Set<String> interfaceNames = definition.getImplements()
                .stream()
172
                .map(anImplement -> GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext, anImplement))
173 174 175 176 177
                .collect(Collectors.toSet());

        Set<String> allInterfaces = new LinkedHashSet<>();
        allInterfaces.addAll(unionsNames);
        allInterfaces.addAll(interfaceNames);
178 179 180
        return allInterfaces;
    }

181
}