package com.googlecode.objectify.repackaged.gentyref;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

/**
 * Utility class for creating instances of {@link Type}.
 * These types can be used with the {@link GenericTypeReflector} or anything else handling Java types.
 *
 * @author Wouter Coekaerts <wouter@coekaerts.be>
 */
public class TypeFactory {
	private static final WildcardType UNBOUND_WILDCARD = new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{});

	private TypeFactory() {
	}

	/**
	 * Creates a type of class <tt>clazz</tt> with <tt>arguments</tt> as type arguments.
	 *

	 * For example: <tt>parameterizedClass(Map.class, Integer.class, String.class)</tt>
	 * returns the type <tt>Map<Integer, String></tt>.
	 *
	 * @param clazz Type class of the type to create
	 * @param arguments Type arguments for the variables of <tt>clazz</tt>, or null if these are not known.
	 * @return A {@link ParameterizedType},
	 *         or simply <tt>clazz</tt> if <tt>arguments</tt> is <tt>null</tt> or empty.
	 */
	public static Type parameterizedClass(Class<?> clazz, Type... arguments) {
		return parameterizedInnerClass(null, clazz, arguments);
	}

	/**
	 * Creates a type of <tt>clazz</tt> nested in <tt>owner</tt>.
	 *
	 * @param owner The owner type. This should be a subtype of <tt>clazz.getDeclaringClass()</tt>,
	 * 		or <tt>null</tt> if no owner is known.
	 * @param clazz Type class of the type to create
	 * @return A {@link ParameterizedType} if the class declaring <tt>clazz</tt> is generic and its type parameters
	 * 	are known in <tt>owner</tt> and <tt>clazz</tt> itself has no type parameters.
	 * 	Otherwise, just returns <tt>clazz</tt>.
	 */
	public static Type innerClass(Type owner, Class<?> clazz) {
		return parameterizedInnerClass(owner, clazz, (Type[])null);
	}

	/**
	 * Creates a type of <tt>clazz</tt> with <tt>arguments</tt> as type arguments, nested in <tt>owner</tt>.
	 *

	 * In the ideal case, this returns a {@link ParameterizedType} with all generic information in it.
	 * If some type arguments are missing or if the resulting type simply doesn't need any type parameters,
	 * it returns the raw <tt>clazz</tt>.
	 * Note that types with some parameters specified and others not, don't exist in Java.
	 *

	 * If the caller does not know the exact <tt>owner</tt> type or <tt>arguments</tt>, <tt>null</tt> should be given
	 * (or {@link #parameterizedClass(Class, Type...)} or {@link #innerClass(Type, Class)} could be used).
	 * If they are not needed (non-generic owner and/or <tt>clazz</tt> has no type parameters), they will be filled in
	 * automatically. If they are needed but are not given, the raw <tt>clazz</tt> is returned.
	 *

	 * The specified <tt>owner</tt> may be any subtype of <tt>clazz.getDeclaringClass()</tt>. It is automatically
	 * converted into the right parameterized version of the declaring class.
	 * If <tt>clazz</tt> is a <tt>static</tt> (nested) class, the owner is not used.
	 *
	 * @param owner The owner type. This should be a subtype of <tt>clazz.getDeclaringClass()</tt>,
	 * 	or <tt>null</tt> if no owner is known.
	 * @param clazz Type class of the type to create
	 * @param arguments Type arguments for the variables of <tt>clazz</tt>, or null if these are not known.
	 * @return A {@link ParameterizedType} if <tt>clazz</tt> or the class declaring <tt>clazz</tt> is generic,
	 * 	and all the needed type arguments are specified in <tt>owner</tt> and <tt>arguments</tt>.
	 * 	Otherwise, just returns <tt>clazz</tt>.
	 * @throws IllegalArgumentException if <tt>arguments</tt> (is non-null and) has an incorrect length,
	 *  or if one of the <tt>arguments</tt> is not within the bounds declared on the matching type variable,
	 * 	or if owner is non-null but <tt>clazz</tt> has no declaring class (e.g. is a top-level class),
	 *  or if owner is not a a subtype of <tt>clazz.getDeclaringClass()</tt>.
	 * @throws NullPointerException if <tt>clazz</tt> or one of the elements in <tt>arguments</tt> is null.
	 */
	public static Type parameterizedInnerClass(Type owner, Class<?> clazz, Type... arguments) {
		// never allow an owner on a class that doesn't have one
		if (clazz.getDeclaringClass() == null && owner != null) {
			throw new IllegalArgumentException("Cannot specify an owner type for a top level class");
		}

		Type realOwner = transformOwner(owner, clazz);

		if (arguments == null) {
			if (clazz.getTypeParameters().length == 0) {
				// no arguments known, but no needed so just use an empty argument list.
				// (we can still end up with a generic type if the owner is generic)
				arguments = new Type[0];
			} else {
				// missing type arguments, return the raw type
				return clazz;
			}
		} else {
			if (arguments.length != clazz.getTypeParameters().length) {
				throw new IllegalArgumentException("Incorrect number of type arguments for [" + clazz + "]: " +
						"expected " + clazz.getTypeParameters().length + ", but got " + arguments.length);
			}
		}

		// if the class and its owner simply have no parameters at all, this is not a parameterized type
		if (!GenericTypeReflector.isMissingTypeParameters(clazz)) {
			return clazz;
		}

		// if the owner type is missing type parameters and clazz is non-static, this is a raw type
		if (realOwner != null && !Modifier.isStatic(clazz.getModifiers())
				&& GenericTypeReflector.isMissingTypeParameters(realOwner)) {
			return clazz;
		}

		ParameterizedType result = new ParameterizedTypeImpl(clazz, arguments, realOwner);
		checkParametersWithinBound(result);
		return result;
	}

	/**
	 * Check if the type arguments of the given type are within the bounds declared on the type parameters.
	 * Only the type arguments of the type itself are checked, the possible owner type is assumed to be valid.
	 *

	 * It does not follow the checks defined in the
	 * JLS because there are
	 * several problems with those (see http://stackoverflow.com/questions/7003009 for one).
	 * Instead, this applies some intuition and follows what Java compilers seem to do.
	 *
	 * @param type possibly inconsistent type to check.
	 * @throws IllegalArgumentException if the type arguments are not within the bounds
	 */
	private static void checkParametersWithinBound(ParameterizedType type) {
		Type[] arguments = type.getActualTypeArguments();
		TypeVariable<?>[] typeParameters = ((Class<?>)type.getRawType()).getTypeParameters();

		// a map of type arguments in the type, to fill in variables in the bounds
		VarMap varMap = new VarMap(type);

		// for every bound on every parameter
		for (int i = 0; i < arguments.length; i++) {
			for (Type bound : typeParameters[i].getBounds()) {
				// replace type variables in the bound by their value
				Type replacedBound = varMap.map(bound);

				if (arguments[i] instanceof WildcardType) {
					WildcardType wildcardTypeParameter = (WildcardType) arguments[i];

					// Check if a type satisfying both the bounds of the variable and of the wildcard could exist

					// upper bounds must not be mutually exclusive
					for (Type wildcardUpperBound : wildcardTypeParameter.getUpperBounds()) {
						if (!couldHaveCommonSubtype(replacedBound, wildcardUpperBound)) {
							throw new TypeArgumentNotInBoundException(arguments[i], typeParameters[i], bound);
						}
					}
					// a lowerbound in the wildcard must satisfy every upperbound
					for (Type wildcardLowerBound : wildcardTypeParameter.getLowerBounds()) {
						if (!GenericTypeReflector.isSuperType(replacedBound, wildcardLowerBound)) {
							throw new TypeArgumentNotInBoundException(arguments[i], typeParameters[i], bound);
						}
					}
				} else {
					if (! GenericTypeReflector.isSuperType(replacedBound, arguments[i])) {
						throw new TypeArgumentNotInBoundException(arguments[i], typeParameters[i], bound);
					}
				}
			}
		}
	}

	/**
	 * Checks if the intersection of two types is not empty.
	 */
	private static boolean couldHaveCommonSubtype(Type type1, Type type2) {
		// this is an optimistically naive implementation.
		// if they are parameterized types their parameters need to be checked,...
		// so we're just a bit too lenient here

		Class<?> erased1 = GenericTypeReflector.erase(type1);
		Class<?> erased2 = GenericTypeReflector.erase(type2);
		// if they are both classes
		if (!erased1.isInterface() &&  !erased2.isInterface()) {
			// then one needs to be a subclass of another
			if (!erased1.isAssignableFrom(erased2) && !erased2.isAssignableFrom(erased1)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Transforms the given owner type into an appropriate one when constructing a parameterized type.
	 */
	private static Type transformOwner(Type givenOwner, Class<?> clazz) {
		if (givenOwner == null) {
			// be lenient: if this is an inner class but no owner was specified, assume a raw owner type
			// (or if there is no owner just return null)
			return clazz.getDeclaringClass();
		} else {
			// If the specified owner is not of the declaring class' type, but instead a subtype,
			// transform it into the declaring class with the exact type parameters.
			// For example with "class StringOuter extends GenericOuter<String>", transform
			// "StringOuter.Inner" into "GenericOuter<String>.Inner", just like the Java compiler does.
			Type transformedOwner = GenericTypeReflector.getExactSuperType(givenOwner, clazz.getDeclaringClass());

			if (transformedOwner == null) { // null means it's not a supertype
				throw new IllegalArgumentException("Given owner type [" + givenOwner + "] is not appropriate for ["
						+ clazz + "]: it should be a subtype of " + clazz.getDeclaringClass());
			}

			if (Modifier.isStatic(clazz.getModifiers())) {
				// for a static inner class, the owner shouldn't have type parameters
				return GenericTypeReflector.erase(transformedOwner);
			} else {
				return transformedOwner;
			}
		}
	}

	/**
	 * Returns the wildcard type without bounds.
	 * This is the '<tt>?</tt>' in for example <tt>List<?&gt</tt>.
	 *
	 * @return The unbound wildcard type
	 */
	public static WildcardType unboundWildcard() {
		return UNBOUND_WILDCARD;
	}

	/**
	 * Creates a wildcard type with an upper bound.
	 *

	 * For example <tt>wildcardExtends(String.class)</tt> returns the type <tt>? extends String</tt>.
	 *
	 * @param upperBound Upper bound of the wildcard
	 * @return A wildcard type
	 */
	public static WildcardType wildcardExtends(Type upperBound) {
		if (upperBound == null) {
			throw new NullPointerException();
		}
		return new WildcardTypeImpl(new Type[]{upperBound}, new Type[]{});
	}

	/**
	 * Creates a wildcard type with a lower bound.
	 *

	 * For example <tt>wildcardSuper(String.class)</tt> returns the type <tt>? super String</tt>.
	 *
	 * @param lowerBound Lower bound of the wildcard
	 * @return A wildcard type
	 */
	public static WildcardType wildcardSuper(Type lowerBound) {
		if (lowerBound == null) {
			throw new NullPointerException();
		}
		return new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBound});
	}

	/**
	 * Creates a array type.
	 *

	 * If <tt>componentType</tt> is not a generic type but a {@link Class} object,
	 * this returns the {@link Class} representing the non-generic array type.
	 * Otherwise, returns a {@link GenericArrayType}.
	 *

	 * For example:

	 *
<tt>arrayOf(String.class)</tt> returns <tt>String[].class</tt>

	 *
<tt>arrayOf(parameterizedClass(List.class, String.class))</tt> returns the {@link GenericArrayType}
	 *     for <tt>List<String>[]</tt>
	 *

	 *
	 * @param componentType The type of the components of the array.
	 * @return An array type.
	 */
	public static Type arrayOf(Type componentType) {
		return GenericArrayTypeImpl.createArrayType(componentType);
	}

}


