package com.apollographql.apollo.cache.normalized; import com.apollographql.apollo.internal.cache.normalized.RecordWeigher; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * A normalized entry that corresponds to a response object. Object fields are stored * if they are a GraphQL Scalars. If a field is a GraphQL Object a {@link CacheReference} will be stored instead. */ public final class Record { private final String key; private final Map<String, Object> fields; private static final int UNKNOWN_SIZE_ESTIMATE = -1; private volatile int sizeInBytes = UNKNOWN_SIZE_ESTIMATE; public static class Builder { private final Map<String, Object> fields; private final String key; public Builder(String key) { this(key, new LinkedHashMap<String, Object>()); } public Builder(String key, Map<String, Object> fields) { this.key = key; this.fields = fields; } public Builder addField(String key, Object value) { fields.put(key, value); return this; } public String key() { return key; } public Record build() { return new Record(key, fields); } } public static Builder builder(String key) { return new Builder(key); } public Builder toBuilder() { return new Builder(key(), this.fields); } public Record(String cacheKey) { this.key = cacheKey; fields = new LinkedHashMap<>(); } public Record(String key, Map<String, Object> fields) { this.key = key; this.fields = fields; } public Object field(String fieldKey) { return fields.get(fieldKey); } public boolean hasField(String fieldKey) { return fields.containsKey(fieldKey); } public String key() { return key; } /** * @param otherRecord The record to merge into this record. * @return A set of field keys which have changed, or were added. A field key incorporates any GraphQL arguments in * addition to the field name. */ public Set<String> mergeWith(Record otherRecord) { Set<String> changedKeys = new HashSet<>(); for (Map.Entry<String, Object> field : otherRecord.fields.entrySet()) { Object newFieldValue = field.getValue(); Object oldFieldValue = this.fields.get(field.getKey()); if ((oldFieldValue == null && newFieldValue != null) || (oldFieldValue != null && !oldFieldValue.equals(newFieldValue))) { this.fields.put(field.getKey(), newFieldValue); changedKeys.add(key() + "." + field.getKey()); adjustSizeEstimate(newFieldValue, oldFieldValue); } } return changedKeys; } /** * @return A map of fieldName to fieldValue. Where fieldValue is a GraphQL Scalar or {@link CacheReference} if it is a * GraphQL Object type. */ public Map<String, Object> fields() { return fields; } /** * @return An approximate number of bytes this Record takes up. */ public int sizeEstimateBytes() { if (sizeInBytes == UNKNOWN_SIZE_ESTIMATE) { sizeInBytes = RecordWeigher.calculateBytes(this); } return sizeInBytes; } private void adjustSizeEstimate(Object newFieldValue, Object oldFieldValue) { if (sizeInBytes != UNKNOWN_SIZE_ESTIMATE) { sizeInBytes += RecordWeigher.byteChange(newFieldValue, oldFieldValue); } } }