package com.apollographql.apollo.internal.reader; import com.apollographql.apollo.api.Field; import com.apollographql.apollo.api.Operation; import com.apollographql.apollo.api.ResponseReader; import com.apollographql.apollo.api.ScalarType; import com.apollographql.apollo.CustomTypeAdapter; import com.apollographql.apollo.api.internal.Optional; import com.apollographql.apollo.internal.field.FieldValueResolver; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @SuppressWarnings("WeakerAccess") public final class RealResponseReader<R> implements ResponseReader { private final Operation.Variables operationVariables; private final R recordSet; private final Map<ScalarType, CustomTypeAdapter> customTypeAdapters; private final FieldValueResolver<R> fieldValueResolver; private final ResponseReaderShadow<R> readerShadow; public RealResponseReader(Operation.Variables operationVariables, R recordSet, FieldValueResolver<R> fieldValueResolver, Map<ScalarType, CustomTypeAdapter> customTypeAdapters, ResponseReaderShadow<R> readerShadow) { this.operationVariables = operationVariables; this.recordSet = recordSet; this.fieldValueResolver = fieldValueResolver; this.customTypeAdapters = customTypeAdapters; this.readerShadow = readerShadow; } @Override public <T> T read(Field field) throws IOException { final Object value; willResolve(field); switch (field.type()) { case STRING: value = readString(field); break; case INT: value = readInt(field); break; case LONG: value = readLong(field); break; case DOUBLE: value = readDouble(field); break; case BOOLEAN: value = readBoolean(field); break; case OBJECT: value = readObject((Field.ObjectField) field); break; case SCALAR_LIST: value = readScalarList((Field.ScalarListField) field); break; case OBJECT_LIST: value = readObjectList((Field.ObjectListField) field); break; case CUSTOM: value = readCustomType((Field.CustomTypeField) field); break; case CONDITIONAL: value = readConditional((Field.ConditionalTypeField) field, operationVariables); break; default: throw new IllegalArgumentException("Unsupported field type"); } didResolve(field); //noinspection unchecked return (T) value; } private void willResolve(Field field) { if (field.type() != Field.Type.CONDITIONAL) { readerShadow.willResolve(field, operationVariables); } } private void didResolve(Field field) { if (field.type() != Field.Type.CONDITIONAL) { readerShadow.didResolve(field, operationVariables); } } String readString(Field field) throws IOException { String value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); return null; } else { readerShadow.didParseScalar(value); return value; } } Integer readInt(Field field) throws IOException { BigDecimal value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); return null; } else { readerShadow.didParseScalar(value); return value.intValue(); } } Long readLong(Field field) throws IOException { BigDecimal value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); return null; } else { readerShadow.didParseScalar(value); return value.longValue(); } } Double readDouble(Field field) throws IOException { BigDecimal value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); return null; } else { readerShadow.didParseScalar(value); return value.doubleValue(); } } Boolean readBoolean(Field field) throws IOException { Boolean value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); return null; } else { readerShadow.didParseScalar(value); return value; } } @SuppressWarnings("unchecked") <T> T readObject(Field.ObjectField field) throws IOException { R value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); readerShadow.willParseObject(field, Optional.fromNullable(value)); if (value == null) { readerShadow.didParseNull(); return null; } else { final T parsedValue = (T) field.objectReader().read(new RealResponseReader(operationVariables, value, fieldValueResolver, customTypeAdapters, readerShadow)); readerShadow.didParseObject(field, Optional.fromNullable(value)); return parsedValue; } } @SuppressWarnings("unchecked") <T> List<T> readScalarList(Field.ScalarListField field) throws IOException { List values = fieldValueResolver.valueFor(recordSet, field); checkValue(values, field.optional()); if (values == null) { readerShadow.didParseNull(); return null; } else { List<T> result = new ArrayList<>(); for (int i = 0; i < values.size(); i++) { readerShadow.willParseElement(i); Object value = values.get(i); T item = (T) field.listReader().read(new ListItemReader(value, customTypeAdapters)); readerShadow.didParseScalar(value); readerShadow.didParseElement(i); result.add(item); } readerShadow.didParseList(values); return Collections.unmodifiableList(result); } } @SuppressWarnings("unchecked") <T> List<T> readObjectList(Field.ObjectListField field) throws IOException { List<R> values = fieldValueResolver.valueFor(recordSet, field); checkValue(values, field.optional()); if (values == null) { return null; } else { List<T> result = new ArrayList<>(); for (int i = 0; i < values.size(); i++) { readerShadow.willParseElement(i); R value = values.get(i); readerShadow.willParseObject(field, Optional.fromNullable(value)); T item = (T) field.objectReader().read(new RealResponseReader(operationVariables, value, fieldValueResolver, customTypeAdapters, readerShadow)); readerShadow.didParseObject(field, Optional.fromNullable(value)); readerShadow.didParseElement(i); result.add(item); } readerShadow.didParseList(values); return Collections.unmodifiableList(result); } } @SuppressWarnings("unchecked") private <T> T readCustomType(Field.CustomTypeField field) throws IOException { Object value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); return null; } else { CustomTypeAdapter<T> typeAdapter = customTypeAdapters.get(field.scalarType()); if (typeAdapter == null) { readerShadow.didParseScalar(value); return (T) value; } else { readerShadow.didParseScalar(value); return typeAdapter.decode(value.toString()); } } } @SuppressWarnings("unchecked") private <T> T readConditional(Field.ConditionalTypeField field, Operation.Variables variables) throws IOException { readerShadow.willResolve(field, variables); String value = fieldValueResolver.valueFor(recordSet, field); checkValue(value, field.optional()); if (value == null) { readerShadow.didParseNull(); readerShadow.didResolve(field, variables); return null; } else { readerShadow.didParseScalar(value); readerShadow.didResolve(field, variables); return (T) field.conditionalTypeReader().read(value, this); } } private void checkValue(Object value, boolean optional) { if (!optional && value == null) { throw new NullPointerException("corrupted response reader, expected non null value"); } } private static class ListItemReader implements Field.ListItemReader { private final Object value; private final Map<ScalarType, CustomTypeAdapter> customTypeAdapters; ListItemReader(Object value, Map<ScalarType, CustomTypeAdapter> customTypeAdapters) { this.value = value; this.customTypeAdapters = customTypeAdapters; } @Override public String readString() throws IOException { return (String) value; } @Override public Integer readInt() throws IOException { return ((BigDecimal) value).intValue(); } @Override public Long readLong() throws IOException { return ((BigDecimal) value).longValue(); } @Override public Double readDouble() throws IOException { return ((BigDecimal) value).doubleValue(); } @Override public Boolean readBoolean() throws IOException { return (Boolean) value; } @SuppressWarnings("unchecked") @Override public <T> T readCustomType(ScalarType scalarType) throws IOException { CustomTypeAdapter<T> typeAdapter = customTypeAdapters.get(scalarType); if (typeAdapter == null) { throw new RuntimeException("Can't resolve custom type adapter for " + scalarType.typeName()); } return typeAdapter.decode(value.toString()); } } }