TypeDefinitionToDataModelMapper.java 11.0 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
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDocument;
7
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
8 9 10
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedInterfaceTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedUnionTypeDefinition;
11
import com.kobylynskyi.graphql.codegen.utils.Utils;
12
import graphql.language.TypeName;
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

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

37
        Map<String, Object> dataModel = new HashMap<>();
38
        // type/enum/input/interface/union classes do not require any imports
39 40
        dataModel.put(PACKAGE, MapperUtils.getModelPackageName(mappingContext));
        dataModel.put(CLASS_NAME, MapperUtils.getClassNameWithPrefixAndSuffix(mappingContext, definition));
41
        dataModel.put(JAVA_DOC, definition.getJavaDoc());
42 43 44 45 46 47
        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());
48 49
        return dataModel;
    }
A
Alberto Valiña 已提交
50

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

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    /**
     * Map field definition to a Freemarker data model of Parametrized Input.
     *
     * @param mappingContext       Global mapping context
     * @param fieldDefinition      GraphQL field definition
     * @param parentTypeDefinition GraphQL parent type definition
     * @return Freemarker data model of the GraphQL Parametrized Input
     */
    public static Map<String, Object> mapParametrizedInput(MappingContext mappingContext,
                                                           ExtendedFieldDefinition fieldDefinition,
                                                           ExtendedObjectTypeDefinition parentTypeDefinition) {
        Map<String, Object> dataModel = new HashMap<>();
        // ParametrizedInput classes are sharing the package with the model classes, so no imports are needed
        dataModel.put(PACKAGE, MapperUtils.getModelPackageName(mappingContext));
        dataModel.put(CLASS_NAME, MapperUtils.getParametrizedInputClassName(mappingContext, fieldDefinition, parentTypeDefinition));
        dataModel.put(JAVA_DOC, Collections.singletonList(String.format("Parametrized input for field %s in type %s",
                fieldDefinition.getName(), parentTypeDefinition.getName())));
        dataModel.put(FIELDS, InputValueDefinitionToParameterMapper.map(
                mappingContext, fieldDefinition.getInputValueDefinitions(), parentTypeDefinition.getName()));
        dataModel.put(BUILDER, mappingContext.getGenerateBuilder());
        dataModel.put(EQUALS_AND_HASH_CODE, mappingContext.getGenerateEqualsAndHashCode());
        // dataModel.put(TO_STRING, mappingConfig.getGenerateToString()); always generated for serialization purposes
        return dataModel;
    }

97 98 99
    /**
     * Get merged attributes from the type and attributes from the interface.
     *
100
     * @param mappingContext Global mapping context
101 102 103 104
     * @param typeDefinition GraphQL type definition
     * @param document       Parent GraphQL document
     * @return Freemarker data model of the GraphQL type
     */
105
    private static Collection<ParameterDefinition> getFields(MappingContext mappingContext,
106 107 108 109 110 111
                                                             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
112
        FieldDefinitionToParameterMapper.mapFields(mappingContext, typeDefinition.getFieldDefinitions(), typeDefinition.getName())
113 114 115
                .forEach(p -> allParameters.put(p.getName(), p));
        // includes parameters from the interface
        getInterfacesOfType(typeDefinition, document).stream()
116
                .map(i -> FieldDefinitionToParameterMapper.mapFields(mappingContext, i.getFieldDefinitions(), i.getName()))
117
                .flatMap(Collection::stream)
118 119 120
                .forEach(paramDef -> allParameters.merge(paramDef.getName(), paramDef, TypeDefinitionToDataModelMapper::merge));
        return allParameters.values();
    }
121

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    /**
     * 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;
138 139 140 141 142
    }

    /**
     * Get merged attributes from the type and attributes from the interface.
     *
143
     * @param mappingContext Global mapping context
144 145 146
     * @param typeDefinition GraphQL type definition
     * @return Freemarker data model of the GraphQL type
     */
147 148
    private static Collection<ProjectionParameterDefinition> getProjectionFields(MappingContext mappingContext,
                                                                                 ExtendedObjectTypeDefinition typeDefinition) {
149 150 151 152
        // 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
153
        FieldDefinitionToParameterMapper.mapProjectionFields(mappingContext, typeDefinition.getFieldDefinitions(), typeDefinition)
154 155
                .forEach(p -> allParameters.put(p.getName(), p));
        // includes parameters from the interface
156
        getInterfacesOfType(typeDefinition, mappingContext.getDocument()).stream()
157
                .map(i -> FieldDefinitionToParameterMapper.mapProjectionFields(mappingContext, i.getFieldDefinitions(), i))
158
                .flatMap(Collection::stream)
159 160 161
                .filter(paramDef -> !allParameters.containsKey(paramDef.getName()))
                .forEach(paramDef -> allParameters.put(paramDef.getName(), paramDef));
        return allParameters.values();
162 163
    }

164 165 166
    /**
     * Scan document and return all interfaces that given type implements.
     *
167 168
     * @param definition GraphQL type definition
     * @param document   GraphQL document
169 170
     * @return all interfaces that given type implements.
     */
171 172
    private static List<ExtendedInterfaceTypeDefinition> getInterfacesOfType(ExtendedObjectTypeDefinition definition,
                                                                             ExtendedDocument document) {
173 174 175
        if (definition.getImplements().isEmpty()) {
            return Collections.emptyList();
        }
176 177 178 179 180
        Set<String> typeImplements = definition.getImplements()
                .stream()
                .filter(type -> TypeName.class.isAssignableFrom(type.getClass()))
                .map(TypeName.class::cast)
                .map(TypeName::getName)
181
                .collect(Collectors.toSet());
182 183
        return document.getInterfaceDefinitions()
                .stream()
184 185 186 187
                .filter(def -> typeImplements.contains(def.getName()))
                .collect(Collectors.toList());
    }

188 189 190
    private static Set<String> getInterfaces(MappingContext mappingContext,
                                             ExtendedObjectTypeDefinition definition) {
        List<String> unionsNames = mappingContext.getDocument().getUnionDefinitions()
191 192 193
                .stream()
                .filter(union -> union.isDefinitionPartOfUnion(definition))
                .map(ExtendedUnionTypeDefinition::getName)
194
                .map(unionName -> MapperUtils.getClassNameWithPrefixAndSuffix(mappingContext, unionName))
195 196 197
                .collect(Collectors.toList());
        Set<String> interfaceNames = definition.getImplements()
                .stream()
198
                .map(anImplement -> GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext, anImplement))
199 200 201 202 203
                .collect(Collectors.toSet());

        Set<String> allInterfaces = new LinkedHashSet<>();
        allInterfaces.addAll(unionsNames);
        allInterfaces.addAll(interfaceNames);
204 205 206
        return allInterfaces;
    }

207
}