Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Mapper generation to use factories #156

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,70 @@
import java.util.Collections;
import java.util.Map;

/** TODO */
public final class Field {
/** TODO add javadocs */
public class Field {
private final Type type;
private final String responseName;
private final String fieldName;
private final Map<String, Object> arguments;
private final ObjectReader objectReader;
private final ListReader listReader;
private final boolean optional;
private final ScalarType scalarType;

public static Field forString(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional) {
return new Field(Type.STRING, responseName, fieldName, arguments, null, null, optional, null);
return new Field(Type.STRING, responseName, fieldName, arguments, optional);
}

public static Field forInt(String responseName, String fieldName, Map<String, Object> arguments, boolean optional) {
return new Field(Type.INT, responseName, fieldName, arguments, null, null, optional, null);
return new Field(Type.INT, responseName, fieldName, arguments, optional);
}

public static <T> Field forLong(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional) {
return new Field(Type.LONG, responseName, fieldName, arguments, null, null, optional, null);
return new Field(Type.LONG, responseName, fieldName, arguments, optional);
}

public static Field forDouble(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional) {
return new Field(Type.DOUBLE, responseName, fieldName, arguments, null, null, optional, null);
return new Field(Type.DOUBLE, responseName, fieldName, arguments, optional);
}

public static Field forBoolean(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional) {
return new Field(Type.BOOLEAN, responseName, fieldName, arguments, null, null, optional, null);
return new Field(Type.BOOLEAN, responseName, fieldName, arguments, optional);
}

public static <T> Field forObject(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional, ObjectReader<T> objectReader) {
return new Field(Type.OBJECT, responseName, fieldName, arguments, objectReader, null, optional, null);
return new ObjectField(responseName, fieldName, arguments, optional, objectReader);
}

public static <T> Field forList(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional, ListReader<T> listReader) {
return new Field(Type.LIST, responseName, fieldName, arguments, null, listReader, optional, null);
return new ScalarListField(responseName, fieldName, arguments, optional, listReader);
}

public static <T> Field forList(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional, ObjectReader<T> objectReader) {
return new Field(Type.LIST, responseName, fieldName, arguments, objectReader, null, optional, null);
return new ObjectListField(responseName, fieldName, arguments, optional, objectReader);
}

public static <T> Field forCustomType(String responseName, String fieldName, Map<String, Object> arguments,
boolean optional, ScalarType scalarType) {
return new Field(Type.CUSTOM, responseName, fieldName, arguments, null, null, optional, scalarType);
return new CustomTypeField(responseName, fieldName, arguments, optional, scalarType);
}

private Field(Type type, String responseName, String fieldName, Map<String, Object> arguments,
ObjectReader objectReader, ListReader listReader, boolean optional, ScalarType scalarType) {
public static <T> Field forConditionalType(String responseName, String fieldName,
ConditionalTypeReader<T> conditionalTypeReader) {
return new ConditionalTypeField(responseName, fieldName, conditionalTypeReader);
}

private Field(Type type, String responseName, String fieldName, Map<String, Object> arguments, boolean optional) {
this.type = type;
this.responseName = responseName;
this.fieldName = fieldName;
this.arguments = arguments == null ? Collections.<String, Object>emptyMap()
: Collections.unmodifiableMap(arguments);
this.objectReader = objectReader;
this.listReader = listReader;
this.optional = optional;
this.scalarType = scalarType;
}

public Type type() {
Expand All @@ -88,31 +86,21 @@ public Map<String, Object> arguments() {
return arguments;
}

public ObjectReader objectReader() {
return objectReader;
}

public boolean optional() {
return optional;
}

public ListReader listReader() {
return listReader;
}

public ScalarType scalarType() {
return scalarType;
}

public static enum Type {
STRING,
INT,
LONG,
DOUBLE,
BOOLEAN,
OBJECT,
LIST,
CUSTOM
SCALAR_LIST,
OBJECT_LIST,
CUSTOM,
CONDITIONAL
}

public interface ObjectReader<T> {
Expand All @@ -123,6 +111,10 @@ public interface ListReader<T> {
T read(ListItemReader reader) throws IOException;
}

public interface ConditionalTypeReader<T> {
T read(String conditionalType, ResponseReader reader) throws IOException;
}

public interface ListItemReader {

String readString() throws IOException;
Expand All @@ -137,4 +129,73 @@ public interface ListItemReader {

<T> T readCustomType(ScalarType scalarType) throws IOException;
}

public static final class ObjectField extends Field {
private final ObjectReader objectReader;

ObjectField(String responseName, String fieldName, Map<String, Object> arguments, boolean optional,
ObjectReader objectReader) {
super(Type.OBJECT, responseName, fieldName, arguments, optional);
this.objectReader = objectReader;
}

public ObjectReader objectReader() {
return objectReader;
}
}

public static final class ScalarListField extends Field {
private final ListReader listReader;

ScalarListField(String responseName, String fieldName, Map<String, Object> arguments, boolean optional,
ListReader listReader) {
super(Type.SCALAR_LIST, responseName, fieldName, arguments, optional);
this.listReader = listReader;
}

public ListReader listReader() {
return listReader;
}
}

public static final class ObjectListField extends Field {
private final ObjectReader objectReader;

ObjectListField(String responseName, String fieldName, Map<String, Object> arguments, boolean optional,
ObjectReader objectReader) {
super(Type.OBJECT_LIST, responseName, fieldName, arguments, optional);
this.objectReader = objectReader;
}

public ObjectReader objectReader() {
return objectReader;
}
}

public static final class CustomTypeField extends Field {
private final ScalarType scalarType;

CustomTypeField(String responseName, String fieldName, Map<String, Object> arguments, boolean optional,
ScalarType scalarType) {
super(Type.CUSTOM, responseName, fieldName, arguments, optional);
this.scalarType = scalarType;
}

public ScalarType scalarType() {
return scalarType;
}
}

public static final class ConditionalTypeField extends Field {
private final ConditionalTypeReader conditionalTypeReader;

ConditionalTypeField(String responseName, String fieldName, ConditionalTypeReader conditionalTypeReader) {
super(Type.CONDITIONAL, responseName, fieldName, null, false);
this.conditionalTypeReader = conditionalTypeReader;
}

public ConditionalTypeReader conditionalTypeReader() {
return conditionalTypeReader;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

/** TODO */
public interface ResponseFieldMapper<T> {
void map(final ResponseReader responseReader, final T instance) throws IOException;
T map(final ResponseReader responseReader) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.apollographql.android.compiler

import com.apollographql.android.api.graphql.ResponseFieldMapper
import com.apollographql.android.api.graphql.ResponseReader
import com.apollographql.android.compiler.ir.CodeGenerationContext
import com.apollographql.android.compiler.ir.Fragment
import com.squareup.javapoet.*
import java.io.IOException
import javax.annotation.Nonnull
import javax.lang.model.element.Modifier

/**
* Responsible for [Fragments.Mapper] class generation
*
* Example of generated class:
*
* ```
* public static final class Mapper implements ResponseFieldMapper<Fragments> {
* final Factory factory;
*
* String conditionalType;
*
* public Mapper(@Nonnull Factory factory, @Nonnull String conditionalType) {
* this.factory = factory;
* this.conditionalType = conditionalType;
* }
*
* @Override
* public Fragments map(ResponseReader reader) throws IOException {
* HeroDetails heroDetails = null;
* if (conditionalType.equals(HeroDetails.TYPE_CONDITION)) {
* heroDetails = new HeroDetails.Mapper(factory.heroDetailsFactory()).map(reader);
* }
* return factory.creator().create(heroDetails);
* }
* }
*
*```
*/
class FragmentsResponseMapperBuilder(
val fragments: List<String>,
val codeGenerationContext: CodeGenerationContext
) {
fun build(): TypeSpec {
val fragmentFields = fragments.map { FieldSpec.builder(fragmentType(it), it.decapitalize()).build() }
return TypeSpec.classBuilder("Mapper")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.addSuperinterface(RESPONSE_FIELD_MAPPER_TYPE)
.addMethod(constructor())
.addField(FACTORY_FIELD)
.addField(FieldSpec.builder(CONDITIONAL_TYPE_PARAM.type, CONDITIONAL_TYPE_PARAM.name).build())
.addMethod(mapMethod(fragmentFields))
.build()
}

private fun fragmentType(fragmentName: String) =
ClassName.get(codeGenerationContext.fragmentsPackage, fragmentName.capitalize())

private fun constructor(): MethodSpec =
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(FACTORY_PARAM)
.addParameter(CONDITIONAL_TYPE_PARAM)
.addStatement("this.$FACTORY_VAR = $FACTORY_VAR")
.addStatement("this.$CONDITIONAL_TYPE_VAR = $CONDITIONAL_TYPE_VAR")
.build()

private fun mapMethod(fragmentFields: List<FieldSpec>) =
MethodSpec.methodBuilder("map")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override::class.java)
.addParameter(READER_PARAM)
.addException(IOException::class.java)
.returns(SchemaTypeSpecBuilder.FRAGMENTS_TYPE)
.addCode(mapMethodCode(fragmentFields))
.build()

private fun mapMethodCode(fragmentFields: List<FieldSpec>) =
CodeBlock.builder()
.add(initFragmentsCode(fragmentFields))
.add(createFragmentsCode(fragmentFields))
.add(");\n")
.build()

private fun initFragmentsCode(fragmentFields: List<FieldSpec>) =
CodeBlock.builder()
.add(fragmentFields
.fold(CodeBlock.builder()) { builder, field -> builder.addStatement("\$T \$N = null", field.type, field) }
.build())
.add(fragmentFields
.fold(CodeBlock.builder()) { builder, field -> builder.add(initFragmentCode(field)) }
.build())
.build()

private fun initFragmentCode(fragmentField: FieldSpec): CodeBlock {
val fragmentFieldTypeName = (fragmentField.type as ClassName).simpleName()
val factoryAccessorMethodName = fragmentFieldTypeName.decapitalize() + Util.FACTORY_TYPE_NAME
return CodeBlock.builder()
.beginControlFlow("if ($CONDITIONAL_TYPE_VAR.equals(\$T.${Fragment.TYPE_CONDITION_FIELD_NAME}))",
fragmentField.type)
.addStatement("\$N = new \$T.Mapper($FACTORY_VAR.$factoryAccessorMethodName()).map($READER_VAR)", fragmentField,
fragmentField.type)
.endControlFlow()
.build()
}

private fun createFragmentsCode(fragmentFields: List<FieldSpec>) =
CodeBlock.builder()
.add("return $FACTORY_VAR.${Util.FACTORY_CREATOR_ACCESS_METHOD_NAME}().${Util.CREATOR_CREATE_METHOD_NAME}(")
.add(fragmentFields
.mapIndexed { i, fieldSpec -> CodeBlock.of("\$L\$L", if (i > 0) ", " else "", fieldSpec.name) }
.fold(CodeBlock.builder(), CodeBlock.Builder::add)
.build())
.build()

companion object {
private val API_RESPONSE_FIELD_MAPPER_TYPE = ClassName.get(ResponseFieldMapper::class.java)
private val RESPONSE_FIELD_MAPPER_TYPE = ParameterizedTypeName.get(API_RESPONSE_FIELD_MAPPER_TYPE,
SchemaTypeSpecBuilder.FRAGMENTS_TYPE)
private val FACTORY_VAR = Util.FACTORY_TYPE_NAME.decapitalize()
private val FACTORY_PARAM = ParameterSpec.builder(Util.FACTORY_INTERFACE_TYPE, FACTORY_VAR)
.addAnnotation(Nonnull::class.java).build()
private val CONDITIONAL_TYPE_VAR = "conditionalType"
private val CONDITIONAL_TYPE_PARAM = ParameterSpec.builder(String::class.java, CONDITIONAL_TYPE_VAR)
.addAnnotation(Nonnull::class.java).build()
private val FACTORY_FIELD = FieldSpec.builder(Util.FACTORY_INTERFACE_TYPE, Util.FACTORY_TYPE_NAME.decapitalize(),
Modifier.FINAL).build()
private val READER_VAR = "reader"
private val READER_PARAM = ParameterSpec.builder(ResponseReader::class.java, READER_VAR).build()
}
}
Loading