package com.googlecode.objectify.impl.translate;

import com.googlecode.objectify.impl.Property;
import com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector;
import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

/**
 *
Unique identifier for a translator instance. Important so we can re-use translators
 * as we navigate the tree, allowing us to store recursive structures.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Data
@RequiredArgsConstructor
public class TypeKey<T>
{
	/** */
	private static final Annotation[] NO_ANNOTATIONS = {};

	/** */
	private final Type type;

	/** */
	private final Annotation[] annotations;

	/** */
	public TypeKey(Type type) {
		this(type, NO_ANNOTATIONS);
	}

	/**
	 * Create a new typekey for a new type but which preserves characteristics of the old typekey (ie annotations).
	 * This is used when creating a new typekey for component types.
	 */
	public TypeKey(Type type, TypeKey previous) {
		this(type, previous.getAnnotations());
	}

	/**
	 * Create a typekey from a property
	 */
	public TypeKey(Property prop) {
		this(prop.getType(), prop.getAnnotations());
	}

	/**
	 * Get the basic class through erasure.
	 */
	@SuppressWarnings("unchecked")
	public Class<T> getTypeAsClass() {
		return (Class<T>)GenericTypeReflector.erase(type);
	}

	/** Gets the annotation that has the specified type, or null if there isn't one */
	@SuppressWarnings("unchecked")
	public  A getAnnotation(Class annotationType) {
		for (Annotation anno: annotations)
			if (annotationType.isAssignableFrom(anno.getClass()))
				return (A)anno;

		return null;
	}

	/** @return true if the annotation is present */
	public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
		return getAnnotation(annotationType) != null;
	}

	/**
	 * Checks not only the listed annotations but also annotations on the class.
	 */
	public  A getAnnotationAnywhere(Class annotationType) {
		A anno = getAnnotation(annotationType);
		if (anno == null) {
			Class<?> clazz = (Class<?>) GenericTypeReflector.erase(type);
			return clazz.getAnnotation(annotationType);
		} else {
			return anno;
		}
	}

	/**
	 * Can this type be assigned to a variable with the specified type?
	 * This is the inverse of isAssignableFrom().
	 */
	public boolean isAssignableTo(final Class<?> superclass) {
		return superclass.isAssignableFrom(getTypeAsClass());
	}
}


package com.googlecode.objectify.impl;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.util.ResultNow;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * The basic session cache.  A lot easier than passing the generic arguments around!
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class Session
{
	/** */
	private final Map<Key<?>, SessionValue<?>> map = new HashMap<>();

	/**
	 * Add/overwrite a SV.
	 */
	public void add(final Key<?> key, final SessionValue<?> value) {
		if (log.isTraceEnabled())
			log.trace("Adding to session: {} -> {}", key, value.getResult());

		map.put(key, value);
	}

	/**
	 * Convenience method
	 */
	public void addValue(final Key<?> key, final Object value) {
		add(key, new SessionValue<>(new ResultNow<>(value)));
	}

	/** Add all entries in the other session to this one */
	public void addAll(final Session other) {
		if (log.isTraceEnabled())
			log.trace("Adding all values to session: {}", other.map.keySet());

		map.putAll(other.map);
	}

	/** */
	@SuppressWarnings("unchecked")
	public <T> SessionValue<T> get(final Key<T> key) {
		return (SessionValue<T>)map.get(key);
	}

	/** */
	public boolean contains(final Key<?> key) {
		return map.containsKey(key);
	}

	/** */
	public void clear() {
		log.trace("Clearing session");
		map.clear();
	}

	/** Convenient for debugging */
	@Override
	public String toString() {
		return map.toString();
	}

	/**
	 * @return all the keys currently in the session. If you really want this data, subclass ObjectifyImpl and
	 * use the protected getSession() method.
	 */
	public Set<Key<?>> keys() { return map.keySet(); }
}


package com.googlecode.objectify.impl;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Result;
import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Manages all the logic of deferring operations
 */
@RequiredArgsConstructor
public class Deferrer {

	/** */
	private final Objectify ofy;

	/** */
	private final Session session;

	/** Values of null mean "delete" */
	private final Map<Key<?>, Object> operations = new HashMap<>();

	/** Entities with autogenerated (null) ids can't be put in the map, they don't have keys */
	private final List<Object> autogeneratedIdSaves = new ArrayList<>();

	/**
	 * Eliminate any deferred operations against the entity. Used when an explicit save (or delete) was
	 * executed against the key, so we no longer need the deferred operation.
	 *
	 * @param keyOrEntity can be a Key, Key<?>, Entity, or entity pojo
	 */
	public void undefer(final Object keyOrEntity) {
		if (keyOrEntity instanceof Key<?>) {
			operations.remove((Key<?>)keyOrEntity);
		}
		else if (keyOrEntity instanceof com.google.cloud.datastore.Key) {
			operations.remove(Key.create((com.google.cloud.datastore.Key)keyOrEntity));
		}
		else if (factory().keys().requiresAutogeneratedId(keyOrEntity)) {	// note might be FullEntity without complete key
			autogeneratedIdSaves.remove(keyOrEntity);
		}
		else {
			final Key<?> key = factory().keys().keyOf(keyOrEntity);
			operations.remove(key);
		}
	}

	/**
	 */
	public void deferSave(final Object entity) {
		if (factory().keys().requiresAutogeneratedId(entity)) {
			autogeneratedIdSaves.add(entity);
		} else {
			final Key<?> key = factory().keys().keyOf(entity);

			session.addValue(key, entity);
			operations.put(key, entity);
		}
	}

	public void deferDelete(Key<?> key) {
		session.addValue(key, null);
		operations.put(key, null);
	}

	public void flush() {
		final List<Result<?>> futures = new ArrayList<>();

		// Need to do this in a loop because @OnSave methods can enlist more deferred operations. Execution
		// of save or delete will undefer() all the relevant items, so both lists empty mean we're done.
		while (!operations.isEmpty() || !autogeneratedIdSaves.isEmpty()) {
			// Sort into two batch operations: one for save, one for delete.
			final List<Object> saves = new ArrayList<>();
			final List<Key<?>> deletes = new ArrayList<>();

			for (final Map.Entry<Key<?>, Object> entry : operations.entrySet()) {
				if (entry.getValue() == null)
					deletes.add(entry.getKey());
				else
					saves.add(entry.getValue());
			}

			saves.addAll(autogeneratedIdSaves);

			if (!saves.isEmpty())
				futures.add(ofy.save().entities(saves));

			if (!deletes.isEmpty())
				futures.add(ofy.delete().keys(deletes));
		}

		// Complete any pending operations
		for (final Result<?> future : futures) {
			future.now();
		}
	}

	private ObjectifyFactory factory() {
		return ofy.factory();
	}

}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.BaseEntity;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.ReadOption;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.impl.ref.LiveRef;
import com.googlecode.objectify.impl.translate.LoadContext;
import com.googlecode.objectify.util.ResultCache;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;

