From bde98b9fb2447eec3d4d1fb917188b251b956dfb Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Mon, 15 Mar 2021 20:25:11 +0000 Subject: [PATCH 1/3] Refactor the reactive and default code generation into separate Assemblers --- .../client/GraphQLClientProcessor.java | 3 +- .../{modules => code}/AbstractQueryStage.java | 41 +------ .../{modules => code}/AbstractStage.java | 31 +----- .../ArgumentAssemblyStage.java | 13 ++- .../graphql/client/code/Assembler.java | 90 +++++++++++++++ .../client/{ => code}/ClientGenerator.java | 65 ++++++----- .../graphql/client/code/MemberVariable.java | 15 +++ .../code/blocking/BlockingAssembler.java | 21 ++++ .../blocking}/BlockingQueryStage.java | 42 ++----- .../blocking}/OptionalReturnStage.java | 11 +- .../code/reactive/ReactiveAssembler.java | 21 ++++ .../code/reactive/ReactiveQueryStage.java | 32 ++++++ .../reactive}/ReactiveReturnStage.java | 15 ++- .../client/modules/ReactiveQueryStage.java | 56 ---------- .../graphql/client/query/QueryContext.java | 1 + .../graphql/client/query/QueryGenerator.java | 15 ++- .../selectors/DelegatingFieldSelector.java | 5 +- .../{modules => visitor}/ClientDetails.java | 2 +- .../client/visitor/ClientDetailsVisitor.java | 1 - .../modules/ArgumentAssemblyStageSpec.groovy | 7 +- .../client/modules/AssemblerSpec.groovy | 103 ++++++++++++++++++ .../client/modules/CodeBlockUtils.groovy | 8 +- .../BlockingQueryStageSpec.groovy | 44 +------- .../OptionalReturnStageSpec.groovy | 8 +- .../ReactiveQueryStageSpec.groovy | 41 +------ .../ReactiveReturnStageSpec.groovy | 6 +- .../query/QueryGeneratorSpec.groovy | 85 --------------- 27 files changed, 394 insertions(+), 388 deletions(-) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => code}/AbstractQueryStage.java (70%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => code}/AbstractStage.java (53%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => code}/ArgumentAssemblyStage.java (80%) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{ => code}/ClientGenerator.java (69%) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => code/blocking}/BlockingQueryStage.java (63%) rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => code/blocking}/OptionalReturnStage.java (81%) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => code/reactive}/ReactiveReturnStage.java (85%) delete mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java rename graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/{modules => visitor}/ClientDetails.java (86%) create mode 100644 graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/{ => blocking}/BlockingQueryStageSpec.groovy (62%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/{ => blocking}/OptionalReturnStageSpec.groovy (92%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/{ => reactive}/ReactiveQueryStageSpec.groovy (61%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/{ => reactive}/ReactiveReturnStageSpec.groovy (95%) delete mode 100644 graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java index 3af1a22..d62e5a1 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java @@ -2,6 +2,7 @@ import com.google.auto.service.AutoService; import com.jacobmountain.graphql.client.annotations.GraphQLClient; +import com.jacobmountain.graphql.client.code.ClientGenerator; import com.jacobmountain.graphql.client.exceptions.SchemaNotFoundException; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; @@ -87,7 +88,7 @@ private void generateJavaDataClasses(Input input) { private void generateClientImplementation(Input client) { GraphQLClient annotation = client.getAnnotation(); log.info("Generating java implementation of {}", client.element.getSimpleName()); - new ClientGenerator(this.filer, client.getTypeMapper(), client.getPackage(), client.getDtoPackage(), client.getSchema(), annotation.reactive()) + new ClientGenerator(this.filer, client.getTypeMapper(), client.getPackage(), client.getSchema(), annotation.reactive()) .generate(client.element, annotation.implSuffix()); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractQueryStage.java similarity index 70% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractQueryStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractQueryStage.java index 55fa6e3..c58fc6d 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractQueryStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractQueryStage.java @@ -1,4 +1,4 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code; import com.jacobmountain.graphql.client.TypeMapper; import com.jacobmountain.graphql.client.dto.Response; @@ -10,50 +10,21 @@ import com.squareup.javapoet.*; import graphql.language.ObjectTypeDefinition; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; public abstract class AbstractQueryStage extends AbstractStage { - protected final TypeName query; - - protected final TypeName mutation; - - protected final TypeName subscription; - private final QueryGenerator queryGenerator; - public AbstractQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper, String dtoPackageName) { + public AbstractQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper) { super(schema, typeMapper); - this.query = ClassName.get(dtoPackageName, schema.getQueryTypeName()); - this.mutation = schema.getMutationTypeName().map(it -> ClassName.get(dtoPackageName, it)).orElse(ClassName.get(Void.class)); - this.subscription = schema.getSubscriptionTypeName().map(it -> ClassName.get(dtoPackageName, it)).orElse(ClassName.get(Void.class)); this.queryGenerator = queryGenerator; } - protected TypeName getFetcherTypeName(Class fetcher) { - return ParameterizedTypeName.get( - ClassName.get(fetcher), - query, - mutation, - TypeVariableName.get("Error") - ); - } - - protected TypeName getSubscriberTypeName(Class fetcher) { - return ParameterizedTypeName.get( - ClassName.get(fetcher), - subscription, - TypeVariableName.get("Error") - ); - } - - - @Override - public List getTypeArguments() { - return Collections.singletonList("Error"); - } - protected TypeName getReturnTypeName(MethodDetails details) { ObjectTypeDefinition typeDefinition = getTypeDefinition(details); return ParameterizedTypeName.get( diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractStage.java similarity index 53% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractStage.java index c9ab140..0aa48fa 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/AbstractStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/AbstractStage.java @@ -1,17 +1,14 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code; import com.jacobmountain.graphql.client.TypeMapper; import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.TypeName; import graphql.language.ObjectTypeDefinition; -import lombok.Builder; import lombok.RequiredArgsConstructor; -import lombok.Value; -import java.util.Collections; -import java.util.List; +import java.util.Optional; @RequiredArgsConstructor public abstract class AbstractStage { @@ -20,17 +17,7 @@ public abstract class AbstractStage { protected final TypeMapper typeMapper; - public List getMemberVariables(ClientDetails details) { - return Collections.emptyList(); - } - - public List getTypeArguments() { - return Collections.emptyList(); - } - - public List assemble(ClientDetails client, MethodDetails method) { - return Collections.emptyList(); - } + public abstract Optional assemble(ClientDetails client, MethodDetails method); protected ObjectTypeDefinition getTypeDefinition(MethodDetails details) { if (details.isQuery()) { @@ -42,14 +29,4 @@ protected ObjectTypeDefinition getTypeDefinition(MethodDetails details) { } } - @Value - @Builder - public static class MemberVariable { - - String name; - - TypeName type; - - } - } \ No newline at end of file diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ArgumentAssemblyStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ArgumentAssemblyStage.java similarity index 80% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ArgumentAssemblyStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ArgumentAssemblyStage.java index 4e2d99c..439bfe3 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ArgumentAssemblyStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ArgumentAssemblyStage.java @@ -1,6 +1,7 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.jacobmountain.graphql.client.visitor.Parameter; import com.squareup.javapoet.ClassName; @@ -8,9 +9,9 @@ import com.squareup.javapoet.TypeName; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class ArgumentAssemblyStage extends AbstractStage { @@ -20,10 +21,10 @@ public ArgumentAssemblyStage() { } @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { List parameters = method.getParameters(); if (parameters.isEmpty()) { - return Collections.emptyList(); + return Optional.empty(); } List ret = new ArrayList<>(); TypeName type = ClassName.bestGuess(method.getArgumentClassname()); @@ -32,7 +33,9 @@ public List assemble(ClientDetails client, MethodDetails method) { .stream() .map(this::setArgumentField) .forEach(ret::add); - return ret; + return Optional.of( + CodeBlock.join(ret, ";") + ); } private CodeBlock setArgumentField(Parameter param) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java new file mode 100644 index 0000000..1f2ec28 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/Assembler.java @@ -0,0 +1,90 @@ +package com.jacobmountain.graphql.client.code; + +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.visitor.ClientDetails; +import com.jacobmountain.graphql.client.visitor.MethodDetails; +import com.squareup.javapoet.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public abstract class Assembler { + + private static final TypeVariableName ERROR_TYPE_VARIABLE = TypeVariableName.get("Error"); + + private final TypeName fetcherInterface; + + private final TypeName subscriberInterface; + + protected final AbstractStage arguments; + + protected final AbstractStage query; + + protected final AbstractStage returnResults; + + protected Assembler(AbstractStage query, + AbstractStage returnResults, + Schema schema, + TypeMapper typeMapper, + Class fetcherInterface, + Class subscriberInterface) { + this.arguments = new ArgumentAssemblyStage(); + this.query = query; + this.returnResults = returnResults; + final TypeName queryType = typeMapper.getType(schema.getQueryTypeName()); + final TypeName mutationType = schema.getMutationTypeName() + .map(typeMapper::getType) + .orElse(ClassName.get(Void.class)); + final TypeName subscriptionType = schema.getSubscriptionTypeName() + .map(typeMapper::getType) + .orElse(ClassName.get(Void.class)); + this.fetcherInterface = ParameterizedTypeName.get( + ClassName.get(fetcherInterface), + queryType, + mutationType, + ERROR_TYPE_VARIABLE + ); + this.subscriberInterface = ParameterizedTypeName.get( + ClassName.get(subscriberInterface), + subscriptionType, + ERROR_TYPE_VARIABLE + ); + } + + public Collection getTypeArguments() { + return Collections.singleton(ERROR_TYPE_VARIABLE); + } + + public List getMemberVariables(ClientDetails client) { + ArrayList vars = new ArrayList<>(2); + if (client.requiresFetcher()) { + vars.add( + MemberVariable.builder() + .name("fetcher") + .type(fetcherInterface) + .build() + ); + } + if (client.requiresSubscriber()) { + vars.add( + MemberVariable.builder() + .name("subscriber") + .type(subscriberInterface) + .build() + ); + } + return vars; + } + + public List assemble(ClientDetails client, MethodDetails method) { + List code = new ArrayList<>(3); + this.arguments.assemble(client, method).ifPresent(code::add); + this.query.assemble(client, method).ifPresent(code::add); + this.returnResults.assemble(client, method).ifPresent(code::add); + return code; + } + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/ClientGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java similarity index 69% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/ClientGenerator.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java index 647bdfe..fc5418a 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/ClientGenerator.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java @@ -1,10 +1,14 @@ -package com.jacobmountain.graphql.client; +package com.jacobmountain.graphql.client.code; -import com.jacobmountain.graphql.client.modules.*; +import com.jacobmountain.graphql.client.PojoBuilder; +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.blocking.BlockingAssembler; +import com.jacobmountain.graphql.client.code.reactive.ReactiveAssembler; import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.AnnotationUtils; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.ClientDetailsVisitor; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.jacobmountain.graphql.client.visitor.MethodDetailsVisitor; @@ -17,11 +21,8 @@ import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * ClientGenerator generates the implementation of the interface annotated with @GraphQLClient @@ -38,25 +39,18 @@ public class ClientGenerator { private final Schema schema; - private final AbstractStage arguments; + private final Assembler module; - private final AbstractStage query; - - private final AbstractStage returnResults; - - public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, String dtoPackageName, Schema schema, boolean reactive) { + public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, Schema schema, boolean reactive) { this.filer = filer; this.typeMapper = typeMapper; this.packageName = packageName; this.schema = schema; - this.arguments = new ArgumentAssemblyStage(); QueryGenerator queryGenerator = new QueryGenerator(schema); if (reactive) { - this.query = new ReactiveQueryStage(queryGenerator, schema, typeMapper, dtoPackageName); - this.returnResults = new ReactiveReturnStage(schema, typeMapper); + this.module = new ReactiveAssembler(queryGenerator, schema, typeMapper); } else { - this.query = new BlockingQueryStage(queryGenerator, schema, typeMapper, dtoPackageName); - this.returnResults = new OptionalReturnStage(schema, typeMapper); + this.module = new BlockingAssembler(queryGenerator, schema, typeMapper); } } @@ -71,29 +65,34 @@ public void generate(Element element, String suffix) { if (StringUtils.isEmpty(suffix)) { throw new IllegalArgumentException("Invalid suffix for implementation of client: " + element.getSimpleName()); } - ClientDetails details = element.accept(new ClientDetailsVisitor(), null); + ClientDetails client = element.accept(new ClientDetailsVisitor(), null); + // Generate the class TypeSpec.Builder builder = TypeSpec.classBuilder(element.getSimpleName() + suffix) .addSuperinterface(ClassName.get((TypeElement) element)) .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationUtils.generated()); - // Add type argument to the client - Stream.of(arguments, query, returnResults) - .flatMap(it -> it.getTypeArguments().stream()) - .map(TypeVariableName::get) - .forEach(builder::addTypeVariable); + + // Add type arguments to the client + for (TypeVariableName typeVariableName : this.module.getTypeArguments()) { + builder.addTypeVariable(typeVariableName); + } + // Add any necessary member variables to the client - List memberVariables = Stream.of(arguments, query, returnResults) - .map(it -> it.getMemberVariables(details)) - .flatMap(Collection::stream) - .peek(memberVariable -> builder.addField(memberVariable.getType(), memberVariable.getName(), Modifier.PRIVATE, Modifier.FINAL)) - .collect(Collectors.toList()); + List memberVariables = module.getMemberVariables(client); + for (MemberVariable memberVariable : memberVariables) { + builder.addField( + memberVariable.getType(), memberVariable.getName(), Modifier.PRIVATE, Modifier.FINAL + ); + } + // generate the constructor builder.addMethod(generateConstructor(memberVariables)); // for each method on the interface, generate its implementation - element.getEnclosedElements() - .forEach(el -> generateImpl(builder, el, details)); + for (Element method : element.getEnclosedElements()) { + generateMethodImplementation(builder, method, client); + } writeToFile(builder.build()); } @@ -103,7 +102,7 @@ public void generate(Element element, String suffix) { * * @param variables the required member variables */ - private MethodSpec generateConstructor(List variables) { + private MethodSpec generateConstructor(List variables) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); variables.forEach(var -> constructor.addParameter(var.getType(), var.getName()) .addStatement("this.$L = $L", var.getName(), var.getName())); @@ -115,7 +114,7 @@ private MethodSpec generateConstructor(List variab * * @param method the method of the @GraphQLClient annotated interface */ - private void generateImpl(TypeSpec.Builder clazz, Element method, ClientDetails client) { + private void generateMethodImplementation(TypeSpec.Builder clazz, Element method, ClientDetails client) { log.info(""); MethodDetails details = method.accept(new MethodDetailsVisitor(schema), typeMapper); log.info("{}", details); @@ -128,9 +127,7 @@ private void generateImpl(TypeSpec.Builder clazz, Element method, ClientDetails .addModifiers(Modifier.PUBLIC) .addParameters(details.getParameterSpec()); - this.arguments.assemble(client, details).forEach(builder::addStatement); - this.query.assemble(client, details).forEach(builder::addStatement); - this.returnResults.assemble(client, details).forEach(builder::addStatement); + this.module.assemble(client, details).forEach(builder::addStatement); clazz.addMethod(builder.build()); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java new file mode 100644 index 0000000..9b4e864 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/MemberVariable.java @@ -0,0 +1,15 @@ +package com.jacobmountain.graphql.client.code; + +import com.squareup.javapoet.TypeName; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class MemberVariable { + + String name; + + TypeName type; + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java new file mode 100644 index 0000000..550a539 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingAssembler.java @@ -0,0 +1,21 @@ +package com.jacobmountain.graphql.client.code.blocking; + +import com.jacobmountain.graphql.client.Fetcher; +import com.jacobmountain.graphql.client.Subscriber; +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.Assembler; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.Schema; + +public class BlockingAssembler extends Assembler { + public BlockingAssembler(QueryGenerator queryGenerator, + Schema schema, + TypeMapper typeMapper) { + super( + new BlockingQueryStage(queryGenerator, schema, typeMapper), + new OptionalReturnStage(schema, typeMapper), + schema, typeMapper, + Fetcher.class, Subscriber.class + ); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/BlockingQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingQueryStage.java similarity index 63% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/BlockingQueryStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingQueryStage.java index bcda7ba..c2f0def 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/BlockingQueryStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/BlockingQueryStage.java @@ -1,13 +1,13 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code.blocking; -import com.jacobmountain.graphql.client.Fetcher; -import com.jacobmountain.graphql.client.Subscriber; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractQueryStage; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.exceptions.MissingAnnotationException; import com.jacobmountain.graphql.client.query.QueryGenerator; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -15,43 +15,18 @@ import com.squareup.javapoet.TypeVariableName; import graphql.language.ObjectTypeDefinition; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Optional; public class BlockingQueryStage extends AbstractQueryStage { private static final ClassName RESPONSE_CLASS_NAME = ClassName.get(Response.class); - public BlockingQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper, String dtoPackageName) { - super(queryGenerator, schema, typeMapper, dtoPackageName); + public BlockingQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper) { + super(queryGenerator, schema, typeMapper); } @Override - public List getMemberVariables(ClientDetails details) { - ArrayList vars = new ArrayList<>(); - if (details.requiresFetcher()) { - vars.add( - MemberVariable.builder() - .name("fetcher") - .type(getFetcherTypeName(Fetcher.class)) - .build() - ); - } - if (details.requiresSubscriber()) { - vars.add( - MemberVariable.builder() - .name("subscriber") - .type(getSubscriberTypeName(Subscriber.class)) - .build() - ); - } - return vars; - } - - @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { String member = method.isSubscription() ? "subscriber" : "fetcher"; ObjectTypeDefinition query = getTypeDefinition(method); final CodeBlock.Builder builder = CodeBlock.builder(); @@ -63,9 +38,8 @@ public List assemble(ClientDetails client, MethodDetails method) { queryCode = generateQueryCode(method.getRequestName(), method); builder.add("$T thing = ", ParameterizedTypeName.get(RESPONSE_CLASS_NAME, typeMapper.getType(query.getName()), TypeVariableName.get("Error"))); } - return Collections.singletonList( - builder.add("$L.$L", member, getMethod(method)).add(queryCode).build() - ); + builder.add("$L.$L", member, getMethod(method)).add(queryCode); + return Optional.of(builder.build()); } private CodeBlock unwrapResponseLambda(ObjectTypeDefinition query, MethodDetails method) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/OptionalReturnStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/OptionalReturnStage.java similarity index 81% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/OptionalReturnStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/OptionalReturnStage.java index 02d48c0..a1a018b 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/OptionalReturnStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/blocking/OptionalReturnStage.java @@ -1,9 +1,11 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code.blocking; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractStage; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -11,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -25,12 +26,12 @@ public OptionalReturnStage(Schema schema, TypeMapper typeMapper) { } @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { if (ClassName.VOID.equals(method.getReturnType())) { if (method.isQuery()) { throw new IllegalArgumentException("void return type on a non mutation method"); } - return Collections.emptyList(); + return Optional.empty(); } ObjectTypeDefinition typeDefinition = getTypeDefinition(method); List ret = new ArrayList<>(); @@ -42,7 +43,7 @@ public List assemble(ClientDetails client, MethodDetails method) { if (!method.returnsClass(Optional.class)) { ret.add(CodeBlock.of("orElse(null)")); } - return Collections.singletonList(CodeBlock.join(ret, "\n\t.")); + return Optional.of(CodeBlock.join(ret, "\n\t.")); } } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java new file mode 100644 index 0000000..277305d --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveAssembler.java @@ -0,0 +1,21 @@ +package com.jacobmountain.graphql.client.code.reactive; + +import com.jacobmountain.graphql.client.ReactiveFetcher; +import com.jacobmountain.graphql.client.ReactiveSubscriber; +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.Assembler; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.Schema; + +public class ReactiveAssembler extends Assembler { + public ReactiveAssembler(QueryGenerator queryGenerator, + Schema schema, + TypeMapper typeMapper) { + super( + new ReactiveQueryStage(queryGenerator, schema, typeMapper), + new ReactiveReturnStage(schema, typeMapper), + schema, typeMapper, + ReactiveFetcher.class, ReactiveSubscriber.class + ); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java new file mode 100644 index 0000000..ff95658 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveQueryStage.java @@ -0,0 +1,32 @@ +package com.jacobmountain.graphql.client.code.reactive; + +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractQueryStage; +import com.jacobmountain.graphql.client.query.QueryGenerator; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.visitor.ClientDetails; +import com.jacobmountain.graphql.client.visitor.MethodDetails; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.ParameterizedTypeName; +import org.reactivestreams.Publisher; + +import java.util.Optional; + +public class ReactiveQueryStage extends AbstractQueryStage { + + public ReactiveQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper) { + super(queryGenerator, schema, typeMapper); + } + + @Override + public Optional assemble(ClientDetails client, MethodDetails method) { + String member = method.isSubscription() ? "subscriber" : "fetcher"; + return Optional.of( + CodeBlock.builder() + .add("$T thing = ", ParameterizedTypeName.get(ClassName.get(Publisher.class), getReturnTypeName(method))) + .add("$L.$L", member, getMethod(method)).add(generateQueryCode(method.getRequestName(), method)) + .build() + ); + } +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveReturnStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveReturnStage.java similarity index 85% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveReturnStage.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveReturnStage.java index 40557a5..7df9fa0 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveReturnStage.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/reactive/ReactiveReturnStage.java @@ -1,9 +1,11 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.code.reactive; import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.code.AbstractStage; import com.jacobmountain.graphql.client.dto.Response; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; +import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.MethodDetails; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -12,7 +14,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.function.Function; public class ReactiveReturnStage extends AbstractStage { @@ -24,12 +29,12 @@ public ReactiveReturnStage(Schema schema, TypeMapper typeMapper) { } @Override - public List assemble(ClientDetails client, MethodDetails method) { + public Optional assemble(ClientDetails client, MethodDetails method) { if (ClassName.VOID.equals(method.getReturnType())) { if (!method.isMutation()) { throw new IllegalArgumentException("void return type on a non mutation method"); } - return Collections.singletonList( + return Optional.of( CodeBlock.of("$T.from(thing).block()", method.isSubscription() ? Flux.class : Mono.class) ); } @@ -47,7 +52,7 @@ public List assemble(ClientDetails client, MethodDetails method) { unwrapReturnType(method).ifPresent(ret::add); - return Collections.singletonList(CodeBlock.join(ret, "\n\t.")); + return Optional.of(CodeBlock.join(ret, "\n\t.")); } private Optional unwrapReturnType(MethodDetails method) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java deleted file mode 100644 index 2a42d8f..0000000 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ReactiveQueryStage.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.jacobmountain.graphql.client.modules; - -import com.jacobmountain.graphql.client.ReactiveFetcher; -import com.jacobmountain.graphql.client.ReactiveSubscriber; -import com.jacobmountain.graphql.client.TypeMapper; -import com.jacobmountain.graphql.client.query.QueryGenerator; -import com.jacobmountain.graphql.client.utils.Schema; -import com.jacobmountain.graphql.client.visitor.MethodDetails; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.ParameterizedTypeName; -import org.reactivestreams.Publisher; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class ReactiveQueryStage extends AbstractQueryStage { - - public ReactiveQueryStage(QueryGenerator queryGenerator, Schema schema, TypeMapper typeMapper, String dtoPackageName) { - super(queryGenerator, schema, typeMapper, dtoPackageName); - } - - @Override - public List getMemberVariables(ClientDetails details) { - ArrayList vars = new ArrayList<>(); - if (details.requiresFetcher()) { - vars.add( - MemberVariable.builder() - .name("fetcher") - .type(getFetcherTypeName(ReactiveFetcher.class)) - .build() - ); - } - if (details.requiresSubscriber()) { - vars.add( - MemberVariable.builder() - .name("subscriber") - .type(getSubscriberTypeName(ReactiveSubscriber.class)) - .build() - ); - } - return vars; - } - - @Override - public List assemble(ClientDetails client, MethodDetails method) { - String member = method.isSubscription() ? "subscriber" : "fetcher"; - return Collections.singletonList( - CodeBlock.builder() - .add("$T thing = ", ParameterizedTypeName.get(ClassName.get(Publisher.class), getReturnTypeName(method))) - .add("$L.$L", member, getMethod(method)).add(generateQueryCode(method.getRequestName(), method)) - .build() - ); - } -} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java index 3661ffd..c0244d8 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryContext.java @@ -36,4 +36,5 @@ public Type getType() { public void newArg(String arg) { args.add(arg); } + } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java index 1dbf013..247d8bf 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/QueryGenerator.java @@ -4,6 +4,7 @@ import com.jacobmountain.graphql.client.query.filters.*; import com.jacobmountain.graphql.client.query.selectors.DefaultFieldSelector; import com.jacobmountain.graphql.client.query.selectors.DelegatingFieldSelector; +import com.jacobmountain.graphql.client.query.selectors.FieldSelector; import com.jacobmountain.graphql.client.query.selectors.InlineFragmentRenderer; import com.jacobmountain.graphql.client.utils.Schema; import com.jacobmountain.graphql.client.utils.StringUtils; @@ -19,8 +20,14 @@ public class QueryGenerator { private final Schema schema; + private final FieldSelector fieldSelector; + public QueryGenerator(Schema registry) { this.schema = registry; + this.fieldSelector = new DelegatingFieldSelector( + new DefaultFieldSelector(schema, this), + new InlineFragmentRenderer(schema, this) + ); } public QueryBuilder query() { @@ -64,7 +71,7 @@ public Optional generateFieldSelection(String alias, String type = Schema.unwrap(context.getFieldDefinition().getType()); TypeDefinition typeDefinition = schema.getTypeDefinition(type).orElse(null); - if (!filters.stream().allMatch(fi -> fi.shouldAddField(context))) { + if (!filters.stream().allMatch(filter -> filter.shouldAddField(context))) { return Optional.empty(); } @@ -73,11 +80,7 @@ public Optional generateFieldSelection(String alias, return Optional.of(alias + args); } - return new DelegatingFieldSelector( - new DefaultFieldSelector(schema, this), - new InlineFragmentRenderer(schema, this) - ) - .selectFields(typeDefinition, context, filters) + return fieldSelector.selectFields(typeDefinition, context, filters) .map(children -> alias + args + " " + children) .findFirst(); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java index 01ec3f8..7b0f1c3 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/query/selectors/DelegatingFieldSelector.java @@ -25,8 +25,9 @@ public Stream selectFields(TypeDefinition typeDefinition, .reduce((a, b) -> String.join(" ", a, b)) .map(children -> "{ " + children + - " __typename" + - " }") + " __typename " + + "}" + ) .map(Stream::of) .orElseGet(Stream::empty); } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ClientDetails.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java similarity index 86% rename from graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ClientDetails.java rename to graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java index b66134c..27c4801 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/modules/ClientDetails.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java @@ -1,4 +1,4 @@ -package com.jacobmountain.graphql.client.modules; +package com.jacobmountain.graphql.client.visitor; import lombok.Builder; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java index 8ecc232..e7af3be 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java @@ -3,7 +3,6 @@ import com.jacobmountain.graphql.client.annotations.GraphQLMutation; import com.jacobmountain.graphql.client.annotations.GraphQLQuery; import com.jacobmountain.graphql.client.annotations.GraphQLSubscription; -import com.jacobmountain.graphql.client.modules.ClientDetails; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy index 2c6a8dd..eff7001 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy @@ -1,7 +1,7 @@ package com.jacobmountain.client.modules -import com.jacobmountain.graphql.client.modules.ArgumentAssemblyStage -import com.jacobmountain.graphql.client.modules.ClientDetails +import com.jacobmountain.graphql.client.code.ArgumentAssemblyStage +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import com.jacobmountain.graphql.client.visitor.Parameter import spock.lang.Specification @@ -18,10 +18,9 @@ class ArgumentAssemblyStageSpec extends Specification { .parameters([]) .build() ) - def code = renderBlocks(blocks).split(";") then: - renderBlocks(blocks).split(";") == [] + !blocks.isPresent() } def "When there's a parameter, we should set it"() { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy new file mode 100644 index 0000000..b5ab605 --- /dev/null +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy @@ -0,0 +1,103 @@ +package com.jacobmountain.client.modules + +import com.jacobmountain.graphql.client.* +import com.jacobmountain.graphql.client.code.Assembler +import com.jacobmountain.graphql.client.code.MemberVariable +import com.jacobmountain.graphql.client.code.blocking.BlockingAssembler +import com.jacobmountain.graphql.client.code.reactive.ReactiveAssembler +import com.jacobmountain.graphql.client.query.QueryGenerator +import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails +import spock.lang.Specification + +class AssemblerSpec extends Specification { + + def "The reactive client class is generated with the correct member variables"() { + given: + Assembler assembler = new ReactiveAssembler( + Mock(QueryGenerator), + new Schema(""" + schema { + query: Query + mutation: Mutation + subscription: Subscription + } + type Query { } + type Mutation { } + type Subscription { } + """), + new TypeMapper("com.test") + ) + + when: + def members = assembler.getMemberVariables( + ClientDetails.builder() + .requiresFetcher(requiresFetcher) + .requiresSubscriber(requiresSubscriber) + .build() + ) + + then: + members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) + getMemberVariable("fetcher", members) + .map { it -> it.type.toString() == "${ReactiveFetcher.class.getName()}" } + .orElse(true) + getMemberVariable("subscriber", members) + .map { it -> it.type.toString() == "${ReactiveSubscriber.class.getName()}" } + .orElse(true) + + where: + requiresFetcher | requiresSubscriber + true | true + true | false + false | true + false | false + } + + def "The default client class is generated with the correct member variables"() { + given: + Assembler assembler = new BlockingAssembler( + Mock(QueryGenerator), + new Schema(""" + schema { + query: Query + mutation: Mutation + subscription: Subscription + } + type Query { } + type Mutation { } + type Subscription { } + """), + new TypeMapper("com.test") + ) + + when: + def members = assembler.getMemberVariables( + ClientDetails.builder() + .requiresFetcher(requiresFetcher) + .requiresSubscriber(requiresSubscriber) + .build() + ) + + then: + members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) + getMemberVariable("fetcher", members) + .map { it -> it.type.toString() == "${Fetcher.class.getName()}" } + .orElse(true) + getMemberVariable("subscriber", members) + .map { it -> it.type.toString() == "${Subscriber.class.getName()}" } + .orElse(true) + + where: + requiresFetcher | requiresSubscriber + true | true + true | false + false | true + false | false + } + + static Optional getMemberVariable(String name, List variables) { + Optional.ofNullable(variables.find { it.name == name }) + } + +} diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy index a842b8e..c38ce7f 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy @@ -4,11 +4,9 @@ import com.squareup.javapoet.CodeBlock class CodeBlockUtils { - static def renderBlocks(List blocks) { - blocks.collect { block -> - block.toString() - .replaceAll("\\t|\\n", "") - }.join(";") + ";" + static def renderBlocks(Optional blocks) { + blocks.get().toString() + .replaceAll("\\t|\\n", "") + ";" } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/BlockingQueryStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/BlockingQueryStageSpec.groovy similarity index 62% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/BlockingQueryStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/BlockingQueryStageSpec.groovy index dd8e833..40a8d1a 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/BlockingQueryStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/BlockingQueryStageSpec.groovy @@ -1,17 +1,14 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.modules.blocking + -import com.jacobmountain.graphql.client.Fetcher -import com.jacobmountain.graphql.client.Subscriber import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.blocking.BlockingQueryStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.AbstractStage -import com.jacobmountain.graphql.client.modules.BlockingQueryStage -import com.jacobmountain.graphql.client.modules.ClientDetails import com.jacobmountain.graphql.client.query.QueryGenerator import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import spock.lang.Specification -import spock.lang.Unroll import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks @@ -37,8 +34,7 @@ class BlockingQueryStageSpec extends Specification { type Mutation { } type Subscription { } """), - new TypeMapper("com.test"), - "com.test" + new TypeMapper("com.test") ) def "We can generate the code for a query"() { @@ -80,34 +76,4 @@ class BlockingQueryStageSpec extends Specification { code.endsWith("subscription -> java.util.Optional.ofNullable(subscription).map(com.jacobmountain.graphql.client.dto.Response::getData).map(com.test.Subscription::getField).ifPresent(callback));") } - @Unroll - def "The reactive client class is generated with the correct member variables"() { - when: - def members = stage.getMemberVariables( - ClientDetails.builder() - .requiresFetcher(requiresFetcher) - .requiresSubscriber(requiresSubscriber) - .build() - ) - - then: - members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) - getMemberVariable("fetcher", members) - .map { it -> it.type.toString() == "${Fetcher.class.getName()}" } - .orElse(true) - getMemberVariable("subscriber", members) - .map { it -> it.type.toString() == "${Subscriber.class.getName()}" } - .orElse(true) - - where: - requiresFetcher | requiresSubscriber - true | true - false | true - true | false - false | false - } - - static Optional getMemberVariable(String name, List variables) { - Optional.ofNullable(variables.find { it.name == name }) - } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/OptionalReturnStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/OptionalReturnStageSpec.groovy similarity index 92% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/OptionalReturnStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/OptionalReturnStageSpec.groovy index 8c13b80..97bf9a1 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/OptionalReturnStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/OptionalReturnStageSpec.groovy @@ -1,10 +1,10 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.modules.blocking import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.blocking.OptionalReturnStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.ClientDetails -import com.jacobmountain.graphql.client.modules.OptionalReturnStage import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import com.squareup.javapoet.ClassName import com.squareup.javapoet.ParameterizedTypeName @@ -39,7 +39,7 @@ class OptionalReturnStageSpec extends Specification { def blocks = stage.assemble(Mock(ClientDetails), methodDetails) then: "there shouldn't be any " - blocks.isEmpty() + !blocks.isPresent() } def "void return types are only supported on mutations"() { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveQueryStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveQueryStageSpec.groovy similarity index 61% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveQueryStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveQueryStageSpec.groovy index 78d501d..7490cd6 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveQueryStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveQueryStageSpec.groovy @@ -1,14 +1,12 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.modules.reactive + -import com.jacobmountain.graphql.client.ReactiveFetcher -import com.jacobmountain.graphql.client.ReactiveSubscriber import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.reactive.ReactiveQueryStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.AbstractStage -import com.jacobmountain.graphql.client.modules.ClientDetails -import com.jacobmountain.graphql.client.modules.ReactiveQueryStage import com.jacobmountain.graphql.client.query.QueryGenerator import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import org.reactivestreams.Publisher import spock.lang.Specification @@ -37,8 +35,7 @@ class ReactiveQueryStageSpec extends Specification { type Mutation { } type Subscription { } """), - new TypeMapper("com.test"), - "com.test" + new TypeMapper("com.test") ) def "We can generate the code for a query"() { @@ -76,34 +73,6 @@ class ReactiveQueryStageSpec extends Specification { renderBlocks(blocks) == """${Publisher.class.getName()}<${Response.class.getName()}> thing = subscriber.subscribe("query", null);""" } - def "The reactive client class is generated with the correct member variables"() { - when: - def members = stage.getMemberVariables( - ClientDetails.builder() - .requiresFetcher(requiresFetcher) - .requiresSubscriber(requiresSubscriber) - .build() - ) - - then: - members.size() == (requiresFetcher ? 1 : 0) + (requiresSubscriber ? 1 : 0) - getMemberVariable("fetcher", members) - .map { it -> it.type.toString() == "${ReactiveFetcher.class.getName()}" } - .orElse(true) - getMemberVariable("subscriber", members) - .map { it -> it.type.toString() == "${ReactiveSubscriber.class.getName()}" } - .orElse(true) - - where: - requiresFetcher | requiresSubscriber - true | true - true | false - false | true - false | false - } - static Optional getMemberVariable(String name, List variables) { - Optional.ofNullable(variables.find { it.name == name }) - } } diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveReturnStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveReturnStageSpec.groovy similarity index 95% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveReturnStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveReturnStageSpec.groovy index c81c8b3..1c6c880 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ReactiveReturnStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveReturnStageSpec.groovy @@ -1,10 +1,10 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.modules.reactive import com.jacobmountain.graphql.client.TypeMapper +import com.jacobmountain.graphql.client.code.reactive.ReactiveReturnStage import com.jacobmountain.graphql.client.dto.Response -import com.jacobmountain.graphql.client.modules.ClientDetails -import com.jacobmountain.graphql.client.modules.ReactiveReturnStage import com.jacobmountain.graphql.client.utils.Schema +import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import com.squareup.javapoet.ClassName import com.squareup.javapoet.ParameterizedTypeName diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy deleted file mode 100644 index 65c4c26..0000000 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/query/QueryGeneratorSpec.groovy +++ /dev/null @@ -1,85 +0,0 @@ -package com.jacobmountain.query - -import com.jacobmountain.graphql.client.query.QueryGenerator -import com.jacobmountain.graphql.client.utils.Schema -import spock.lang.Specification - -class QueryGeneratorSpec extends Specification { - - static QueryGenerator givenQuery(String query, String types) { - new QueryGenerator(new Schema(""" - schema { - query: Query - } - type Query { - $query - } - $types - """)) - } - - def "I can generate a query with a fragment"() { - given: - def generator = givenQuery("hero: Hero",""" - type Hero { - id: String - name: String - } - """) - - when: - def query = generator.query() - .build(null, "hero", [] as Set) - - then: - query == "query Hero { hero { id name __typename } }" - } - - def "I can generate a complex query with a fragment"() { - given: - def generator = givenQuery("hero: Hero",""" - type Hero { - id: String - name: String - ships: [Starship!]! - } - type Starship { - id: String! - name: String! - length(unit: LengthUnit = METER): Float - coordinates: [[Float!]!] - } - enum LengthUnit { - METER - FOOT - } - """) - - when: - def query = generator.query() - .build(null, "hero", [] as Set) - - then: - query == "query Hero { hero { id name ships { id name length coordinates __typename } __typename } }" - } - - def "I can generate a recursive query with a fragment"() { - given: - def generator = givenQuery("hero: Hero",""" - type Hero { - id: String - name: String - friends: [Hero!]! - } - """) - - when: - def query = generator.query() - .maxDepth(3) - .build(null, "hero", [] as Set) - - then: - query == "query Hero { hero { id name friends { id name friends { id name __typename } __typename } __typename } }" - } - -} From 657d0a1c3291be88c3c67d22eadaa7a9bc7f27cc Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Tue, 16 Mar 2021 17:10:22 +0000 Subject: [PATCH 2/3] Refactor so the code generators don't have to visit the Elements themselves --- .../client/GraphQLClientProcessor.java | 1 + .../graphql/client/code/ClientGenerator.java | 51 +++---- .../graphql/client/visitor/ClientDetails.java | 15 ++- .../client/visitor/ClientDetailsVisitor.java | 22 ++- .../visitor/ClientDetailsVisitorArgs.java | 16 +++ .../client/visitor/MethodDetailsVisitor.java | 2 +- .../ArgumentAssemblyStageSpec.groovy | 4 +- .../{modules => code}/AssemblerSpec.groovy | 2 +- .../client/code/ClientGeneratorSpec.groovy | 127 ++++++++++++++++++ .../{modules => code}/CodeBlockUtils.groovy | 2 +- .../blocking/BlockingQueryStageSpec.groovy | 4 +- .../blocking/OptionalReturnStageSpec.groovy | 4 +- .../reactive/ReactiveQueryStageSpec.groovy | 4 +- .../reactive/ReactiveReturnStageSpec.groovy | 4 +- 14 files changed, 215 insertions(+), 43 deletions(-) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/ArgumentAssemblyStageSpec.groovy (94%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/AssemblerSpec.groovy (98%) create mode 100644 graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/CodeBlockUtils.groovy (83%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/blocking/BlockingQueryStageSpec.groovy (95%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/blocking/OptionalReturnStageSpec.groovy (95%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/reactive/ReactiveQueryStageSpec.groovy (95%) rename graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/{modules => code}/reactive/ReactiveReturnStageSpec.groovy (97%) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java index d62e5a1..b7103d1 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java @@ -85,6 +85,7 @@ private void generateJavaDataClasses(Input input) { dtoGenerator.generate(input.getSchema().types().values()); } + @SneakyThrows private void generateClientImplementation(Input client) { GraphQLClient annotation = client.getAnnotation(); log.info("Generating java implementation of {}", client.element.getSimpleName()); diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java index fc5418a..34a3367 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java @@ -10,17 +10,19 @@ import com.jacobmountain.graphql.client.utils.StringUtils; import com.jacobmountain.graphql.client.visitor.ClientDetails; import com.jacobmountain.graphql.client.visitor.ClientDetailsVisitor; +import com.jacobmountain.graphql.client.visitor.ClientDetailsVisitorArgs; import com.jacobmountain.graphql.client.visitor.MethodDetails; -import com.jacobmountain.graphql.client.visitor.MethodDetailsVisitor; -import com.squareup.javapoet.*; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import javax.annotation.processing.Filer; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; +import java.io.IOException; import java.util.List; import java.util.Optional; @@ -39,7 +41,7 @@ public class ClientGenerator { private final Schema schema; - private final Assembler module; + private final Assembler assembler; public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, Schema schema, boolean reactive) { this.filer = filer; @@ -48,9 +50,9 @@ public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, S this.schema = schema; QueryGenerator queryGenerator = new QueryGenerator(schema); if (reactive) { - this.module = new ReactiveAssembler(queryGenerator, schema, typeMapper); + this.assembler = new ReactiveAssembler(queryGenerator, schema, typeMapper); } else { - this.module = new BlockingAssembler(queryGenerator, schema, typeMapper); + this.assembler = new BlockingAssembler(queryGenerator, schema, typeMapper); } } @@ -60,26 +62,28 @@ public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, S * @param element the Element that has the @GraphQLClient on * @param suffix the implementations suffix */ - @SneakyThrows - public void generate(Element element, String suffix) { + public void generate(Element element, String suffix) throws IOException { if (StringUtils.isEmpty(suffix)) { throw new IllegalArgumentException("Invalid suffix for implementation of client: " + element.getSimpleName()); } - ClientDetails client = element.accept(new ClientDetailsVisitor(), null); + ClientDetails client = element.accept( + new ClientDetailsVisitor(), + new ClientDetailsVisitorArgs(schema, typeMapper) + ); // Generate the class - TypeSpec.Builder builder = TypeSpec.classBuilder(element.getSimpleName() + suffix) - .addSuperinterface(ClassName.get((TypeElement) element)) + TypeSpec.Builder builder = TypeSpec.classBuilder(client.getName() + suffix) + .addSuperinterface(client.getClientInterface()) .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationUtils.generated()); // Add type arguments to the client - for (TypeVariableName typeVariableName : this.module.getTypeArguments()) { + for (TypeVariableName typeVariableName : this.assembler.getTypeArguments()) { builder.addTypeVariable(typeVariableName); } // Add any necessary member variables to the client - List memberVariables = module.getMemberVariables(client); + List memberVariables = assembler.getMemberVariables(client); for (MemberVariable memberVariable : memberVariables) { builder.addField( memberVariable.getType(), memberVariable.getName(), Modifier.PRIVATE, Modifier.FINAL @@ -90,7 +94,7 @@ public void generate(Element element, String suffix) { builder.addMethod(generateConstructor(memberVariables)); // for each method on the interface, generate its implementation - for (Element method : element.getEnclosedElements()) { + for (MethodDetails method : client.getMethods()) { generateMethodImplementation(builder, method, client); } @@ -114,20 +118,19 @@ private MethodSpec generateConstructor(List variables) { * * @param method the method of the @GraphQLClient annotated interface */ - private void generateMethodImplementation(TypeSpec.Builder clazz, Element method, ClientDetails client) { + private void generateMethodImplementation(TypeSpec.Builder clazz, MethodDetails method, ClientDetails client) { log.info(""); - MethodDetails details = method.accept(new MethodDetailsVisitor(schema), typeMapper); - log.info("{}", details); + log.info("{}", method); - generateArgumentDTO(details) + generateArgumentDTO(method) .ifPresent(clazz::addType); - MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getSimpleName().toString()) - .returns(details.getReturnType()) + MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getMethodName()) + .returns(method.getReturnType()) .addModifiers(Modifier.PUBLIC) - .addParameters(details.getParameterSpec()); + .addParameters(method.getParameterSpec()); - this.module.assemble(client, details).forEach(builder::addStatement); + this.assembler.assemble(client, method).forEach(builder::addStatement); clazz.addMethod(builder.build()); } @@ -150,7 +153,7 @@ public Optional generateArgumentDTO(MethodDetails details) { }); } - private void writeToFile(TypeSpec spec) throws Exception { + private void writeToFile(TypeSpec spec) throws IOException { JavaFile.builder(packageName, spec) .indent("\t") .skipJavaLangImports(true) diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java index 27c4801..77a9a9d 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetails.java @@ -1,13 +1,24 @@ package com.jacobmountain.graphql.client.visitor; +import com.squareup.javapoet.ClassName; import lombok.Builder; +import lombok.Data; +import java.util.List; + +@Data @Builder public class ClientDetails { - private final boolean requiresSubscriber; + private String name; + + private ClassName clientInterface; + + private boolean requiresSubscriber; + + private boolean requiresFetcher; - private final boolean requiresFetcher; + private List methods; public boolean requiresSubscriber() { return requiresSubscriber; diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java index e7af3be..26c43ca 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitor.java @@ -3,32 +3,46 @@ import com.jacobmountain.graphql.client.annotations.GraphQLMutation; import com.jacobmountain.graphql.client.annotations.GraphQLQuery; import com.jacobmountain.graphql.client.annotations.GraphQLSubscription; +import com.squareup.javapoet.ClassName; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementKindVisitor8; import java.lang.annotation.Annotation; +import java.util.List; +import java.util.stream.Collectors; -public class ClientDetailsVisitor extends ElementKindVisitor8 { +public class ClientDetailsVisitor extends ElementKindVisitor8 { @Override - public ClientDetails visitType(TypeElement type, Void unused) { + public ClientDetails visitType(TypeElement type, ClientDetailsVisitorArgs args) { return ClientDetails.builder() + .name(type.getSimpleName().toString()) + .clientInterface(ClassName.get(type)) .requiresSubscriber(requiresSubscriber(type)) .requiresFetcher(requiresFetcher(type)) + .methods(visitMethods(args, type)) .build(); } + private List visitMethods(ClientDetailsVisitorArgs args, TypeElement type) { + final MethodDetailsVisitor methodVisitor = new MethodDetailsVisitor(args.getSchema()); + return type.getEnclosedElements() + .stream() + .map(method -> methodVisitor.visit(method, args.getTypeMapper())) + .collect(Collectors.toList()); + } + private boolean requiresSubscriber(TypeElement element) { return element.getEnclosedElements() .stream() - .anyMatch(it -> hasAnnotation(it, GraphQLSubscription.class)); + .anyMatch(method -> hasAnnotation(method, GraphQLSubscription.class)); } private boolean requiresFetcher(TypeElement element) { return element.getEnclosedElements() .stream() - .anyMatch(it -> hasAnnotation(it, GraphQLQuery.class) || hasAnnotation(it, GraphQLMutation.class)); + .anyMatch(method -> hasAnnotation(method, GraphQLQuery.class) || hasAnnotation(method, GraphQLMutation.class)); } private boolean hasAnnotation(Element el, Class annotation) { diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java new file mode 100644 index 0000000..535bc55 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/ClientDetailsVisitorArgs.java @@ -0,0 +1,16 @@ +package com.jacobmountain.graphql.client.visitor; + +import com.jacobmountain.graphql.client.TypeMapper; +import com.jacobmountain.graphql.client.utils.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ClientDetailsVisitorArgs { + + private Schema schema; + + private TypeMapper typeMapper; + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java index b519196..6c3bddb 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/visitor/MethodDetailsVisitor.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; @Slf4j -public class MethodDetailsVisitor extends ElementKindVisitor8 { +class MethodDetailsVisitor extends ElementKindVisitor8 { private final Schema schema; diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ArgumentAssemblyStageSpec.groovy similarity index 94% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ArgumentAssemblyStageSpec.groovy index eff7001..120d9aa 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/ArgumentAssemblyStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ArgumentAssemblyStageSpec.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code import com.jacobmountain.graphql.client.code.ArgumentAssemblyStage import com.jacobmountain.graphql.client.visitor.ClientDetails @@ -6,7 +6,7 @@ import com.jacobmountain.graphql.client.visitor.MethodDetails import com.jacobmountain.graphql.client.visitor.Parameter import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class ArgumentAssemblyStageSpec extends Specification { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/AssemblerSpec.groovy similarity index 98% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/AssemblerSpec.groovy index b5ab605..65480b2 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/AssemblerSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/AssemblerSpec.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code import com.jacobmountain.graphql.client.* import com.jacobmountain.graphql.client.code.Assembler diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy new file mode 100644 index 0000000..e3135e1 --- /dev/null +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/ClientGeneratorSpec.groovy @@ -0,0 +1,127 @@ +//package com.jacobmountain.client.code +// +//import com.jacobmountain.graphql.client.TypeMapper +//import com.jacobmountain.graphql.client.code.ClientGenerator +//import com.jacobmountain.graphql.client.utils.Schema +//import spock.lang.Specification +// +//import javax.annotation.processing.Filer +//import javax.lang.model.element.AnnotationMirror +//import javax.lang.model.element.Element +//import javax.lang.model.element.ElementKind +//import javax.lang.model.element.ElementVisitor +//import javax.lang.model.element.Modifier +//import javax.lang.model.element.Name +//import javax.lang.model.element.NestingKind +//import javax.lang.model.element.TypeElement +//import javax.lang.model.element.TypeParameterElement +//import javax.lang.model.type.TypeMirror +//import java.lang.annotation.Annotation +// +//class ClientGeneratorSpec extends Specification { +// +// ClientGenerator generator = new ClientGenerator( +// Mock(Filer), +// new TypeMapper("com.test"), +// "com.test", +// new Schema(""" +// schema { +// query: Query +// } +// type Query {} +// """), +// false +// ) +// +// def "Test"() { +// given: +// Element el = Mock(TypeElement) { +// getSimpleName() >> Mock(Name) { +// toString() >> "MyClient" +// } +// getEnclosingElement() >> new TypeElement() { +// @Override +// List getEnclosedElements() { +// return null +// } +// +// @Override +// NestingKind getNestingKind() { +// return null +// } +// +// @Override +// Name getQualifiedName() { +// return null +// } +// +// @Override +// Name getSimpleName() { +// return null +// } +// +// @Override +// TypeMirror getSuperclass() { +// return null +// } +// +// @Override +// List getInterfaces() { +// return null +// } +// +// @Override +// List getTypeParameters() { +// return null +// } +// +// @Override +// Element getEnclosingElement() { +// return null +// } +// +// @Override +// TypeMirror asType() { +// return null +// } +// +// @Override +// ElementKind getKind() { +// return null +// } +// +// @Override +// Set getModifiers() { +// return null +// } +// +// @Override +// List getAnnotationMirrors() { +// return null +// } +// +// @Override +// def A getAnnotation(Class annotationType) { +// return null +// } +// +// @Override +// def R accept(ElementVisitor v, P p) { +// return null +// } +// +// @Override +// def A[] getAnnotationsByType(Class annotationType) { +// return null +// } +// } +// } +// +// when: +// generator.generate(el, "impl") +// +// then: +// true +// } +// +//} diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/CodeBlockUtils.groovy similarity index 83% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/CodeBlockUtils.groovy index c38ce7f..1d4d958 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/CodeBlockUtils.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/CodeBlockUtils.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules +package com.jacobmountain.client.code import com.squareup.javapoet.CodeBlock diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/BlockingQueryStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/BlockingQueryStageSpec.groovy similarity index 95% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/BlockingQueryStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/BlockingQueryStageSpec.groovy index 40a8d1a..4e12298 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/BlockingQueryStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/BlockingQueryStageSpec.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules.blocking +package com.jacobmountain.client.code.blocking import com.jacobmountain.graphql.client.TypeMapper @@ -10,7 +10,7 @@ import com.jacobmountain.graphql.client.visitor.ClientDetails import com.jacobmountain.graphql.client.visitor.MethodDetails import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class BlockingQueryStageSpec extends Specification { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/OptionalReturnStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/OptionalReturnStageSpec.groovy similarity index 95% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/OptionalReturnStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/OptionalReturnStageSpec.groovy index 97bf9a1..c430714 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/blocking/OptionalReturnStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/blocking/OptionalReturnStageSpec.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules.blocking +package com.jacobmountain.client.code.blocking import com.jacobmountain.graphql.client.TypeMapper import com.jacobmountain.graphql.client.code.blocking.OptionalReturnStage @@ -10,7 +10,7 @@ import com.squareup.javapoet.ClassName import com.squareup.javapoet.ParameterizedTypeName import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class OptionalReturnStageSpec extends Specification { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveQueryStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveQueryStageSpec.groovy similarity index 95% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveQueryStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveQueryStageSpec.groovy index 7490cd6..ee54224 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveQueryStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveQueryStageSpec.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules.reactive +package com.jacobmountain.client.code.reactive import com.jacobmountain.graphql.client.TypeMapper @@ -11,7 +11,7 @@ import com.jacobmountain.graphql.client.visitor.MethodDetails import org.reactivestreams.Publisher import spock.lang.Specification -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class ReactiveQueryStageSpec extends Specification { diff --git a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveReturnStageSpec.groovy b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveReturnStageSpec.groovy similarity index 97% rename from graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveReturnStageSpec.groovy rename to graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveReturnStageSpec.groovy index 1c6c880..91b9ee7 100644 --- a/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/modules/reactive/ReactiveReturnStageSpec.groovy +++ b/graphql-java-client-processor/src/test/groovy/com/jacobmountain/client/code/reactive/ReactiveReturnStageSpec.groovy @@ -1,4 +1,4 @@ -package com.jacobmountain.client.modules.reactive +package com.jacobmountain.client.code.reactive import com.jacobmountain.graphql.client.TypeMapper import com.jacobmountain.graphql.client.code.reactive.ReactiveReturnStage @@ -14,7 +14,7 @@ import spock.lang.Specification import java.util.function.Function -import static com.jacobmountain.client.modules.CodeBlockUtils.renderBlocks +import static com.jacobmountain.client.code.CodeBlockUtils.renderBlocks class ReactiveReturnStageSpec extends Specification { From 25c18040ef3756cc2276251caac608f9562c05e0 Mon Sep 17 00:00:00 2001 From: Jacob Mountain Date: Sat, 20 Mar 2021 11:25:09 +0000 Subject: [PATCH 3/3] Don't create a new ClientGenerator for each client --- .../client/GraphQLClientProcessor.java | 65 ++-------------- .../jacobmountain/graphql/client/Input.java | 77 +++++++++++++++++++ .../graphql/client/code/ClientGenerator.java | 76 +++++++++--------- 3 files changed, 119 insertions(+), 99 deletions(-) create mode 100644 graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java index b7103d1..21844ad 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/GraphQLClientProcessor.java @@ -3,12 +3,7 @@ import com.google.auto.service.AutoService; import com.jacobmountain.graphql.client.annotations.GraphQLClient; import com.jacobmountain.graphql.client.code.ClientGenerator; -import com.jacobmountain.graphql.client.exceptions.SchemaNotFoundException; -import com.jacobmountain.graphql.client.utils.Schema; -import com.jacobmountain.graphql.client.utils.StringUtils; -import lombok.AllArgsConstructor; import lombok.SneakyThrows; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import javax.annotation.processing.*; @@ -19,10 +14,8 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; -import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -38,6 +31,8 @@ public class GraphQLClientProcessor extends AbstractProcessor { private Filer filer; + private ClientGenerator clientGenerator; + private Messager messager; private Path root; @@ -47,6 +42,7 @@ public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.filer = processingEnv.getFiler(); this.messager = processingEnv.getMessager(); + this.clientGenerator = new ClientGenerator(this.filer); } @Override @@ -60,7 +56,7 @@ public boolean process(Set annotations, RoundEnvironment } final List interfaces = elements.stream() .map(el -> (TypeElement) el) - .map(Input::new) + .map(type -> new Input(type, getRoot(), processingEnv.getElementUtils().getPackageOf(type).toString())) .collect(toList()); interfaces.stream() .filter(new OncePerSchemaPredicate()) @@ -87,10 +83,8 @@ private void generateJavaDataClasses(Input input) { @SneakyThrows private void generateClientImplementation(Input client) { - GraphQLClient annotation = client.getAnnotation(); - log.info("Generating java implementation of {}", client.element.getSimpleName()); - new ClientGenerator(this.filer, client.getTypeMapper(), client.getPackage(), client.getSchema(), annotation.reactive()) - .generate(client.element, annotation.implSuffix()); + log.info("Generating java implementation of {}", client.getElement().getSimpleName()); + clientGenerator.generate(client); } @SneakyThrows @@ -108,51 +102,4 @@ private Path getRoot() { return root; } - @Value - @AllArgsConstructor - private class Input { - - TypeElement element; - - GraphQLClient getAnnotation() { - return element.getAnnotation(GraphQLClient.class); - } - - TypeMapper getTypeMapper() { - return new TypeMapper(getDtoPackage(), getAnnotation().mapping()); - } - - String getDtoPackage() { - return String.join(".", Arrays.asList( - getPackage(), - getAnnotation().dtoPackage() - )); - } - - String getPackage() { - return processingEnv.getElementUtils().getPackageOf(element).toString(); - } - - Schema getSchema() { - String value = getAnnotation().schema(); - File file = getSchemaFile(); - try { - if (StringUtils.hasLength(value)) { - log.info("Reading schema {}", file); - return new Schema(getSchemaFile()); - } - } catch (Exception e) { - e.printStackTrace(); - } - throw new SchemaNotFoundException(file.getPath()); - } - - File getSchemaFile() { - return getRoot().resolve(getAnnotation().schema()) - .toAbsolutePath() - .toFile(); - } - - } - } diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java new file mode 100644 index 0000000..f6b0352 --- /dev/null +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/Input.java @@ -0,0 +1,77 @@ +package com.jacobmountain.graphql.client; + +import com.jacobmountain.graphql.client.annotations.GraphQLClient; +import com.jacobmountain.graphql.client.exceptions.SchemaNotFoundException; +import com.jacobmountain.graphql.client.utils.Schema; +import com.jacobmountain.graphql.client.utils.StringUtils; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +import javax.lang.model.element.TypeElement; +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; + +@Slf4j +@Value +public class Input { + + TypeElement element; + + @Getter(value = AccessLevel.PRIVATE) + String packageStr; + + Path root; + + public Input(TypeElement element, Path root, String packageStr) { + this.element = element; + this.packageStr = packageStr; + this.root = root; + } + + public GraphQLClient getAnnotation() { + return element.getAnnotation(GraphQLClient.class); + } + + public TypeMapper getTypeMapper() { + return new TypeMapper(getDtoPackage(), getAnnotation().mapping()); + } + + public String getDtoPackage() { + return String.join(".", Arrays.asList( + getPackage(), + getAnnotation().dtoPackage() + )); + } + + public String getPackage() { + return packageStr; + } + + public Schema getSchema() { + String value = getAnnotation().schema(); + File file = getSchemaFile(); + try { + if (StringUtils.hasLength(value)) { + log.info("Reading schema {}", file); + return new Schema(getSchemaFile()); + } + } catch (Exception e) { + e.printStackTrace(); + } + throw new SchemaNotFoundException(file.getPath()); + } + + public File getSchemaFile() { + return root.resolve(getAnnotation().schema()) + .toAbsolutePath() + .toFile(); + } + + public boolean isReactive() { + return getAnnotation().reactive(); + } + +} diff --git a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java index 34a3367..464f28f 100644 --- a/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java +++ b/graphql-java-client-processor/src/main/java/com/jacobmountain/graphql/client/code/ClientGenerator.java @@ -1,5 +1,6 @@ package com.jacobmountain.graphql.client.code; +import com.jacobmountain.graphql.client.Input; import com.jacobmountain.graphql.client.PojoBuilder; import com.jacobmountain.graphql.client.TypeMapper; import com.jacobmountain.graphql.client.code.blocking.BlockingAssembler; @@ -16,11 +17,9 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import javax.annotation.processing.Filer; -import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import java.io.IOException; import java.util.List; @@ -30,55 +29,40 @@ * ClientGenerator generates the implementation of the interface annotated with @GraphQLClient */ @Slf4j -@RequiredArgsConstructor public class ClientGenerator { private final Filer filer; - private final TypeMapper typeMapper; - - private final String packageName; - - private final Schema schema; - - private final Assembler assembler; - - public ClientGenerator(Filer filer, TypeMapper typeMapper, String packageName, Schema schema, boolean reactive) { + public ClientGenerator(Filer filer) { this.filer = filer; - this.typeMapper = typeMapper; - this.packageName = packageName; - this.schema = schema; - QueryGenerator queryGenerator = new QueryGenerator(schema); - if (reactive) { - this.assembler = new ReactiveAssembler(queryGenerator, schema, typeMapper); - } else { - this.assembler = new BlockingAssembler(queryGenerator, schema, typeMapper); - } } /** * Generates the implementation of the @GraphQLClient interface * - * @param element the Element that has the @GraphQLClient on - * @param suffix the implementations suffix + * @param input the Input data required to generate the client implementation */ - public void generate(Element element, String suffix) throws IOException { - if (StringUtils.isEmpty(suffix)) { - throw new IllegalArgumentException("Invalid suffix for implementation of client: " + element.getSimpleName()); + public void generate(Input input) throws IOException { + if (StringUtils.isEmpty(input.getAnnotation().implSuffix())) { + throw new IllegalArgumentException("Invalid suffix for implementation of client: " + input.getElement().getSimpleName()); } - ClientDetails client = element.accept( + final Schema schema = input.getSchema(); + final TypeMapper typeMapper = input.getTypeMapper(); + ClientDetails client = input.getElement().accept( new ClientDetailsVisitor(), new ClientDetailsVisitorArgs(schema, typeMapper) ); + Assembler assembler = initializeAssembler(schema, typeMapper, input.isReactive()); + // Generate the class - TypeSpec.Builder builder = TypeSpec.classBuilder(client.getName() + suffix) + TypeSpec.Builder builder = TypeSpec.classBuilder(client.getName() + input.getAnnotation().implSuffix()) .addSuperinterface(client.getClientInterface()) .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationUtils.generated()); // Add type arguments to the client - for (TypeVariableName typeVariableName : this.assembler.getTypeArguments()) { + for (TypeVariableName typeVariableName : assembler.getTypeArguments()) { builder.addTypeVariable(typeVariableName); } @@ -95,10 +79,20 @@ public void generate(Element element, String suffix) throws IOException { // for each method on the interface, generate its implementation for (MethodDetails method : client.getMethods()) { - generateMethodImplementation(builder, method, client); + generateArgumentDTO(method, input.getDtoPackage()).ifPresent(builder::addType); + builder.addMethod(generateMethodImplementation(assembler, method, client)); } - writeToFile(builder.build()); + writeToFile(builder.build(), input.getPackage()); + } + + private Assembler initializeAssembler(Schema schema, TypeMapper typeMapper, boolean reactive) { + QueryGenerator queryGenerator = new QueryGenerator(schema); + if (reactive) { + return new ReactiveAssembler(queryGenerator, schema, typeMapper); + } else { + return new BlockingAssembler(queryGenerator, schema, typeMapper); + } } /** @@ -116,26 +110,28 @@ private MethodSpec generateConstructor(List variables) { /** * Generates the client implementation of one method of the interface * - * @param method the method of the @GraphQLClient annotated interface + * @param assembler the assembler that creates the code for the method + * @param method the method details + * @param client the client classes details + * @return the code of the method */ - private void generateMethodImplementation(TypeSpec.Builder clazz, MethodDetails method, ClientDetails client) { + private MethodSpec generateMethodImplementation(Assembler assembler, + MethodDetails method, + ClientDetails client) { log.info(""); log.info("{}", method); - generateArgumentDTO(method) - .ifPresent(clazz::addType); - MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getMethodName()) .returns(method.getReturnType()) .addModifiers(Modifier.PUBLIC) .addParameters(method.getParameterSpec()); - this.assembler.assemble(client, method).forEach(builder::addStatement); + assembler.assemble(client, method).forEach(builder::addStatement); - clazz.addMethod(builder.build()); + return builder.build(); } - public Optional generateArgumentDTO(MethodDetails details) { + public Optional generateArgumentDTO(MethodDetails details, String packageName) { return Optional.of(details) .filter(MethodDetails::hasParameters) .map(it -> { @@ -153,7 +149,7 @@ public Optional generateArgumentDTO(MethodDetails details) { }); } - private void writeToFile(TypeSpec spec) throws IOException { + private void writeToFile(TypeSpec spec, String packageName) throws IOException { JavaFile.builder(packageName, spec) .indent("\t") .skipJavaLangImports(true)