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

import com.kobylynskyi.graphql.codegen.model.MappingConfig;
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 30
     * @param mappingConfig Global mapping configuration
     * @param definition    Definition of object type including base definition and its extensions
     * @param document      GraphQL Document
31 32
     * @return Freemarker data model of the GraphQL type
     */
33 34
    public static Map<String, Object> map(MappingConfig mappingConfig,
                                          ExtendedObjectTypeDefinition definition,
35
                                          ExtendedDocument document) {
36
        Map<String, Object> dataModel = new HashMap<>();
37 38
        // type/enum/input/interface/union classes do not require any imports
        dataModel.put(PACKAGE, MapperUtils.getModelPackageName(mappingConfig));
39
        dataModel.put(CLASS_NAME, MapperUtils.getClassNameWithPrefixAndSuffix(mappingConfig, definition));
40
        dataModel.put(JAVA_DOC, definition.getJavaDoc());
41 42
        dataModel.put(IMPLEMENTS, getInterfaces(mappingConfig, definition, document));
        dataModel.put(FIELDS, getFields(mappingConfig, definition, document));
43
        dataModel.put(BUILDER, mappingConfig.getGenerateBuilder());
A
Alberto Valiña 已提交
44 45
        dataModel.put(EQUALS_AND_HASH_CODE, mappingConfig.getGenerateEqualsAndHashCode());
        dataModel.put(TO_STRING, mappingConfig.getGenerateToString());
46
        dataModel.put(TO_STRING_FOR_REQUEST, mappingConfig.getGenerateRequests());
47 48
        return dataModel;
    }
A
Alberto Valiña 已提交
49

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

75 76 77 78 79 80 81 82
    /**
     * Get merged attributes from the type and attributes from the interface.
     *
     * @param mappingConfig  Global mapping configuration
     * @param typeDefinition GraphQL type definition
     * @param document       Parent GraphQL document
     * @return Freemarker data model of the GraphQL type
     */
83 84 85 86 87 88 89 90 91 92 93
    private static Collection<ParameterDefinition> getFields(MappingConfig mappingConfig,
                                                             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
        FieldDefinitionToParameterMapper.mapFields(mappingConfig, typeDefinition.getFieldDefinitions(), typeDefinition.getName())
                .forEach(p -> allParameters.put(p.getName(), p));
        // includes parameters from the interface
        getInterfacesOfType(typeDefinition, document).stream()
94 95
                .map(i -> FieldDefinitionToParameterMapper.mapFields(mappingConfig, i.getFieldDefinitions(), i.getName()))
                .flatMap(Collection::stream)
96 97 98
                .forEach(paramDef -> allParameters.merge(paramDef.getName(), paramDef, TypeDefinitionToDataModelMapper::merge));
        return allParameters.values();
    }
99

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    /**
     * 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;
116 117 118 119 120 121 122 123 124 125 126
    }

    /**
     * Get merged attributes from the type and attributes from the interface.
     *
     * @param mappingConfig  Global mapping configuration
     * @param typeDefinition GraphQL type definition
     * @param document       Parent GraphQL document
     * @param typeNames      Names of all GraphQL types
     * @return Freemarker data model of the GraphQL type
     */
127 128 129 130 131 132 133 134 135 136 137 138
    private static Collection<ProjectionParameterDefinition> getProjectionFields(MappingConfig mappingConfig,
                                                                                 ExtendedObjectTypeDefinition typeDefinition,
                                                                                 ExtendedDocument document,
                                                                                 Set<String> typeNames) {
        // 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
        FieldDefinitionToParameterMapper.mapProjectionFields(mappingConfig, typeDefinition.getFieldDefinitions(), typeNames)
                .forEach(p -> allParameters.put(p.getName(), p));
        // includes parameters from the interface
        getInterfacesOfType(typeDefinition, document).stream()
139
                .map(i -> FieldDefinitionToParameterMapper.mapProjectionFields(mappingConfig, i.getFieldDefinitions(), typeNames))
140
                .flatMap(Collection::stream)
141 142 143
                .filter(paramDef -> !allParameters.containsKey(paramDef.getName()))
                .forEach(paramDef -> allParameters.put(paramDef.getName(), paramDef));
        return allParameters.values();
144 145
    }

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

170 171 172 173 174 175 176 177 178 179 180
    private static Set<String> getInterfaces(MappingConfig mappingConfig,
                                             ExtendedObjectTypeDefinition definition,
                                             ExtendedDocument document) {
        List<String> unionsNames = document.getUnionDefinitions()
                .stream()
                .filter(union -> union.isDefinitionPartOfUnion(definition))
                .map(ExtendedUnionTypeDefinition::getName)
                .map(unionName -> MapperUtils.getClassNameWithPrefixAndSuffix(mappingConfig, unionName))
                .collect(Collectors.toList());
        Set<String> interfaceNames = definition.getImplements()
                .stream()
181
                .map(anImplement -> GraphqlTypeToJavaTypeMapper.getJavaType(mappingConfig, anImplement))
182 183 184 185 186
                .collect(Collectors.toSet());

        Set<String> allInterfaces = new LinkedHashSet<>();
        allInterfaces.addAll(unionsNames);
        allInterfaces.addAll(interfaceNames);
187 188 189
        return allInterfaces;
    }

190
}