/**
 * Represents one "batch" of loading.  Get a number of Result<?> objects, then execute().  Some work is done
 * right away, some work is done on the first get().  There might be multiple rounds of execution to process
 * all the @Load groups, but that is invisible outside this class.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class LoadEngine
{
	/** */
	final ObjectifyImpl ofy;
	private final AsyncDatastoreReaderWriter datastore;

	@Getter
	private final Session session;

	@Getter
	private final LoadArrangement loadArrangement;

	private final ImmutableSet<ReadOption> readOptions;

	/** The current round, replaced whenever the round executes */
	Round round;

	/**
	 */
	public LoadEngine(
			final ObjectifyImpl ofy,
			final Session session,
			final AsyncDatastoreReaderWriter datastore,
			final LoadArrangement loadArrangement,
			final ImmutableSet<ReadOption> readOptions) {
		this.ofy = ofy;
		this.session = session;
		this.datastore = datastore;
		this.loadArrangement = loadArrangement;
		this.readOptions = readOptions;

		this.round = new Round(this, 0);

		log.trace("Starting load engine with groups {}", loadArrangement);
	}

	/**
	 * Gets the result, possibly from the session, putting it in the session if necessary.
	 * Also will recursively prepare the session with @Load parents as appropriate.
	 * @throws NullPointerException if key is null
	 */
	public <T> Result<T> load(final Key<T> key) {
		if (key == null)
			throw new NullPointerException("You tried to load a null key!");

		final Result<T> result = round.get(key);

		// If we are running a transaction, enlist the result so that it gets processed on commit even
		// if the client never materializes the result.
		if (ofy.getTransaction() != null)
			((PrivateAsyncTransaction)ofy.getTransaction()).enlist(result);

		// Now check to see if we need to recurse and add our parent(s) to the round
		if (key.getParent() != null) {
			final KeyMetadata<?> meta = ofy.factory().keys().getMetadata(key);
			// Is it really possible for this to be null?
			if (meta != null) {
				if (meta.shouldLoadParent(loadArrangement)) {
					load(key.getParent());
				}
			}
		}

		return result;
	}

	/**
	 * Starts asychronous fetching of the batch.
	 */
	public void execute() {
		if (round.needsExecution()) {
			Round old = round;
			round = old.next();
			old.execute();
		}
	}

	/**
	 * Create a Ref for the key, and maybe start a load operation depending on current load groups.
	 *
	 * @param rootEntity is the entity key which holds this property (possibly through some level of embedded objects)
	 */
	public <T> Ref<T> makeRef(final Key<?> rootEntity, final LoadConditions loadConditions, final Key<T> key) {
		final Ref<T> ref = new LiveRef<>(key, ofy);

		if (shouldLoad(loadConditions)) {
			load(key);
		}

		return ref;
	}

	/**
	 * @return true if the specified property should be loaded in this batch
	 */
	public boolean shouldLoad(final LoadConditions loadConditions) {
		return loadConditions.shouldLoad(loadArrangement, ofy.getTransaction() != null);
	}

	/**
	 * Stuffs an Entity into a place where values in the round can be obtained instead of going to the datastore.
	 * Called by non-hybrid queries to add results and eliminate batch fetching.
	 */
	public void stuff(final Entity ent) {
		round.stuff(ent);
	}

	/**
	 * Asynchronously translate raw to processed; might produce successive load operations as refs are filled in
	 */
	public Result<Map<Key<?>, Object>> translate(final Result<Map<com.google.cloud.datastore.Key, Entity>> raw) {
		return new ResultCache<Map<Key<?>, Object>>() {

			/** */
			private LoadContext ctx;

			/** */
			@Override
			public Map<Key<?>, Object> nowUncached() {
				final Map<Key<?>, Object> result = new HashMap<>(raw.now().size() * 2);

				ctx = new LoadContext(LoadEngine.this);

				for (final Entity ent: raw.now().values()) {
					final Key<?> key = Key.create(ent.getKey());
					final Object entity = load(ent, ctx);
					result.put(key, entity);
				}

				return result;
			}

			/**
			 * We need to execute the done() after the translated value has been set, otherwise we
			 * can produce an infinite recursion problem.
			 */
			@Override
			protected void postExecuteHook() {
				ctx.done();
				ctx = null;
			}
		};
	}

	/**
	 * Fetch the keys from the async datastore using the current transaction context
	 */
	public Result<Map<com.google.cloud.datastore.Key, Entity>> fetch(Set<com.google.cloud.datastore.Key> keys) {
		log.debug("Fetching {} keys: {}", keys.size(), keys);

		final Future<Map<com.google.cloud.datastore.Key, Entity>> fut = datastore.get(keys, readOptions.toArray(new ReadOption[readOptions.size()]));
		return ResultAdapter.create(fut);
	}

	/**
	 * Converts a datastore entity into a typed pojo object
	 * @return an assembled pojo, or the Entity itself if the kind is not registered, or null if the input value was null
	 */
	@SuppressWarnings("unchecked")
	public <T> T load(final BaseEntity<com.google.cloud.datastore.Key> ent, final LoadContext ctx) {
		if (ent == null)
			return null;

		final EntityMetadata<T> meta = ofy.factory().getMetadata(ent.getKey().getKind());
		if (meta == null)
			return (T)ent;
		else
			return meta.load(ent, ctx);
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Cursor;
import com.google.cloud.datastore.EntityQuery;
import com.google.cloud.datastore.KeyQuery;
import com.google.cloud.datastore.ProjectionEntityQuery;
import com.google.cloud.datastore.ProjectionEntityQuery.Builder;
import com.google.cloud.datastore.Query;
import com.google.cloud.datastore.StructuredQuery;
import com.google.cloud.datastore.StructuredQuery.CompositeFilter;
import com.google.cloud.datastore.StructuredQuery.Filter;
import com.google.cloud.datastore.StructuredQuery.OrderBy;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.util.Queries;
import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.util.function.Supplier;

/**
 * The SDK Query hierarchy and associated builders make it hard to convert between keys-only
 * entity, and projection. So we have to store the state of the query ourselves.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Data
@RequiredArgsConstructor
public class QueryDef
{
	private final String kind;
	private final ImmutableList<String> projection;
	private final Filter filter;
	private final ImmutableList<String> distinctOn;
	private final ImmutableList<OrderBy> orderBy;
	private final Cursor startCursor;
	private final Cursor endCursor;
	private final int offset;
	private final Integer limit;

	/** To simulate the old distinct(boolean) behavior, this will add all projections to distinct when Query is generated */
	private final boolean distinctOnAll;

	private <T> ImmutableList<T> concat(final ImmutableList<T> base, final T element) {
		return ImmutableList.<T>builder().addAll(base).add(element).build();
	}

	public QueryDef() {
		this(null, ImmutableList.of(), null, ImmutableList.of(), ImmutableList.of(), null, null, 0, null, false);
	}

	public QueryDef kind(final String kind) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef project(final String projection) {
		return new QueryDef(kind, concat(this.projection, projection), filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef filter(final Filter filter) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	/** Convenince method that creates a composite filter with any existing filter (if present) */
	public QueryDef andFilter(final Filter addFilter) {
		return filter(this.filter == null ? addFilter : CompositeFilter.and(this.filter, addFilter));
	}

	public QueryDef distinctOn(final String distinctOn) {
		return new QueryDef(kind, projection, filter, concat(this.distinctOn, distinctOn), orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	/**
	 * At the last minute add distinct on all projected fields. This can be called before the projections are added.
	 */
	public QueryDef distinctOnAll(final boolean distinctOnAll) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef orderBy(final OrderBy orderBy) {
		return new QueryDef(kind, projection, filter, distinctOn, concat(this.orderBy, orderBy), startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef startCursor(final Cursor startCursor) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef endCursor(final Cursor endCursor) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef offset(final int offset) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public QueryDef limit(final Integer limit) {
		return new QueryDef(kind, projection, filter, distinctOn, orderBy, startCursor, endCursor, offset, limit, distinctOnAll);
	}

	public KeyQuery newKeyQuery() {
		Preconditions.checkState(projection.isEmpty(), "You cannot create a keys-only query with a projection");
		return build(Query::newKeyQueryBuilder).build();
	}

	public EntityQuery newEntityQuery() {
		Preconditions.checkState(projection.isEmpty(), "You cannot create an entity query with a projection");
		return build(Query::newEntityQueryBuilder).build();
	}

	public ProjectionEntityQuery newProjectionQuery() {
		Preconditions.checkState(!projection.isEmpty(), "You must have projections to create a projection query");

		final Builder builder = build(Query::newProjectionEntityQueryBuilder);
		Queries.addProjection(builder, projection);
		Queries.addDistinctOn(builder, distinctOnAll ? projection : distinctOn);

		return builder.build();
	}

	private <B extends StructuredQuery.Builder<?>> B build(final Supplier<B> into) {
		final B builder = into.get();

		builder
				.setKind(kind)
				.setFilter(filter)
				.setStartCursor(startCursor)
				.setEndCursor(endCursor)
				.setOffset(offset)
				.setLimit(limit);

		Queries.addOrderBy(builder, orderBy);

		return builder;
	}
}


package com.googlecode.objectify.impl;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.cmd.DeleteIds;
import com.googlecode.objectify.cmd.DeleteType;
import com.googlecode.objectify.util.ArrayUtils;

import java.util.Arrays;
import java.util.Collections;

/**
 * Implementation of the DeleteType and DeleteIds interfaces.  No need for separate implementations.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
class DeleteTypeImpl implements DeleteType
{
	/** */
	private final DeleterImpl deleter;

	/** Translated from the type class */
	private final Class<?> type;

	/** Possible parent */
	private final Key<?> parent;

	/**
	 * @param type must be a registered type
	 */
	DeleteTypeImpl(final DeleterImpl deleter, final Class<?> type) {
		this(deleter, type, null);
	}

	/**
	 * @param parent can be Key, Key<?>, or entity
	 */
	DeleteTypeImpl(final DeleterImpl deleter, final Class<?> type, final Key<?> parent) {
		this.deleter = deleter;
		this.type = type;
		this.parent = parent;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.DeleteType#parent(java.lang.Object)
	 */
	@Override
	public DeleteIds parent(final Object keyOrEntity) {
		final Key<?> parentKey = factory().keys().anythingToKey(keyOrEntity);
		return new DeleteTypeImpl(deleter, type, parentKey);
	}

	private ObjectifyFactory factory() {
		return deleter.ofy.factory();
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.DeleteIds#id(long)
	 */
	@Override
	public Result<Void> id(final long id) {
		return ids(Collections.singleton(id));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.DeleteIds#id(java.lang.String)
	 */
	@Override
	public Result<Void> id(final String id) {
		return ids(Collections.singleton(id));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.DeleteIds#ids(long[])
	 */
	@Override
	public Result<Void> ids(final long... ids) {
		return ids(ArrayUtils.asList(ids));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.DeleteIds#ids(java.lang.String[])
	 */
	@Override
	public Result<Void> ids(final String... ids) {
		return ids(Arrays.asList(ids));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.DeleteIds#ids(java.lang.Iterable)
	 */
	@Override
	public <S> Result<Void> ids(final Iterable<S> ids) {
		return this.deleter.keys(factory().keys().createKeys(parent, type, ids));
	}

}


package com.googlecode.objectify.cache;

import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * A Future which merges some previously loaded values with the results of another
 * Future that is in progress.  It can apply to any key/value pair type; typically
 * it will be for Key/Entity or Key<T>/T
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class MergeFuture<K, V> implements Future<Map<K, V>>
{
	/**
	 * Values that we have obtained.  Starts with just the preloaded values,
	 * then when the pending is complete the values get appended to this collection.
	 */
	Map<K, V> loaded;

	/** Pending requests - if null, this Future is complete and all values are in loaded */
	Future<Map<K, V>> pending;

	/**
	 * @param preloaded is a collection of entities that have already been obtained, say
	 * from the memcache.  TAKES OWNERSHIP OF THE MAP OBJECT - it will be modified later.
	 * @param pending is a future of entities that will be obtained sometime later, or
	 * null if merging is unnecessary and the preloaded values complete the result.
	 */
	public MergeFuture(Map<K, V> preloaded, Future<Map<K, V>> pending)
	{
		assert preloaded != null;
		this.loaded = preloaded;
		this.pending = pending;
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#cancel(boolean)
	 */
	@Override
	public boolean cancel(boolean mayInterruptIfRunning)
	{
		//return this.raw.cancel(mayInterruptIfRunning);
		throw new UnsupportedOperationException("This makes my head spin. Don't do it.");
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#isCancelled()
	 */
	@Override
	public boolean isCancelled()
	{
		return false;
	}

	/**
	 *
	 * @see java.util.concurrent.Future#isDone()
	 */
	@Override
	public boolean isDone()
	{
		if (this.pending == null)
			return true;
		else
			return this.pending.isDone();
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#get()
	 */
	@Override
	public Map<K, V> get() throws InterruptedException, ExecutionException
	{
		if (this.pending != null)
		{
			this.loaded.putAll(this.pending.get());
			this.pending = null;
		}

		return this.loaded;
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
	 */
	@Override
	public Map<K, V> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
	{
		if (this.pending != null)
		{
			this.loaded.putAll(this.pending.get(timeout, unit));
			this.pending = null;
		}

		return this.loaded;
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.FullEntity;
import com.google.cloud.datastore.IncompleteKey;
import com.google.cloud.datastore.LongValue;
import com.google.cloud.datastore.StringValue;
import com.google.cloud.datastore.Value;
import com.google.common.base.Preconditions;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.util.KeyFormat;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 *
Gives us a slightly more organized interface for manipulating keys. While this is part of Objectify's
 * public interface, you probably shouldn't use it. It's subject to change without notice. If you want to
 * work with keys, use the Key.create() methods.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@RequiredArgsConstructor
public class Keys
{
	private final Datastore datastore;
	private final Registrar registrar;

	/**
	 * @return the Key<?> for a registered pojo entity.
	 */
	public <T> Key<T> keyOf(final T pojo) {
		return Key.create(rawKeyOf(pojo));
	}

	/**
	 * @return the native datastore key for a registered pojo entity (or FullEntity).
	 */
	public com.google.cloud.datastore.Key rawKeyOf(final Object pojo) {
		if (pojo instanceof FullEntity<?>) {
			return (com.google.cloud.datastore.Key)((FullEntity)pojo).getKey();
		} else {
			return getMetadataSafe(pojo).getCompleteKey(pojo);
		}
	}

	/**
	 * @return the metadata for a registered pojo, or throw exception if none
	 * @throws IllegalStateException if the pojo class has not been registered
	 */
	public <T> KeyMetadata<T> getMetadataSafe(final Class<T> clazz) {
		return registrar.getMetadataSafe(clazz).getKeyMetadata();
	}

	/**
	 * @return the metadata for a registeerd pojo, or throw exception if none
	 * @throws IllegalStateException if the pojo class has not been registered
	 */
	@SuppressWarnings("unchecked")
	public <T> KeyMetadata<T> getMetadataSafe(final T pojo) {
		return (KeyMetadata<T>)getMetadataSafe(pojo.getClass());
	}

	/**
	 * @return the metadata for a registered pojo, or null if there is none
	 */
	@SuppressWarnings("unchecked")
	public <T> KeyMetadata<T> getMetadata(final Key<T> key) {
		final EntityMetadata<T> em = registrar.getMetadata(key.getKind());
		return em == null ? null : em.getKeyMetadata();
	}

	/**
	 *
Gets the Key<T> given an object that might be a Key, Key<T>, or entity.

	 *
	 * @param keyOrEntity must be a Key, Key<T>, or registered entity.
	 * @throws NullPointerException if keyOrEntity is null
	 * @throws IllegalArgumentException if keyOrEntity is not a Key, Key<T>, or registered entity
	 */
	@SuppressWarnings("unchecked")
	public <T> Key<T> anythingToKey(final Object keyOrEntity) {

		if (keyOrEntity instanceof Key<?>)
			return (Key<T>)keyOrEntity;
		else if (keyOrEntity instanceof com.google.cloud.datastore.Key)
			return Key.create((com.google.cloud.datastore.Key)keyOrEntity);
		else if (keyOrEntity instanceof Ref)
			return ((Ref<T>)keyOrEntity).key();
		else if (keyOrEntity instanceof FullEntity<?>)
			return Key.create(getKey((FullEntity<?>)keyOrEntity));
		else
			return keyOf((T)keyOrEntity);
	}

	/**
	 *
Gets the raw datstore Key given an object that might be a Key, Key<T>, or entity.

	 *
	 * @param keyOrEntity must be a Key, Key<T>, or registered entity.
	 * @throws NullPointerException if keyOrEntity is null
	 * @throws IllegalArgumentException if keyOrEntity is not a Key, Key<T>, or registered entity
	 */
	public com.google.cloud.datastore.Key anythingToRawKey(final Object keyOrEntity) {

		if (keyOrEntity instanceof com.google.cloud.datastore.Key)
			return (com.google.cloud.datastore.Key)keyOrEntity;
		else if (keyOrEntity instanceof Key<?>)
			return ((Key<?>)keyOrEntity).getRaw();
		else if (keyOrEntity instanceof Ref)
			return ((Ref<?>)keyOrEntity).key().getRaw();
		else if (keyOrEntity instanceof FullEntity<?>)
			return getKey((FullEntity<?>)keyOrEntity);
		else
			return rawKeyOf(keyOrEntity);
	}

	/** @return the Key, or throw an exception if entity's key is missing or incomplete */
	public static com.google.cloud.datastore.Key getKey(final FullEntity<?> entity) {
		Preconditions.checkNotNull(entity.getKey(), "FullEntity<?> is expected to have a key");
		Preconditions.checkArgument(entity.getKey() instanceof com.google.cloud.datastore.Key, "Unexpected IncompleteKey");
		return (com.google.cloud.datastore.Key)entity.getKey();
	}

	/**
	 * @return true of the entity has a null id which can be autogenerated on save
	 */
	public boolean requiresAutogeneratedId(final Object entity) {
		if (entity instanceof FullEntity<?>)
			return !(((FullEntity)entity).getKey() instanceof com.google.cloud.datastore.Key);
		else
			return getMetadataSafe(entity).requiresAutogeneratedId(entity);
	}

	/**
	 * @param parent can be null for root keys
	 */
	public com.google.cloud.datastore.Key createRaw(final com.google.cloud.datastore.Key parent, final String kind, final long id) {
		if (parent == null) {
			return datastore.newKeyFactory().setKind(kind).newKey(id);
		} else {
			return com.google.cloud.datastore.Key.newBuilder(parent, kind, id).build();
		}
	}

	/**
	 * @param parent can be null for root keys
	 */
	public com.google.cloud.datastore.Key createRaw(final com.google.cloud.datastore.Key parent, final String kind, final String name) {
		if (parent == null) {
			return datastore.newKeyFactory().setKind(kind).newKey(name);
		} else {
			return com.google.cloud.datastore.Key.newBuilder(parent, kind, name).build();
		}
	}

	/**
	 * Construct a Key from a Long or String id
	 * @param id must be either Long or String
	 */
	public com.google.cloud.datastore.Key createRawAny(final com.google.cloud.datastore.Key parent, final String kind, final Object id) {
		if (id instanceof String)
			return createRaw(parent, kind, (String)id);
		else if (id instanceof Long)
			return createRaw(parent, kind, (Long)id);
		else
			throw new IllegalArgumentException("id '" + id + "' must be String or Long");
	}

	/**
	 * @param parent can be null for root keys
	 */
	public IncompleteKey createRawIncomplete(final com.google.cloud.datastore.Key parent, final String kind) {
		if (parent == null) {
			return datastore.newKeyFactory().setKind(kind).newKey();
		} else {
			return IncompleteKey.newBuilder(parent, kind).build();
		}
	}

	/**
	 * @param parent can be null for root keys
	 */
	public <T> Key<T> createKey(final Key<?> parent, final Class<T> kind, final long id) {
		final com.google.cloud.datastore.Key key = createRaw(raw(parent), Key.getKind(kind), id);
		return Key.create(key);
	}

	/**
	 * @param parent can be null for root keys
	 */
	public <T> Key<T> createKey(final Key<?> parent, final Class<T> kind, final String name) {
		final com.google.cloud.datastore.Key key = createRaw(raw(parent), Key.getKind(kind), name);
		return Key.create(key);
	}

	/**
	 * Construct a Key<?> from a Long or String id
	 * @param id must be either Long or String
	 */
	public <T> Key<T> createKeyAny(final Key<?> parent, final Class<T> kind, final Object id) {
		if (id instanceof String)
			return createKey(parent, kind, (String)id);
		else if (id instanceof Long)
			return createKey(parent, kind, (Long)id);
		else
			throw new IllegalArgumentException("id '" + id + "' must be String or Long");
	}

	/** Null-safe extraction of the raw key */
	public static com.google.cloud.datastore.Key raw(final Key<?> key) {
		return key == null ? null : key.getRaw();
	}

	/**
	 * Make a list of Key<?>s
	 * @param ids must contain either Long or String
	 */
	public <T> List<Key<T>> createKeys(final Key<?> parent, final Class<T> kind, final Iterable<?> ids) {
		return StreamSupport.stream(ids.spliterator(), false)
				.map(id -> createKeyAny(parent, kind, id))
				.collect(Collectors.toList());
	}

	public static com.google.cloud.datastore.Key[] toArray(final Collection<com.google.cloud.datastore.Key> collection) {
		return collection.toArray(new com.google.cloud.datastore.Key[collection.size()]);
	}

	/**
	 * Gets the String or Long id from the key as a Value, or null if incomplete
	 */
	@SuppressWarnings("unchecked")
	public static <S> Value<S> getIdValue(final IncompleteKey key) {
		if (key instanceof com.google.cloud.datastore.Key) {
			final com.google.cloud.datastore.Key completeKey = (com.google.cloud.datastore.Key)key;
			if (completeKey.hasId())
				return (Value<S>)LongValue.of(completeKey.getId());
			else
				return (Value<S>)StringValue.of(completeKey.getName());
		} else {
			return null;
		}
	}

	/**
	 * Understands both the legacy format "ag1zfnZvb2Rvb2R5bmUwcgcLEgFCGAEM" and new format,
	 * providing the key either way.
	 */
	@SneakyThrows
	public static com.google.cloud.datastore.Key fromUrlSafe(final String urlSafeKey) {
		if (urlSafeKey.startsWith("a")) {
			return KeyFormat.INSTANCE.parseOldStyleAppEngineKey(urlSafeKey);
		} else {
			return com.google.cloud.datastore.Key.fromUrlSafe(urlSafeKey);
		}
	}
}


package com.googlecode.objectify.impl;

import com.googlecode.objectify.TxnType;
import com.googlecode.objectify.Work;

/**
 * Implementation for when we start a transaction.  Maintains a separate session, but then copies all
 * data into the original session on successful commit.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
class TransactorYes extends Transactor
{
	/** Our transaction. */
	private final AsyncTransaction transaction;

	/** The non-transactional transactor that spawned us */
	private final TransactorNo parentTransactor;

	/**
	 */
	TransactorYes(final ObjectifyImpl current, final TransactorNo parentTransactor) {
		super(current);

		this.transaction = factory.asyncDatastore(current.getOptions().isCache()).newTransaction(this::committed);
		this.parentTransactor = parentTransactor;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.cmd.Transactor#getTransaction()
	 */
	@Override
	public AsyncTransaction getTransaction() {
		return this.transaction;
	}

	/**
	 * This version goes back to life without a transaction, but preserves current options.
	 * We use the session from the parent, ie life before transactions.
	 */
	@Override
	public ObjectifyImpl transactionless(final ObjectifyImpl parent) {
		return parent.makeNew(next -> new TransactorNo(next, parentTransactor.getSession()));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.cmd.Transactor#execute(com.googlecode.objectify.TxnType, com.googlecode.objectify.Work)
	 */
	@Override
	public <R> R execute(final ObjectifyImpl parent, final TxnType txnType, final Work<R> work) {
		switch (txnType) {
			case MANDATORY:
			case REQUIRED:
			case SUPPORTS:
				return work.run();

			case NOT_SUPPORTED:
				return transactionless(parent, work);

			case NEVER:
				throw new IllegalStateException("MANDATORY transaction but no transaction present");

			case REQUIRES_NEW:
				return transactNew(parent, Transactor.DEFAULT_TRY_LIMIT, work);

			default:
				throw new IllegalStateException("Impossible, some unknown txn type");
		}
	}

	@Override
	public <R> R transactionless(final ObjectifyImpl parent, final Work<R> work) {
		final ObjectifyImpl ofy = factory.open(parent.getOptions(), next -> new TransactorNo(next, parentTransactor.getSession()));
		try {
			return work.run();
		} finally {
			ofy.close();
		}
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.Transactor#transact(com.googlecode.objectify.impl.ObjectifyImpl, com.googlecode.objectify.Work)
	 */
	@Override
	public <R> R transact(final ObjectifyImpl parent, final Work<R> work) {
		return work.run();
	}

	/**
	 * We need to make sure the parentSession is the transactionless session, not the session
	 * for our transaction.  This gives proper transaction isolation.
	 */
	@Override
	public <R> R transactNew(final ObjectifyImpl parent, final int limitTries, final Work<R> work) {
		return transactionless(parent).transactNew(limitTries, work);
	}

	/**
	 * Called when the associated transaction is committed. Dumps the contents of the transactional session into the parent's
	 * session.
	 */
	public void committed() {
		parentTransactor.getSession().addAll(getSession());
	}

	@Override
	public AsyncDatastoreReaderWriter asyncDatastore() {
		return this.transaction;
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.StructuredQuery.Filter;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.LoadResult;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.cmd.LoadIds;
import com.googlecode.objectify.cmd.LoadType;
import com.googlecode.objectify.cmd.Query;
import com.googlecode.objectify.util.ResultCache;
import com.googlecode.objectify.util.ResultProxy;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Implementation of the LoadType interface.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
class LoadTypeImpl<T> extends Queryable<T> implements LoadType<T>
{
	/** */
	private final String kind;

	/** Might be null; perhaps we specified a raw kind only */
	private final Class<T> type;

	/** Possible parent */
	private final Key<T> parent;

	/**
	 */
	LoadTypeImpl(LoaderImpl loader, String kind, Class<T> type) {
		this(loader, kind, type, null);
	}

	/** */
	LoadTypeImpl(LoaderImpl loader, String kind, Class<T> type, Key<T> parent) {
		super(loader);
		this.kind = kind;
		this.type = type;
		this.parent = parent;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.cmd.QueryCommonImpl#createQuery()
	 */
	@Override
	QueryImpl<T> createQuery() {
		return new QueryImpl<>(loader, kind, type);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#filter(java.lang.String, java.lang.Object)
	 */
	@Override
	public Query<T> filter(String condition, Object value) {
		QueryImpl<T> q = createQuery();
		q.addFilter(condition, value);
		return q;
	}

	/* */
	@Override
	public Query<T> filter(final Filter filter) {
		final QueryImpl<T> q = createQuery();
		q.addFilter(filter);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#order(java.lang.String)
	 */
	@Override
	public Query<T> order(String condition) {
		QueryImpl<T> q = createQuery();
		q.addOrder(condition);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.LoadIds#id(long)
	 */
	@Override
	public LoadResult<T> id(final long id) {
		return loader.key(this.makeKey(id));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.LoadIds#id(java.lang.String)
	 */
	@Override
	public LoadResult<T> id(final String id) {
		return loader.key(this.makeKey(id));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.LoadIds#ids(Long[])
	 */
	@Override
	public Map<Long, T> ids(final Long... ids) {
		return ids(Arrays.asList(ids));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.LoadIds#ids(java.lang.String[])
	 */
	@Override
	public Map<String, T> ids(final String... ids) {
		return ids(Arrays.asList(ids));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.LoadIds#ids(java.lang.Iterable)
	 */
	@Override
	public <S> Map<S, T> ids(final Iterable<S> ids) {

		final Map<Key<T>, S> keymap = new LinkedHashMap<>();
		for (final S id: ids)
			keymap.put(this.makeKey(id), id);

		final Map<Key<T>, T> loaded = loader.keys(keymap.keySet());

		return ResultProxy.create(Map.class, new ResultCache<Map<S, T>>() {
			@Override
			protected Map<S, T> nowUncached() {
				final Map<S, T> proper = new LinkedHashMap<>(loaded.size() * 2);

				for (final Map.Entry<Key<T>, T> entry: loaded.entrySet())
					proper.put(keymap.get(entry.getKey()), entry.getValue());

				return proper;
			}
		});
	}

	/**
	 * Make a key for the given id, which could be either string or long
	 */
	private <T> Key<T> makeKey(final Object id) {
		final com.google.cloud.datastore.Key key = factory().keys().createRawAny(Keys.raw(this.parent), kind, id);
		return Key.create(key);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.LoadType#parent(java.lang.Object)
	 */
	@Override
	public LoadIds<T> parent(final Object keyOrEntity) {
		final Key<T> parentKey = factory().keys().anythingToKey(keyOrEntity);
		return new LoadTypeImpl<>(loader, kind, type, parentKey);
	}

	/** */
	private ObjectifyFactory factory() {
		return loader.getObjectifyImpl().factory();
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.DatastoreException;
import com.google.common.base.Preconditions;
import com.google.rpc.Code;
import com.googlecode.objectify.TxnType;
import com.googlecode.objectify.Work;
import lombok.extern.slf4j.Slf4j;

/**
 * Transactor which represents the absence of a transaction.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
class TransactorNo extends Transactor
{
	/**
	 */
	TransactorNo(final ObjectifyImpl ofy) {
		super(ofy);
	}

	/**
	 */
	TransactorNo(final ObjectifyImpl ofy, final Session session) {
		super(ofy, session);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.Objectify#getTransaction()
	 */
	@Override
	public AsyncTransactionImpl getTransaction() {
		// This version doesn't have a transaction, always null.
		return null;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.cmd.Transactor#transactionless()
	 */
	@Override
	public ObjectifyImpl transactionless(ObjectifyImpl parent) {
		return parent;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.cmd.Transactor#execute(com.googlecode.objectify.TxnType, com.googlecode.objectify.Work)
	 */
	@Override
	public <R> R execute(final ObjectifyImpl parent, final TxnType txnType, final Work<R> work) {
		switch (txnType) {
			case MANDATORY:
				throw new IllegalStateException("MANDATORY transaction but no transaction present");

			case NOT_SUPPORTED:
			case NEVER:
			case SUPPORTS:
				return work.run();

			case REQUIRED:
			case REQUIRES_NEW:
				return transact(parent, work);

			default:
				throw new IllegalStateException("Impossible, some unknown txn type");
		}

	}

	@Override
	public <R> R transactionless(final ObjectifyImpl parent, final Work<R> work) {
		return work.run();
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.Transactor#transact(com.googlecode.objectify.impl.ObjectifyImpl, com.googlecode.objectify.Work)
	 */
	@Override
	public <R> R transact(final ObjectifyImpl parent, final Work<R> work) {
		return this.transactNew(parent, DEFAULT_TRY_LIMIT, work);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.Transactor#transactNew(com.googlecode.objectify.impl.ObjectifyImpl, int, com.googlecode.objectify.Work)
	 */
	@Override
	public <R> R transactNew(final ObjectifyImpl parent, int limitTries, final Work<R> work) {
		Preconditions.checkArgument(limitTries >= 1);
		final int ORIGINAL_TRIES = limitTries;

		while (true) {
			try {
				return transactOnce(parent, work);
			} catch (DatastoreException ex) {

				// This doesn't work because the SDK considers all transactions to be non-retryable. Objectify has always
				// assumed that transactions are idempotent and retries accordingly. So we have to explicitly check against
				// code 10, which is ABORTED. https://cloud.google.com/datastore/docs/concepts/errors
//				if (!ex.isRetryable())
//					throw ex;
				// I hate this so much. Sometimes the transaction gets closed by the datastore during contention and
				// then it proceeds to freak out and 503.
				if (Code.ABORTED.getNumber() == ex.getCode() || (Code.INVALID_ARGUMENT.getNumber() == ex.getCode() && ex.getMessage().contains("transaction closed"))) {
					// Continue to retry logic
				} else {
					throw ex;
				}

				if (--limitTries > 0) {
					log.warn("Retrying {} failure for {}: {}", ex.getReason(), work, ex);
					log.trace("Details of transaction failure", ex);
					try {
						// Do increasing backoffs with randomness
						Thread.sleep(Math.min(10000, (long) (0.5 * Math.random() + 0.5) * 200 * (ORIGINAL_TRIES - limitTries + 2)));
					} catch (InterruptedException ignored) {
					}
				} else {
					throw new DatastoreException(ex.getCode(), "Failed retrying datastore " + ORIGINAL_TRIES + " times ", ex.getReason(), ex);
				}
			}
		}
	}

	@Override
	public AsyncDatastoreReaderWriter asyncDatastore() {
		return factory.asyncDatastore(ofy.getOptions().isCache());
	}

	/**
	 * One attempt at executing a transaction
	 */
	private <R> R transactOnce(final ObjectifyImpl parent, final Work<R> work) {
		final ObjectifyImpl txnOfy = factory.open(parent.getOptions(), next -> new TransactorYes(next, this));

		boolean committedSuccessfully = false;
		try {
			final R result = work.run();
			txnOfy.flush();
			txnOfy.getTransaction().commit();
			committedSuccessfully = true;
			return result;
		}
		finally {
			if (txnOfy.getTransaction().isActive()) {
				try {
					txnOfy.getTransaction().rollback();
				} catch (RuntimeException ex) {
					log.error("Rollback failed, suppressing error", ex);
				}
			}

			txnOfy.close();

			if (committedSuccessfully) {
				((PrivateAsyncTransaction)txnOfy.getTransaction()).runCommitListeners();
			}
		}
	}
}


package com.googlecode.objectify.impl;

import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Unindex;
import com.googlecode.objectify.condition.If;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Type;

/**
 * Property which encapsulates a simple field.
 */
public class FieldProperty extends AbstractProperty
{
	Field field;
	MethodHandle getter;
	MethodHandle setter;

	/** These are authoritative */
	If<?, ?>[] indexConditions;
	If<?, ?>[] unindexConditions;
	If<?, ?>[] ignoreSaveConditions;

	/** If we have an @IgnoreSave and it isn't Always */
	boolean hasIgnoreSaveConditions;

	/**
	 * @param examinedClass is the actual top level concrete class we are examining; the field might
	 * be declared on a superclass of this class so it's not the same as field.getDeclaringClass()
	 */
	public FieldProperty(ObjectifyFactory fact, Class<?> examinedClass, Field field) {
		super(field.getName(), field.getAnnotations(), field);

		field.setAccessible(true);
		this.field = field;
		try {
			this.getter = MethodHandles.lookup().unreflectGetter(field);
			this.setter = MethodHandles.lookup().unreflectSetter(field);
		}
		catch (ReflectiveOperationException e) {
			throw new IllegalStateException(e);
		}

		IfConditionGenerator ifGenerator = new IfConditionGenerator(fact);

		// Check @Index and @Unindex conditions
		Index indexedAnn = field.getAnnotation(Index.class);
		Unindex unindexedAnn = field.getAnnotation(Unindex.class);

		if (indexedAnn != null && unindexedAnn != null)
			throw new IllegalStateException("Cannot have @Indexed and @Unindexed on the same field: " + field);

		if (indexedAnn != null)
			this.indexConditions = ifGenerator.generateIfConditions(indexedAnn.value(), field);

		if (unindexedAnn != null)
			this.unindexConditions = ifGenerator.generateIfConditions(unindexedAnn.value(), field);

		// Now watch out for @IgnoreSave conditions
		IgnoreSave ignoreSave = field.getAnnotation(IgnoreSave.class);
		if (ignoreSave != null)
			ignoreSaveConditions = ifGenerator.generateIfConditions(ignoreSave.value(), field);
	}

	/** */
	@Override
	public Type getType() { return this.field.getGenericType(); }

	/** */
	@Override
	public void set(Object pojo, Object value) {
		try {
			//this.field.set(pojo, value);
			setter.invoke(pojo, value);
		}
		catch (RuntimeException ex) { throw ex; }
		catch (Throwable ex) { throw new RuntimeException(ex); }
	}

	/** */
	@Override
	public Object get(Object pojo) {
		try {
			//return this.field.get(pojo);
			return getter.invoke(pojo);
		}
		catch (RuntimeException ex) { throw ex; }
		catch (Throwable ex) { throw new RuntimeException(ex); }
	}

	/** */
	@Override
	public String toString() {
		return this.field.toString();
	}

	/** */
	@Override
	public boolean isSaved(Object onPojo) {
		return !this.matches(onPojo, ignoreSaveConditions);
	}

	/** */
	@Override
	public Boolean getIndexInstruction(Object onPojo) {
		if (this.matches(onPojo, indexConditions))
			return true;
		else if (this.matches(onPojo, unindexConditions))
			return false;
		else
			return null;
	}

	/**
	 * Tests whether a set of conditions match.
	 * @param conditions can be null; this always matches false
	 * @return true if we match the conditions, false if we do not
	 */
	private boolean matches(Object onPojo, If<?, ?>[] conditions) {
		if (conditions == null)
			return false;

		Object value = this.get(onPojo);

		for (If<?, ?> condition: conditions) {
			@SuppressWarnings("unchecked")
			If<Object, Object> cond = (If<Object, Object>)condition;

			if (cond.matchesValue(value))
				return true;

			if (cond.matchesPojo(onPojo))
				return true;
		}

		return false;
	}
}


package com.googlecode.objectify.impl.ref;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Ref;

import java.io.ObjectStreamException;

/**
 *
Implementation of Refs which are "live" and connected to the datastore so they can fetch
 * entity values even if they have not already been loaded. This is the standard Ref implementation.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class LiveRef<T> extends Ref<T>
{
	private static final long serialVersionUID = 1L;

	/** So that Refs can be associated with a session */
	protected transient Objectify ofy;

	/** For GWT serialization */
	protected LiveRef() {}

	/**
	 * Create a Ref based on the key
	 */
	public LiveRef(Key<T> key) {
		this(key, ObjectifyService.ofy());
	}

	/**
	 * Create a Ref based on the key, with the specified session
	 */
	public LiveRef(Key<T> key, Objectify ofy) {
		super(key);
		this.ofy = ofy;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.Ref#get()
	 */
	@Override
	public T get() {
		return ofy().load().now(key());
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.Ref#isLoaded()
	 */
	@Override
	public boolean isLoaded() {
		return ofy().isLoaded(key());
	}

	/**
	 * Get the current objectify instance associated with this ref
	 */
	private Objectify ofy() {
		// If we have an expired transaction context, we need a new context
		if (ofy == null || (ofy.getTransaction() != null && !ofy.getTransaction().isActive()))
			ofy = ObjectifyService.ofy();

		return ofy;
	}

	/**
	 * When this serializes, write out the DeadRef version. Use the getValue() for value so that
	 * if the value is not loaded, it serializes as null.
	 */
	protected Object writeReplace() throws ObjectStreamException {
		return new DeadRef<>(key(), getValue());
	}

}


package com.googlecode.objectify;

import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.IncompleteKey;
import com.googlecode.objectify.cache.CachingAsyncDatastore;
import com.googlecode.objectify.cache.EntityMemcache;
import com.googlecode.objectify.cache.MemcacheService;
import com.googlecode.objectify.cache.spymemcached.SpyMemcacheService;
import com.googlecode.objectify.impl.AsyncDatastore;
import com.googlecode.objectify.impl.AsyncDatastoreImpl;
import com.googlecode.objectify.impl.CacheControlImpl;
import com.googlecode.objectify.impl.EntityMemcacheStats;
import com.googlecode.objectify.impl.EntityMetadata;
import com.googlecode.objectify.impl.Forge;
import com.googlecode.objectify.impl.Keys;
import com.googlecode.objectify.impl.ObjectifyImpl;
import com.googlecode.objectify.impl.ObjectifyOptions;
import com.googlecode.objectify.impl.Registrar;
import com.googlecode.objectify.impl.TransactorSupplier;
import com.googlecode.objectify.impl.TypeUtils;
import com.googlecode.objectify.impl.translate.Translators;
import net.spy.memcached.MemcachedClient;

import java.lang.reflect.Constructor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
 *
Factory which allows us to construct implementations of the Objectify interface.
 * You should usually use the ObjectifyService to access Objectify.

 *
 *
ObjectifyFactory is designed to be subclassed; much default behavior can be changed
 * by overriding methods.  In particular, see createObjectify(), construct(), getAsyncDatastoreService().

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class ObjectifyFactory implements Forge
{
	/** Default memcache namespace */
	public static final String MEMCACHE_NAMESPACE = "ObjectifyCache";

	/**
	 * Thread local stack of Objectify instances corresponding to transaction depth
	 */
	private final ThreadLocal<Deque<Objectify>> stacks = ThreadLocal.withInitial(ArrayDeque::new);

	/** The raw interface to the datastore from the Cloud SDK */
	protected Datastore datastore;

	/** The low-level interface to memcache */
	protected MemcacheService memcache;

	/** Encapsulates entity registration info */
	protected Registrar registrar;

	/** Some useful tools for working with keys */
	protected Keys keys;

	/** */
	protected Translators translators;

	/** */
	protected EntityMemcacheStats memcacheStats = new EntityMemcacheStats();

	/** Manages caching of entities; might be null to indicate "no cache" */
	protected EntityMemcache entityMemcache;

	/** Uses default datastore, no memcache */
	public ObjectifyFactory() {
		this(DatastoreOptions.getDefaultInstance().getService());
	}

	/**
	 * No memcache
	 */
	public ObjectifyFactory(final Datastore datastore) {
		this(datastore, (MemcacheService)null);
	}

	/** Uses default datastore */
	public ObjectifyFactory(final MemcachedClient memcache) {
		this(DatastoreOptions.getDefaultInstance().getService(), memcache);
	}

	/** Uses default datastore */
	public ObjectifyFactory(final MemcacheService memcache) {
		this(DatastoreOptions.getDefaultInstance().getService(), memcache);
	}

	/**
	 */
	public ObjectifyFactory(final Datastore datastore, final MemcachedClient memcache) {
		this(datastore, new SpyMemcacheService(memcache));
	}

	/**
	 */
	public ObjectifyFactory(final Datastore datastore, final MemcacheService memcache) {
		this.datastore = datastore;
		this.registrar = new Registrar(this);
		this.keys = new Keys(datastore, registrar);
		this.translators = new Translators(this);
		this.memcache = memcache;

		this.entityMemcache = memcache == null ? null : new EntityMemcache(memcache, MEMCACHE_NAMESPACE, new CacheControlImpl(this), this.memcacheStats);
	}

	/** */
	public Datastore datastore() {
		return this.datastore;
	}

	/** */
	public MemcacheService memcache() {
		return this.memcache;
	}

	/** Always the non-caching version */
	public AsyncDatastore asyncDatastore() {
		return new AsyncDatastoreImpl(datastore);
	}

	/**
	 * Might produce a caching version if caching is enabled.
	 */
	public AsyncDatastore asyncDatastore(final boolean enableGlobalCache) {
		if (this.entityMemcache != null && enableGlobalCache && this.registrar.isCacheEnabled())
			return new CachingAsyncDatastore(asyncDatastore(), this.entityMemcache);
		else
			return asyncDatastore();
	}

	/**
	 *
Construct an instance of the specified type.  Objectify uses this method whenever possible to create
	 * instances of entities, condition classes, or other types; by overriding this method you can substitute Guice or other
	 * dependency injection mechanisms.  By default it constructs with a simple no-args constructor.

	 */
	@Override
	public <T> T construct(final Class<T> type) {
		// We do this instead of calling newInstance directly because this lets us work around accessiblity
		final Constructor<T> ctor = TypeUtils.getNoArgConstructor(type);
		return TypeUtils.newInstance(ctor);
	}

	/**
	 *
Construct a collection of the specified type and the specified size for use on a POJO field.  You can override
	 * this with Guice or whatnot.

	 *
	 *
The default is to call construct(Class), with one twist - if a Set, SortedSet, or List interface is presented,
	 * Objectify will construct a HashSet, TreeSet, or ArrayList (respectively).  If you override this method with
	 * dependency injection and you use uninitialized fields of these interface types in your entity pojos, you will
	 * need to bind these interfaces to concrete types.

	 */
	@SuppressWarnings("unchecked")
	public <T extends Collection<?>> T constructCollection(final Class<T> type, final int size) {
		if ((Class<?>)type == List.class || (Class<?>)type == Collection.class)
			return (T)new ArrayList<>(size);
		else if ((Class<?>)type == Set.class)
			return (T)new HashSet<>((int)(size * 1.5));
		else if ((Class<?>)type == SortedSet.class)
			return (T)new TreeSet<>();
		else
			return construct(type);
	}

	/**
	 *
Construct a map of the specified type for use on a POJO field.  You can override this with Guice or whatnot.

	 *
	 *
The default is to call construct(Class), with one twist - if a Map or SortedMap List interface is presented,
	 * Objectify will construct a HashMap or TreeMap (respectively).  If you override this method with
	 * dependency injection and you use uninitialized fields of these interface types in your entity pojos, you will
	 * need to bind these interfaces to concrete types.

	 */
	@SuppressWarnings("unchecked")
	public <T extends Map<?, ?>> T constructMap(final Class<T> type) {
		if ((Class<?>)type == Map.class)
			return (T)new HashMap<>();
		else if ((Class<?>)type == SortedMap.class)
			return (T)new TreeMap<>();
		else
			return construct(type);
	}

	/**
	 * This is the beginning of any Objectify session.  It creates an Objectify instance with the default
	 * options, unless you override this method to alter the options.  You can also override this method
	 * to produce a wholly different Objectify implementation (possibly using ObjectifyWrapper).
	 *
	 *
The default options are:

	 *
	 *

	 *
Do NOT begin a transaction.

	 *
DO use a global cache.

	 *
Use STRONG consistency.

	 *
Apply no deadline to calls.

	 *

	 *
	 *
Note that when using Objectify you will almost never directly call this method.  Instead you
	 * should call the static ofy() method on ObjectifyService.

	 *
	 * @return a new Objectify instance
	 *
	 * @deprecated This method is a holdover from the 1.x days and will be removed soon.
	 * 		Clients should use {@link ObjectifyService#ofy()} to obtain Objectify instances.
	 */
	@Deprecated
	public Objectify begin() {
		return new ObjectifyImpl(this);
	}

	/**
	 *
All POJO entity classes which are to be managed by Objectify
	 * must be registered first.  This method must be called in a single-threaded
	 * mode sometime around application initialization.

	 *
	 *
Any extra translators must be added to the Translators *before*
	 * entity classes are registered.

	 *
	 *
Attempts to re-register entity classes are ignored.

	 */
	public <T> void register(final Class<T> clazz) {
		this.registrar.register(clazz);
	}

	/**
	 * Get the object that tracks memcache stats.
	 */
	public EntityMemcacheStats getMemcacheStats() { return this.memcacheStats; }

	/**
	 * Sets the error handler for the main memcache object.
	 */
//	@SuppressWarnings("deprecation")
//	public void setMemcacheErrorHandler(final com.google.appengine.api.memcache.ErrorHandler handler) {
//		this.entityMemcache.setErrorHandler(handler);
//	}

	//
	// Stuff which should only be necessary internally, but might be useful to others.
	//

	/**
	 * @return the metadata for a kind of typed object
	 * @throws IllegalArgumentException if the kind has not been registered
	 */
	public <T> EntityMetadata<T> getMetadata(final Class<T> clazz) throws IllegalArgumentException {
		return this.registrar.getMetadataSafe(clazz);
	}

	/**
	 * @return the metadata for a kind of entity based on its key
	 * @throws IllegalArgumentException if the kind has not been registered
	 */
	public <T> EntityMetadata<T> getMetadata(final com.google.cloud.datastore.Key key) throws IllegalArgumentException {
		return this.registrar.getMetadataSafe(key.getKind());
	}

	/**
	 * @return the metadata for a kind of entity based on its key
	 * @throws IllegalArgumentException if the kind has not been registered
	 */
	public <T> EntityMetadata<T> getMetadata(final Key<T> key) throws IllegalArgumentException {
		return this.registrar.getMetadataSafe(key.getKind());
	}

	/**
	 * Gets metadata for the specified kind, returning null if nothing registered. This method is not like
	 * the others because it returns null instead of throwing an exception if the kind is not found.
	 * @return null if the kind is not registered.
	 */
	public <T> EntityMetadata<T> getMetadata(final String kind) {
		return this.registrar.getMetadata(kind);
	}

	/**
	 * Named differently so you don't accidentally use the Object form
	 * @return the metadata for a kind of typed object.
	 * @throws IllegalArgumentException if the kind has not been registered
	 */
	@SuppressWarnings("unchecked")
	public <T> EntityMetadata<T> getMetadataForEntity(final T obj) throws IllegalArgumentException {
		// Type erasure sucks
		return (EntityMetadata<T>)this.getMetadata(obj.getClass());
	}

	/**
	 * Allocates a single id from the allocator for the specified kind.  Safe to use in concert
	 * with the automatic generator.  This is just a convenience method for allocateIds().
	 *
	 * @param clazz must be a registered entity class with a Long or long id field.
	 * @return a key with an id that is unique to the kind
	 */
	public <T> Key<T> allocateId(final Class<T> clazz) {
		return allocateIds(clazz, 1).iterator().next();
	}

	/**
	 * Allocates a single id from the allocator for the specified kind.  Safe to use in concert
	 * with the automatic generator.  This is just a convenience method for allocateIds().
	 *
	 * Note that the id is only unique within the parent, not across the entire kind.
	 *
	 * @param parentKeyOrEntity must be a legitimate parent for the class type.  It need not
	 * point to an existent entity, but it must be the correct type for clazz.
	 * @param clazz must be a registered entity class with a Long or long id field, and
	 * a parent key of the correct type.
	 * @return a key with a new id unique to the kind and parent
	 */
	public <T> Key<T> allocateId(final Object parentKeyOrEntity, final Class<T> clazz) {
		return allocateIds(parentKeyOrEntity, clazz, 1).iterator().next();
	}

	/**
	 *
Preallocate multiple unique ids within the namespace of the
	 * specified entity class.  These ids can be used in concert with the normal
	 * automatic allocation of ids when save()ing entities with null Long id fields.

	 *
	 *
The {@code KeyRange<?>} class is deprecated; when using this method,
	 * treat the return value as {@code List<Key<T>>}.

	 *
	 * @param clazz must be a registered entity class with a Long or long id field.
	 * @param num must be >= 1 and small enough we can fit a set of keys in RAM.
	 */
	public <T> KeyRange<T> allocateIds(final Class<T> clazz, final int num) {
		final String kind = Key.getKind(clazz);
		final IncompleteKey incompleteKey = datastore().newKeyFactory().setKind(kind).newKey();

		return allocate(incompleteKey, num);
	}

	/**
	 * Preallocate a contiguous range of unique ids within the namespace of the
	 * specified entity class and the parent key.  These ids can be used in concert with the normal
	 * automatic allocation of ids when put()ing entities with null Long id fields.
	 *
	 * @param parentKeyOrEntity must be a legitimate parent for the class type.  It need not
	 * point to an existent entity, but it must be the correct type for clazz.
	 * @param clazz must be a registered entity class with a Long or long id field, and
	 * a parent key of the correct type.
	 * @param num must be >= 1 and <= 1 billion
	 */
	public <T> KeyRange<T> allocateIds(final Object parentKeyOrEntity, final Class<T> clazz, final int num) {
		final Key<?> parent = keys().anythingToKey(parentKeyOrEntity);
		final String kind = Key.getKind(clazz);

		final IncompleteKey incompleteKey = com.google.cloud.datastore.Key.newBuilder(parent.getRaw(), kind).build();

		return allocate(incompleteKey, num);
	}

	/** Allocate num copies of the incompleteKey */
	private <T> KeyRange<T> allocate(final IncompleteKey incompleteKey, final int num) {
		final IncompleteKey[] allocations = new IncompleteKey[num];
		Arrays.fill(allocations, incompleteKey);

		final List<Key<T>> typedKeys = datastore().allocateId(allocations).stream()
				.map(Key::<T>create)
				.collect(Collectors.toList());

		return new KeyRange<>(typedKeys);
	}

	/**
	 *
Gets the master list of all registered TranslatorFactory objects.  By adding Translators, Objectify
	 * can process additional field types which are not part of the standard GAE SDK.  You must
	 * add translators *before* registering entity pojo classes.

	 *
	 * @return the repository of TranslatorFactory objects, to which you can optionally add translators
	 */
	public Translators getTranslators() {
		return this.translators;
	}

	/**
	 * Some tools for working with keys. This is an internal Objectify API and subject to change without
	 * notice. You probably want the Key.create() methods instead.
	 */
	public Keys keys() {
		return keys;
	}

	/**
	 * The method to call at any time to get the current Objectify, which may change depending on txn context.
	 * Normally you should use the static {@link ObjectifyService#ofy()} which calls this method.
	 */
	public Objectify ofy() {
		final Deque<Objectify> stack = stacks.get();

		if (stack.isEmpty())
			throw new IllegalStateException("You have not started an Objectify context. You are probably missing the " +
					"ObjectifyFilter. If you are not running in the context of an http request, see the " +
					"ObjectifyService.run() method.");

		return stack.getLast();
	}

	/**
	 *
Start a scope of work. This is the outermost scope of work, typically created by the ObjectifyFilter
	 * or by one of the methods on ObjectifyService. You need one of these to do anything at all.

	 */
	public ObjectifyImpl open() {
		final ObjectifyImpl objectify = new ObjectifyImpl(this);
		stacks.get().add(objectify);
		return objectify;
	}

	/** This is only public because it is used from the impl package; don't use this as a public API */
	public ObjectifyImpl open(final ObjectifyOptions opts, final TransactorSupplier transactorSupplier) {
		final ObjectifyImpl objectify = new ObjectifyImpl(this, opts, transactorSupplier);
		stacks.get().add(objectify);
		return objectify;
	}

	/** Pops context off of stack after a transaction completes. For internal housekeeping only. */
	public void close(final Objectify ofy) {
		final Deque<Objectify> stack = stacks.get();
		if (stack.isEmpty())
			throw new IllegalStateException("You have already destroyed the Objectify context.");

		final Objectify popped = stack.removeLast();
		assert popped == ofy : "Mismatched objectify instances; somehow the stack was corrupted";
	}
}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.Value;
import com.googlecode.objectify.impl.Path;

/**
 *
Just in case anyone has a {@code Value<?>} field. Just store it as-is.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class RawValueTranslatorFactory implements TranslatorFactory<Value<Object>, Object>
{
	/* */
	@Override
	public Translator<Value<Object>, Object> create(final TypeKey<Value<Object>> tk, final CreateContext ctx, final Path path) {
		if (!tk.isAssignableTo(Value.class))
			return null;

		return new NullSafeTranslator<Value<Object>, Object>() {
			@Override
			protected Value<Object> loadSafe(final Value<Object> value, final LoadContext ctx, final Path path) throws SkipException {
				return value;
			}

			@Override
			protected Value<Object> saveSafe(final Value<Object> pojo, final boolean index, final SaveContext ctx, final Path path) throws SkipException {
				return pojo;
			}
		};
	}
}


/*
 */

package com.googlecode.objectify;

import com.google.common.base.Preconditions;
import com.googlecode.objectify.util.Closeable;

/**
 * Holder of the master ObjectifyFactory and provider of the current thread-local Objectify instance.
 * Call {@code ofy()} at any point to get the current Objectify with the correct transaction context.
 *
 * @author Jeff Schnitzer
 */
public class ObjectifyService
{
	/** */
	private static ObjectifyFactory factory;

	/** This is a shortcut for {@code ObjectifyService.init(new ObjectifyFactory())}*/
	public static void init() {
		init(new ObjectifyFactory());
	}

	/** */
	public static void init(final ObjectifyFactory fact) {
		factory = fact;
	}

	/**
	 * @return the current factory
	 */
	public static ObjectifyFactory factory() {
		Preconditions.checkState(factory != null, "You must call ObjectifyService.init() before using Objectify");
		return factory;
	}

	/**
	 * A shortcut for {@code factory().register()}
	 *
	 * @see ObjectifyFactory#register(Class)
	 */
	public static void register(Class<?> clazz) {
		factory().register(clazz);
	}

	/**
	 * The method to call at any time to get the current Objectify, which may change depending on txn context
	 */
	public static Objectify ofy() {
		return factory().ofy();
	}

	/**
	 *
Runs one unit of work, making the root Objectify context available. This does not start a transaction,
	 * but it makes the static ofy() method return an appropriate object.

	 *
	 *
Normally you do not need to use this method. When servicing a normal request, the ObjectifyFilter
	 * will run this for you. This method is useful for using Objectify outside of a normal request -
	 * using the remote api, for example.

	 *
	 *
Alternatively, you can use the begin() method and close the session manually.

	 *
	 * @return the result of the work.
	 */
	public static <R> R run(final Work<R> work) {
		try (Closeable closeable = begin()) {
			return work.run();
		}
	}

	/**
	 *
An alternative to run() which is somewhat easier to use with testing (ie, @Before and @After) frameworks.
	 * You must close the return value at the end of the request in a finally block. It's better/safer to use run().

	 *
	 *
This method is not typically necessary - in a normal request, the ObjectifyFilter takes care of this housekeeping
	 * for you. However, in unit tests or remote API calls it can be useful.

	 */
	public static Closeable begin() {
		return factory().open();
	}
}


package com.googlecode.objectify.util;

import com.google.cloud.datastore.Value;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * Some static utility methods for interacting with {@code Value<?>}
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Values
{
	/** @return a value that has the specified index flag (possibly the original itself) */
	@SuppressWarnings("unchecked")
	public static <D> Value<D> index(final Value<D> original, final boolean index) {
		if (original.excludeFromIndexes() == !index) {
			return original;
		} else {
			return (Value<D>)original.toBuilder().setExcludeFromIndexes(!index).build();
		}
	}

	/**
	 * The datastore has a weird behavior of reordering values in a list so that indexed ones come before nonindexed
	 * ones. This can really mess up ordered lists. So if we find a heterogeneous list, we need to force index everything.
	 */
	public static void homogenizeIndexes(final List<Value<?>> list) {
		if (isIndexHomogeneous(list))
			return;

		for (int i=0; i<list.size(); i++) {
			final Value<?> value = list.get(i);
			if (value.excludeFromIndexes())
				list.set(i, index(value, true));
		}
	}

	/** @return true if all values have the same index status */
	private static boolean isIndexHomogeneous(final List<Value<?>> list) {
		if (list.isEmpty())
			return true;

		final boolean excluded = list.get(0).excludeFromIndexes();

		for (int i=1; i<list.size(); i++) {
			if (list.get(i).excludeFromIndexes() != excluded)
				return false;
		}

		return true;
	}
}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.LongValue;
import com.google.cloud.datastore.Value;
import com.google.cloud.datastore.ValueType;
import com.google.common.primitives.Primitives;
import com.googlecode.objectify.impl.Path;

/**
 *
Integers are funky in the datastore.  You can save numbers of any size, but they always retrieve as Long.
 * For the hell of it, we also handle String in the datastore by trying to parse it.

 *
 *
Not a ValueTranslatorFactory because Numbers are not assignable to primitives.  Java lame.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class IntegerTranslatorFactory implements TranslatorFactory<Number, Long>
{
	@Override
	public Translator<Number, Long> create(final TypeKey<Number> tk, final CreateContext ctx, final Path path) {
		final Class<?> clazz = Primitives.wrap(tk.getTypeAsClass());

		if (!(clazz == Byte.class || clazz == Short.class || clazz == Integer.class || clazz == Long.class))
			return null;

		return new ValueTranslator<Number, Long>(ValueType.LONG, ValueType.STRING) {
			@Override
			protected Number loadValue(final Value<Long> value, final LoadContext ctx, final Path path) throws SkipException {
				// In theory it's a number, but maybe there was a string instead? We'll have to remove the typecheck
				// in the base ValueTranslator class for this to work.
				if (value.getType() == ValueType.STRING) {
					try {
						return coerceNumber(Long.valueOf(((Value<String>)(Value)value).get()), clazz);
					} catch (NumberFormatException ex) {}
				} else {
					return coerceNumber(value.get(), clazz);
				}

				path.throwIllegalState("Don't know how to translate " + value + " to a number");
				return null;	// never gets here
			}

			@Override
			protected Value<Long> saveValue(final Number value, final SaveContext ctx, final Path path) throws SkipException {
				return LongValue.of(value.longValue());
			}
		};
	}

	/**
	 * Coerces the value to be a number of the specified type; needed because
	 * all numbers come back from the datastore as Long/Double and this screws up
	 * any type that expects something smaller. We don't need to worry about primitive
	 * types because we wrapped the class earlier.
	 */
	private Number coerceNumber(final Number value, final Class<?> type) {
		if (type == Byte.class) return value.byteValue();
		else if (type == Short.class) return value.shortValue();
		else if (type == Integer.class) return value.intValue();
		else if (type == Long.class) return value.longValue();
		else throw new IllegalArgumentException();	// should be impossible
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Cursor;
import com.googlecode.objectify.cmd.QueryKeys;
import com.googlecode.objectify.cmd.SimpleQuery;

/**
 * Base for command classes that include methods for defining a query (filter, order, limit, etc).
 * Does not include the methods for executing a query.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public abstract class SimpleQueryImpl<T> implements SimpleQuery<T>
{
	/** Constant seems to have disappeared from the SDK */
	private final String KEY_RESERVED_PROPERTY = "__key__";

	/** */
	protected final LoaderImpl loader;

	/**
	 * There is a special case - if loader is null, use 'this' as the LoaderImpl. It's a bit of a hack
	 * but we can't pass in 'this' to super constructors.
	 */
	SimpleQueryImpl(final LoaderImpl loader) {
		this.loader = loader == null ? (LoaderImpl)this : loader;
	}

	/**
	 * Create an initial query object; for a typed query this will have a class, otherwise it will be generic.
	 * For the real QueryImpl itself this is a clone() operation.
	 */
	abstract QueryImpl<T> createQuery();

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.QueryCommon#filterKey(java.lang.String, java.lang.Object)
	 */
	@Override
	public QueryImpl<T> filterKey(String condition, Object value) {
		QueryImpl<T> q = createQuery();
		q.addFilter(KEY_RESERVED_PROPERTY + " " + condition.trim(), value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.SimpleQuery#filterKey(java.lang.Object)
	 */
	@Override
	public QueryImpl<T> filterKey(Object value) {
		return filterKey("=", value);
	}

	@Override
	public QueryImpl<T> orderKey(boolean descending) {
		String prefix = descending ? "-" : "";

		QueryImpl<T> q = createQuery();
		q.addOrder(prefix + KEY_RESERVED_PROPERTY);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#ancestor(java.lang.Object)
	 */
	@Override
	public QueryImpl<T> ancestor(Object keyOrEntity) {
		QueryImpl<T> q = createQuery();
		q.setAncestor(keyOrEntity);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#limit(int)
	 */
	@Override
	public QueryImpl<T> limit(int value) {
		QueryImpl<T> q = createQuery();
		q.setLimit(value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#offset(int)
	 */
	@Override
	public QueryImpl<T> offset(int value) {
		QueryImpl<T> q = createQuery();
		q.setOffset(value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#startCursor(com.google.cloud.datastore.Cursor)
	 */
	@Override
	public QueryImpl<T> startAt(Cursor value) {
		QueryImpl<T> q = createQuery();
		q.setStartCursor(value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#endCursor(com.google.cloud.datastore.Cursor)
	 */
	@Override
	public QueryImpl<T> endAt(Cursor value) {
		QueryImpl<T> q = createQuery();
		q.setEndCursor(value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#chunk(int)
	 */
	@Override
	public QueryImpl<T> chunk(int value) {
		QueryImpl<T> q = createQuery();
		q.setChunk(value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.SimpleQuery#hybrid(boolean)
	 */
	@Override
	public QueryImpl<T> hybrid(boolean force) {
		QueryImpl<T> q = createQuery();
		q.setHybrid(force);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.SimpleQuery#chunkAll()
	 */
	@Override
	public QueryImpl<T> chunkAll()
	{
		return chunk(Integer.MAX_VALUE);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#keys()
	 */
	@Override
	public QueryKeys<T> keys() {
		QueryImpl<T> q = createQuery();
		q.checkKeysOnlyOk();
		return new QueryKeysImpl<>(q);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.SimpleQuery#distinct(boolean)
	 */
	@Override
	public QueryImpl<T> distinct(boolean value) {
		QueryImpl<T> q = createQuery();
		q.setDistinct(value);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.SimpleQuery#project(String...)
	 */
	@Override
	public QueryImpl<T> project(String... fields) {
		QueryImpl<T> q = createQuery();
		q.addProjection(fields);
		return q;
	}

}


package com.googlecode.objectify;

import com.googlecode.objectify.impl.ref.LiveRef;

import java.io.Serializable;

/**
 *
Ref<?> is a Key<?> which allows the entity value to be fetched directly.

 *
 *
Note that the methods might or might not throw runtime exceptions related to datastore operations;
 * ConcurrentModificationException, DatastoreTimeoutException, DatastoreFailureException, and DatastoreNeedIndexException.
 * Some Refs hide datastore operations that could throw these exceptions.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
abstract public class Ref<T> implements Serializable, Comparable<Ref<T>>
{
	private static final long serialVersionUID = 1L;

	/** Key.create(Blah.class, id) is easier to type than new Key<Blah>(Blah.class, id) */
	public static <T> Ref<T> create(Key<T> key) {
		if (key == null)
			throw new NullPointerException("Cannot create a Ref from a null key");

		return new LiveRef<>(key);
	}

	/** Creates a Ref from a registered pojo entity */
	public static <T> Ref<T> create(T value) {
		Key<T> key = Key.create(value);
		return create(key);
	}

	/** The key associated with this ref */
	protected Key<T> key;

	/** For GWT serialization */
	protected Ref() {}

	/**
	 * Create a Ref based on the key, with the specified session
	 */
	protected Ref(Key<T> key) {
		if (key == null)
			throw new NullPointerException("Cannot create a Ref for a null key");

		this.key = key;
	}

	/**
	 * @return the key associated with this Ref
	 */
	public Key<T> key() {
		return key;
	}

	/**
	 * Obtain the entity value associated with the key. Will pull from session if present, otherwise will
	 * fetch from the datastore.
	 *
	 * @return the entity referenced, or null if the entity was not found
	 */
	abstract public T get();

	/**
	 * If an entity has been loaded into the session or is otherwise available, this will return true.
	 * Calls to get() will not require a trip to backing store.
	 * Note that even when loaded, get() can still return null if there is no entity which corresponds to the key.
	 *
	 * @return true if the value is in the session or otherwise immediately available; false if get() will
	 * require a trip to the datastore or memcache.
	 */
	abstract public boolean isLoaded();

	/**
	 * This method exists to facilitate serialization via javabeans conventions. Unlike get(),
	 * it will return null if isLoaded() is false.
	 *
	 * @return the entity referenced, or null if either the entity was not found or isLoaded() is false.
	 */
	final public T getValue() {
		return isLoaded() ? get() : null;
	}

	/**
	 * Same as key() but conforms to JavaBeans conventions in case this is being processed by a JSON
	 * converter or expression language.
	 */
	final public Key<T> getKey() {
		return key();
	}

	/**
	 * Obtain the entity value, throwing an exception if the entity was not found.
	 *
	 * @return the entity referenced. Never returns null.
	 * @throws NotFoundException if the specified entity was not found
	 */
	final public T safe() throws NotFoundException {
		T t = this.get();
		if (t == null)
			throw new NotFoundException(key());
		else
			return t;
	}

	/** Comparison is based on key */
	@Override
	public int compareTo(Ref<T> o) {
		return this.key().compareTo(o.key());
	}

	/** Equality comparison is based on key equivalence */
	@Override
	public boolean equals(Object obj) {
		return obj != null && obj instanceof Ref && key().equals(((Ref<?>)obj).key());
	}

	/** Type-safe comparison for key equivalence */
	public boolean equivalent(Ref<T> other) {
		return equals(other);
	}

	/** Type safe comparison for key equivalence */
	public boolean equivalent(Key<T> other) {
		return key().equivalent(other);
	}

	/** Hash code is simply that of key */
	@Override
	public int hashCode() {
		return key().hashCode();
	}

	/** Renders some info about the key */
	@Override
	public String toString() {
		return this.getClass().getSimpleName() + "(" + key() + ")";
	}
}


/*
 */

package com.googlecode.objectify.util;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;

/**
 * Identity-based Multimap that stores values in an ArrayList.
 *
 * @author Jeff Schnitzer
 */
public class IdentityMultimapList<K, V> extends IdentityHashMap<K, List<V>>
{
	private static final long serialVersionUID = 1L;

	/**
	 * Adds a value to the set associated with the key.
	 */
	public boolean add(K key, V value) {

		List<V> list = this.get(key);
		if (list == null) {
			list = new ArrayList<>();
			this.put(key, list);
		}

		return list.add(value);
	}
}


package com.googlecode.objectify.cache;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 *

 * A Future<?> wrapper that executes an abstract method with the result at some point after
 * the data becomes available.  A "best effort" is made to ensure execution, but it may be
 * left untriggered until the end of a request.
 *

 *
 *

 * Notification will happen ONCE:
 *

 *
 *

 *
After get() is called

 *
When the future is done and isDone() is called

 *
At the end of a request that has the AsyncCacheFilter enabled.

 *

 *
 *
Use the AsyncCacheFilter for normal requests. For situations where a filter is not appropriate
 * (ie, the remote api) be sure to call PendingFutures.completeAllPendingFutures() manually.

 *
 *
Note that if you are using this with Objectify, you probably want to use ObjectifyFilter.complete()
 * rather than PendingFutures or AsyncCacheFilter static methods.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
abstract public class TriggerFuture<T> implements Future<T>
{
	/** Wrap the raw Future<?> */
	protected Future<T> raw;

	/** If we have run the trigger() method already */
	boolean triggered = false;

	/** Wrap a normal Future<?> */
	public TriggerFuture(Future<T> raw) {
		this.raw = raw;

		// We now need to register ourself so that we'll get checked at future API calls
		PendingFutures.addPending(this);
	}

	/**
	 * This method will be called ONCE upon completion of the future, successful or not.
	 * Beware that this.get() may throw an exception.
	 */
	abstract protected void trigger();

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#cancel(boolean)
	 */
	@Override
	public boolean cancel(boolean mayInterruptIfRunning) {
		throw new UnsupportedOperationException("This makes my head spin. Don't do it.");
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#isCancelled()
	 */
	@Override
	public boolean isCancelled()
	{
		return this.raw.isCancelled();
	}

	/**
	 * This version also checks to see if we are done and we still need to call the trigger.
	 * If so, it calls it.
	 *
	 * @see java.util.concurrent.Future#isDone()
	 */
	@Override
	public boolean isDone() {
		boolean done = this.raw.isDone();

		if (!triggered && done) {
			this.triggered = true;
			PendingFutures.removePending(this);

			this.trigger();
		}

		return done;
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#get()
	 */
	@Override
	public T get() throws InterruptedException, ExecutionException
	{
		try {
			return this.raw.get();
		} finally {
			this.isDone();
		}
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
	 */
	@Override
	public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
	{
		try {
			return this.raw.get(timeout, unit);
		} finally {
			this.isDone();
		}
	}
}