package com.googlecode.objectify.impl.load; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Collection; import java.util.Iterator; import java.util.Set; import com.googlecode.objectify.impl.FieldWrapper; import com.googlecode.objectify.impl.LoadContext; import com.googlecode.objectify.impl.TypeUtils; /** * <p>This is a base class for handling setter operations on collections and arrays.</p> */ abstract public class EmbeddedMultivalueSetter extends CollisionDetectingSetter { /** * The field which holds the embedded collection. We use FieldWrapper instead of * Field because we want to use methods that take a the wrapper type. */ FieldWrapper field; /** * The blah.blah.blah path to the embedded collection. This is used as * a base path to discover the null values index. */ String path; /** */ public EmbeddedMultivalueSetter(Field field, String path, Collection<String> collisionPaths) { super(collisionPaths); assert TypeUtils.isEmbedded(field); assert TypeUtils.isArrayOrCollection(field.getType()); this.field = new FieldWrapper(field); this.path = path; } /** @return the no-arg constructor of the embedded type */ protected abstract Constructor<?> getComponentConstructor(); /** * Gets the collection in the relevant field of the specified POJO, or creates (and * sets) a new one. If the field is an array type, set it up and return a Collection * facade of the array. * * @param toPojo is the entity pojo that has a field for us to set * @param size is the size of the pojo to create, if necessary */ protected abstract Collection<Object> getOrCreateCollection(Object toPojo, int size); /* (non-Javadoc) * @see com.googlecode.objectify.impl.load.CollisionDetectingSetter#safeSet(java.lang.Object, java.lang.Object, com.googlecode.objectify.impl.LoadContext) */ @Override @SuppressWarnings("unchecked") protected void safeSet(Object toPojo, Object value, LoadContext context) { // The datastore always gives us collections, never a native array if (!(value instanceof Collection<?>)) throw new IllegalStateException("Tried to load a non-collection type into embedded collection " + this.field); Collection<Object> datastoreCollection = (Collection<Object>)value; // We will need the null state set, which might be null itself Set<Integer> nullIndexes = TypeUtils.getNullIndexes(context.getEntity(), this.path); // Some nulls, some real, this is what we get int collectionSize = datastoreCollection.size(); if (nullIndexes != null) collectionSize += nullIndexes.size(); Collection<Object> embeddedMultivalue = this.getOrCreateCollection(toPojo, collectionSize); if (embeddedMultivalue.isEmpty()) { // Initialize it with relevant POJOs for (int i=0; i<collectionSize; i++) { // Make an explicit null check instead of using emptySet() to reduce autoboxing overhead if (nullIndexes != null && nullIndexes.contains(i)) { embeddedMultivalue.add(null); } else { Object embedded = TypeUtils.newInstance(this.getComponentConstructor()); embeddedMultivalue.add(embedded); } } } // There will be a datastore value for each of the non-null POJOs in the collection Iterator<Object> datastoreIterator = datastoreCollection.iterator(); for (Object embedded: embeddedMultivalue) { if (embedded != null) { Object datastoreValue = datastoreIterator.next(); this.next.set(embedded, datastoreValue, context); } } context.addProcessedEmbeddedPath(this.path); } }