package com.apollographql.apollo.internal.cache.normalized;
import com.apollographql.apollo.api.Field;
import com.apollographql.apollo.api.Operation;
import com.apollographql.apollo.api.internal.Optional;
import com.apollographql.apollo.cache.normalized.CacheKey;
import com.apollographql.apollo.cache.normalized.CacheKeyResolver;
import com.apollographql.apollo.cache.normalized.CacheReference;
import com.apollographql.apollo.cache.normalized.Record;
import com.apollographql.apollo.cache.normalized.RecordSet;
import com.apollographql.apollo.internal.reader.ResponseReaderShadow;
import com.apollographql.apollo.internal.util.SimpleStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public abstract class ResponseNormalizer<R> implements ResponseReaderShadow<R> {
private SimpleStack<List<String>> pathStack;
private SimpleStack<Record> recordStack;
private SimpleStack<Object> valueStack;
private List<String> path;
private Record.Builder currentRecordBuilder;
private RecordSet recordSet = new RecordSet();
private Set<String> dependentKeys = Collections.emptySet();
public Collection<Record> records() {
return recordSet.allRecords();
}
public Set<String> dependentKeys() {
return dependentKeys;
}
@Override public void willResolveRootQuery(Operation operation) {
pathStack = new SimpleStack<>();
recordStack = new SimpleStack<>();
valueStack = new SimpleStack<>();
dependentKeys = new HashSet<>();
path = new ArrayList<>();
currentRecordBuilder = Record.builder(CacheKeyResolver.rootKeyForOperation(operation).key());
recordSet = new RecordSet();
}
@Override public void willResolve(Field field, Operation.Variables variables) {
String key = field.cacheKey(variables);
path.add(key);
}
@Override public void didResolve(Field field, Operation.Variables variables) {
path.remove(path.size() - 1);
Object value = valueStack.pop();
String cacheKey = field.cacheKey(variables);
String dependentKey = currentRecordBuilder.key() + "." + cacheKey;
dependentKeys.add(dependentKey);
currentRecordBuilder.addField(cacheKey, value);
if (recordStack.isEmpty()) {
recordSet.merge(currentRecordBuilder.build());
}
}
@Override public void didParseScalar(@Nullable Object value) {
valueStack.push(value);
}
@Override public void willParseObject(Field field, Optional<R> objectSource) {
pathStack.push(path);
CacheKey cacheKey = objectSource.isPresent() ? resolveCacheKey(field, objectSource.get()) : CacheKey.NO_KEY;
String cacheKeyValue = cacheKey.key();
if (cacheKey == CacheKey.NO_KEY) {
cacheKeyValue = pathToString();
} else {
path = new ArrayList<>();
path.add(cacheKeyValue);
}
recordStack.push(currentRecordBuilder.build());
currentRecordBuilder = Record.builder(cacheKeyValue);
}
@Override public void didParseObject(Field field, Optional<R> objectSource) {
path = pathStack.pop();
Record completedRecord = currentRecordBuilder.build();
valueStack.push(new CacheReference(completedRecord.key()));
dependentKeys.add(completedRecord.key());
recordSet.merge(completedRecord);
currentRecordBuilder = recordStack.pop().toBuilder();
}
@Override public void didParseList(List array) {
List<Object> parsedArray = new ArrayList<>(array.size());
for (int i = 0, size = array.size(); i < size; i++) {
parsedArray.add(0, valueStack.pop());
}
valueStack.push(parsedArray);
}
@Override public void willParseElement(int atIndex) {
path.add(Integer.toString(atIndex));
}
@Override public void didParseElement(int atIndex) {
path.remove(path.size() - 1);
}
@Override public void didParseNull() {
valueStack.push(null);
}
@Nonnull public abstract CacheKey resolveCacheKey(@Nonnull Field field, @Nonnull R record);
private String pathToString() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, size = path.size(); i < size; i++) {
String pathPiece = path.get(i);
stringBuilder.append(pathPiece);
if (i < size - 1) {
stringBuilder.append(".");
}
}
return stringBuilder.toString();
}
@SuppressWarnings("unchecked") public static final ResponseNormalizer NO_OP_NORMALIZER = new ResponseNormalizer() {
@Override public void willResolveRootQuery(Operation operation) {
}
@Override public void willResolve(Field field, Operation.Variables variables) {
}
@Override public void didResolve(Field field, Operation.Variables variables) {
}
@Override public void didParseScalar(Object value) {
}
@Override public void willParseObject(Field field, Optional objectSource) {
}
@Override public void didParseObject(Field field, Optional objectSource) {
}
@Override public void didParseList(List array) {
}
@Override public void willParseElement(int atIndex) {
}
@Override public void didParseElement(int atIndex) {
}
@Override public void didParseNull() {
}
@Override public Collection<Record> records() {
return Collections.emptyList();
}
@Override public Set<String> dependentKeys() {
return Collections.emptySet();
}
@Nonnull @Override public CacheKey resolveCacheKey(@Nonnull Field field, @Nonnull Object record) {
return CacheKey.NO_KEY;
}
};
}