package com.googlecode.objectify.cache;

import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;

/**
 *
This class maintains a thread local list of all the outstanding Future<?> objects
 * that have pending triggers.  When a Future<?> is done and its trigger is executed,
 * it is removed from the list.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class PendingFutures
{
	/**
	 * We use ConcurrentHashMap not for concurrency but because it doesn't throw
	 * ConcurrentModificationException.  We need to be able to iterate while Futures remove
	 * themselves from the set. A Set is just a Map of key to key.
	 */
	private static ThreadLocal<Map<Future<?>, Future<?>>> pending = ThreadLocal.withInitial(() -> new ConcurrentHashMap<>(64, 0.75f, 1));

	/**
	 * Register a pending Future that has a callback.
	 * @param future must have at least one callback
	 */
	public static void addPending(Future<?> future) {
		pending.get().put(future, future);
	}

	/**
	 * Deregister a pending Future that had a callback.
	 */
	public static void removePending(Future<?> future) {
		pending.get().remove(future);
	}

	/**
	 * Iterate through all pending futures and get() them, forcing any callbacks to be called.
	 * This is used only by the AsyncCacheFilter (if using cache without Objectify) or ObjectifyFilter
	 * (if using Objectify normally) because we don't have a proper hook otherwise.
	 */
	public static void completeAllPendingFutures() {
		// This will cause done Futures to fire callbacks and remove themselves
		for (Future<?> fut: pending.get().keySet()) {
			try {
				fut.get();
			}
			catch (Exception e) {
				log.error("Error cleaning up pending Future: " + fut, e);
			}
		}
	}
}


package com.googlecode.objectify.impl;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Subclass;

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

/**
 *
Maintains information about registered entity classes

 *
 *
There logic here is convoluted by polymorphic hierarchies.  Entity classes can
 * be registered in any particular order, requiring some considerable care.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class Registrar
{
	/** Needed to obtain the converters */
	protected ObjectifyFactory fact;

	/** This maps kind to EntityMetadata */
	protected Map<String, EntityMetadata<?>> byKind = new HashMap<>();

	/** True if any @Cached entities have been registered */
	protected boolean cacheEnabled;

	/** @return true if any entities are cacheable */
	public boolean isCacheEnabled()
	{
		return this.cacheEnabled;
	}

	/**
	 * @param fact is so that the translations can be obtained
	 */
	public Registrar(ObjectifyFactory fact) {
		this.fact = fact;
	}

	/**
	 *
All @Entity and @Subclass classes (for both entity and embedded classes)
	 * must be registered before using Objectify to load or save data.  This method
	 * must be called in a single-threaded mode sometime around application initialization.

	 *
	 *
Re-registering a class has no effect.

	 *
	 * @param clazz must be annotated with either @Entity or @Subclass
	 */
	public <T> void register(Class<T> clazz) {
		// There are two possible cases
		// 1) This might be a simple class with @Entity
		// 2) This might be a class annotated with @Subclass

		// @Entity is inherited, but we only create entity metadata for the class with the @Entity declaration
		if (TypeUtils.isDeclaredAnnotationPresent(clazz, Entity.class)) {
			String kind = Key.getKind(clazz);

			// If we are already registered, ignore
			if (this.byKind.containsKey(kind))
				return;

			EntityMetadata<T> cmeta = new EntityMetadata<>(this.fact, clazz);
			this.byKind.put(kind, cmeta);

			if (cmeta.getCacheExpirySeconds() != null)
				this.cacheEnabled = true;
		}
		else if (clazz.isAnnotationPresent(Subclass.class)) {
			// We just need to make sure that a translator was created
			fact.getTranslators().getRoot(clazz);
		}
		else {
			throw new IllegalArgumentException(clazz + " must be annotated with either @Entity or @Subclass");
		}
	}

	/**
	 * @return the metadata for the specified kind, or null if there was nothing appropriate registered
	 */
	@SuppressWarnings("unchecked")
	public <T> EntityMetadata<T> getMetadata(String kind) {
		return (EntityMetadata<T>)this.byKind.get(kind);
	}

	/**
	 * @return the metadata for the specified class, or null if there was nothing appropriate registered
	 */
	@SuppressWarnings("unchecked")
	public <T> EntityMetadata<T> getMetadata(Class<T> clazz) {
		return getMetadata(Key.getKind(clazz));
	}

	/**
	 * Gets metadata for the specified kind
	 * @throws IllegalArgumentException if the kind has not been registered
	 */
	public <T> EntityMetadata<T> getMetadataSafe(String kind) throws IllegalArgumentException {
		EntityMetadata<T> metadata = this.getMetadata(kind);
		if (metadata == null)
			throw new IllegalArgumentException("No entity class has been registered which matches kind '" + kind + "'");
		else
			return metadata;
	}

	/**
	 * @return the metadata for a kind of typed object
	 * @throws IllegalArgumentException if the kind has not been registered
	 */
	public <T> EntityMetadata<T> getMetadataSafe(Class<T> clazz) throws IllegalArgumentException {
		EntityMetadata<T> metadata = this.getMetadata(clazz);
		if (metadata == null)
			throw new IllegalArgumentException("No class '" + clazz.getName() + "' was registered");
		else
			return metadata;
	}

}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.EntityQuery;
import com.google.cloud.datastore.KeyQuery;
import com.google.cloud.datastore.ProjectionEntityQuery;
import com.google.cloud.datastore.QueryResults;
import com.google.common.collect.Iterators;
import com.googlecode.objectify.Key;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * Logic for dealing with queries.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
@RequiredArgsConstructor
public class QueryEngine
{
	/** */
	private final LoaderImpl loader;
	private final AsyncDatastoreReaderWriter ds;

	/**
	 * Perform a keys-only query.
	 */
	public <T> QueryResults<Key<T>> queryKeysOnly(final KeyQuery query) {
		log.trace("Starting keys-only query");

		return new KeyQueryResults<>(ds.run(query));
	}

	/**
	 * Perform a keys-only plus batch gets.
	 */
	public <T> QueryResults<T> queryHybrid(final KeyQuery query, final int chunkSize) {
		log.trace("Starting hybrid query");

		final QueryResults<Key<T>> results = new KeyQueryResults<>(ds.run(query));

		return new HybridQueryResults<>(loader.createLoadEngine(), results, chunkSize);
	}

	/**
	 * A normal, non-hybrid query
	 */
	public <T> QueryResults<T> queryNormal(final EntityQuery query, final int chunkSize) {
		log.trace("Starting normal query");

		// Normal queries are actually more complex than hybrid queries because we need the fetched entities to
		// be stuffed back into the engine to satisfy @Load instructions without extra fetching. Even though
		// this looks like we're doing hybrid load-by-key operations, the data is pulled from the stuffed values.

		final LoadEngine loadEngine = loader.createLoadEngine();

		final QueryResults<Entity> entityResults = ds.run(query);

		final QueryResults<com.google.cloud.datastore.Key> stuffed = new StuffingQueryResults(loadEngine, entityResults);

		final QueryResults<Key<T>> keyResults = new KeyQueryResults<>(stuffed);

		return new HybridQueryResults<>(loadEngine, keyResults, chunkSize);
	}

	/**
	 * A projection query. Bypasses the session entirely.
	 */
	public <T> QueryResults<T> queryProjection(final ProjectionEntityQuery query) {
		log.trace("Starting projection query");

		final LoadEngine loadEngine = loader.createLoadEngine();

		return new ProjectionQueryResults<>(ds.run(query), loadEngine);
	}

	/**
	 * The fundamental query count operation. This doesn't appear to be implemented in the new SDK, so we simulate
	 * with a keys-only query.
	 */
	public int queryCount(final KeyQuery query) {
		log.trace("Starting count query");

		final QueryResults<com.google.cloud.datastore.Key> results = ds.run(query);
		return Iterators.size(results);
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Cursor;
import com.google.cloud.datastore.QueryResults;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.util.IterateFunction;
import lombok.Getter;

import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

/**
 * Converts keys-only query results into hybrid query results. This involves chunking the keys into batches and loading
 * each from the datastore. Care is taken to preserve cursor behavior and filter null results (possible due to both
 * the time delay between the query and the load and also eventual consistency in general).
 */
public class HybridQueryResults<T> implements QueryResults<T> {

	/** */
	private final LoadEngine loadEngine;

	/** */
	private final Iterator<ResultWithCursor<T>> stream;

	/** We need this so we can acquire the cursor at the end */
	private final QueryResults<Key<T>> source;

	/** Track the values for the next time we need to get this */
	@Getter
	private Cursor cursorAfter;

	/**
	 * @param chunkSize can be MAX_VALUE to indicate "just one chunk"
	 */
	public HybridQueryResults(
			final LoadEngine loadEngine,
			final QueryResults<Key<T>> source,
			final int chunkSize) {

		this.loadEngine = loadEngine;
		this.source = source;

		// Always start with whatever was in the source to begin with
		this.cursorAfter = source.getCursorAfter();

		// Turn the result in to {key, cursor} pairs
		final Iterator<ResultWithCursor<Key<T>>> withCursor = new ResultWithCursor.Iterator<>(source);

		// Break it into chunks
		final Iterator<Iterator<ResultWithCursor<Key<T>>>> chunked = safePartition(withCursor, chunkSize);

		// Load each chunk as a batch
		final Iterator<Iterator<ResultWithCursor<T>>> loaded = Iterators.transform(chunked, this::load);

		// Put the chunks back into a linear stream
		final Iterator<ResultWithCursor<T>> concatenated = Iterators.concat(loaded);

		// Filter out any null results
		this.stream = Iterators.filter(concatenated, rwc -> rwc.getResult() != null);
	}

	/** Detects Integer.MAX_VALUE and prevents OOM exceptions */
	private <T> Iterator<Iterator<T>> safePartition(final Iterator<T> input, int chunkSize) {
		// Cloud Datastore library errors if you try to fetch more than 1000 keys at a time
		if (chunkSize > 1000) {
			chunkSize = 1000;
		}
		return Iterators.transform(Iterators.partition(input, chunkSize), IterateFunction.instance());
	}

	/** Loads them; note that it's possible for some loaded results to be null */
	private Iterator<ResultWithCursor<T>> load(final Iterator<ResultWithCursor<Key<T>>> keys) {
		final List<Entry<ResultWithCursor<Key<T>>, Result<T>>> results = Lists.newArrayList();

		while (keys.hasNext()) {
			final ResultWithCursor<Key<T>> next = keys.next();
			results.add(Maps.immutableEntry(next, loadEngine.load(next.getResult())));
		}

		loadEngine.execute();

		return Iterators.transform(results.iterator(), entry -> new ResultWithCursor<>(entry.getValue().now(), entry.getKey().getCursorAfter()));
	}

	@Override
	public boolean hasNext() {
		final boolean hasNext = stream.hasNext();

		// This addresses the edge case of iterating on an empty result set. In that case, the
		// cursor we get after calling source.hasNext() is different from the one we get before.
		if (!hasNext)
			this.cursorAfter = source.getCursorAfter();

		return hasNext;
	}

	@Override
	public T next() {
		final ResultWithCursor<T> result = stream.next();
		cursorAfter = result.getCursorAfter();

		return result.getResult();
	}

	/** Not implemented */
	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}

	@Override
	public Class<?> getResultClass() {
		// Not really possible to do this; a query could produce anything
		return Object.class;
	}
}


package com.googlecode.objectify.util;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class ArrayUtils {

	private ArrayUtils() {
	}

	/**
	 * Copies an array of primitive longs while boxing them at the same time,
	 * and then converting into a list. Can't use {@link Arrays#asList(Object[])}
	 * directly as that will create a list of arrays instead.
	 *
	 * @param elements The array of primitive longs to convert to a list
	 * @return A list of boxed {@link long} values
	 */
	public static List<Long> asList(long... elements) {
		Objects.requireNonNull(elements);
		Long[] copy = new Long[elements.length];
		for (int index = 0; index < elements.length; index++) {
			copy[index] = elements[index];
		}
		return Arrays.asList(copy);
	}
}


package com.googlecode.objectify;

/**
 *
Enhances the basic Result<?> with some additional methods useful when loading data.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class LoadResult<T> implements Result<T>
{
	private final Key<T> key;
	private final Result<T> result;

	public LoadResult(Key<T> key, Result<T> result) {
		this.key = key;
		this.result = result;
	}

	/**
	 * Obtain the loaded value now.
	 */
	@Override
	public T now() {
		return result.now();
	}

	/**
	 * Like now(), but throws NotFoundException instead of returning null.
	 * @throws NotFoundException if the loaded value was not found
	 */
	public final T safe() throws NotFoundException {
		T t = now();
		if (t == null)
			throw new NotFoundException(key);
		else
			return t;
	}
}


/*
 */

package com.googlecode.objectify.util;

import com.googlecode.objectify.impl.Path;

/**
 *
Static methods that help out making log messages.

 *
 * @author Jeff Schnitzer
 */
public class LogUtils
{
	/** */
	static final int PATH_PADDING = 13;

	private LogUtils() {
	}

	/** Create a log a message for a given path */
	public static String msg(Path path, String msg) {
		StringBuilder bld = new StringBuilder();
		bld.append("\t.");
		bld.append(path.toPathString());

		if (bld.length() < PATH_PADDING)
			while (bld.length() < PATH_PADDING)
				bld.append(' ');
		else
			bld.append('\t');

		bld.append(msg);

		return bld.toString();
	}
}


package com.googlecode.objectify;

import com.google.cloud.datastore.KeyFactory;
import com.google.cloud.datastore.PathElement;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.impl.Keys;
import com.googlecode.objectify.impl.TypeUtils;
import com.googlecode.objectify.util.KeyFormat;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.util.Iterator;

/**
 *
A typesafe wrapper for the datastore Key object.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 * @author Scott Hernandez
 */
@EqualsAndHashCode(of="raw")
public class Key<T> implements Serializable, Comparable<Key<?>>
{
	private static final long serialVersionUID = -262390393952444121L;

	/** Key.create(key) is easier to type than new Key<Blah>(key) */
	public static <T> Key<T> create(final com.google.cloud.datastore.Key raw) {
		if (raw == null)
			throw new NullPointerException("Cannot create a Key<?> from a null datastore Key");

		return new Key<>(raw);
	}

	/** Key.create(Blah.class, id) is easier to type than new Key<Blah>(Blah.class, id) */
	public static <T> Key<T> create(final Class<? extends T> kindClass, final long id) {
		return new Key<>(kindClass, id);
	}

	/** Key.create(Blah.class, name) is easier to type than new Key<Blah>(Blah.class, name) */
	public static <T> Key<T> create(final Class<? extends T> kindClass, final String name) {
		return new Key<>(kindClass, name);
	}

	/** Key.create(parent, Blah.class, id) is easier to type than new Key<Blah>(parent, Blah.class, id) */
	public static <T> Key<T> create(final Key<?> parent, final Class<? extends T> kindClass, final long id) {
		return new Key<>(parent, kindClass, id);
	}

	/** Key.create(parent, Blah.class, name) is easier to type than new Key<Blah>(parent, Blah.class, name) */
	public static <T> Key<T> create(final Key<?> parent, final Class<? extends T> kindClass, final String name) {
		return new Key<>(parent, kindClass, name);
	}

	/** Key.create(urlSafeString) is easier to type than new Key<Blah>(urlSafeString) */
	public static <T> Key<T> create(final String urlSafeString) {
		if (urlSafeString == null)
			throw new NullPointerException("Cannot create a Key<?> from a null String");

		return new Key<>(urlSafeString);
	}

	/** This is an alias for Key.create(String) which exists for JAX-RS compliance. */
	public static <T> Key<T> valueOf(final String webSafeString) {
		return Key.create(webSafeString);
	}

	/** Create a key from a registered POJO entity. */
	public static <T> Key<T> create(final T pojo) {
		return ObjectifyService.factory().keys().keyOf(pojo);
	}

	/** */
	protected final com.google.cloud.datastore.Key raw;

	/** Cache the instance of the parent wrapper to avoid unnecessary garbage */
	transient protected Key<?> parent;

	/** Wrap a raw Key */
	private Key(final com.google.cloud.datastore.Key raw) {
		this.raw = raw;
		this.parent = null;
	}

	/** Create a key with a long id */
	private Key(final Class<? extends T> kindClass, final long id) {
		this(null, kindClass, id);
	}

	/** Create a key with a String name */
	private Key(final Class<? extends T> kindClass, final String name) {
		this(null, kindClass, name);
	}

	/** Create a key with a parent and a long id */
	private Key(final Key<?> parent, final Class<? extends T> kindClass, final long id) {
		final String kind = getKind(kindClass);

		if (parent == null) {
			this.raw = newKeyFactory().setKind(kind).newKey(id);
		} else {
			this.raw = com.google.cloud.datastore.Key.newBuilder(key(parent), kind, id).build();
		}

		this.parent = parent;
	}

	/** Create a key with a parent and a String name */
	private Key(final Key<?> parent, final Class<? extends T> kindClass, final String name) {
		final String kind = getKind(kindClass);

		if (parent == null) {
			this.raw = newKeyFactory().setKind(kind).newKey(name);
		} else {
			this.raw = com.google.cloud.datastore.Key.newBuilder(key(parent), kind, name).build();
		}

		this.parent = parent;
	}

	/**
	 * Reconstitute a Key from a web safe string. Understands the current (Cloud SDK) format and the legacy
	 * format (always starts with 'a'). These can be generated with toUrlSafeString() and toLegacyUrlSafeString().
	 */
	private Key(final String urlSafe) {
		this(Keys.fromUrlSafe(urlSafe));
	}

	/** Obtain it from the global ObjectifyFactory */
	private KeyFactory newKeyFactory() {
		return ObjectifyService.factory().datastore().newKeyFactory();
	}

	/**
	 * @return the raw datastore version of this key
	 */
	public com.google.cloud.datastore.Key getRaw() {
		return this.raw;
	}

	/**
	 * @return the id associated with this key, or 0 if this key has a name.
	 */
	public long getId() {
		return this.raw.getId();
	}

	/**
	 * @return the name associated with this key, or null if this key has an id
	 */
	public String getName() {
		return this.raw.getName();
	}

	/**
	 * @return the low-level datastore kind associated with this Key
	 */
	public String getKind() {
		return this.raw.getKind();
	}

	/**
	 * @return the parent key, or null if there is no parent.  Note that
	 *  the parent could potentially have any type.
	 */
	@SuppressWarnings("unchecked")
	public <V> Key<V> getParent() {
		if (this.parent == null && this.raw.getParent() != null)
			this.parent = new Key<V>(this.raw.getParent());

		return (Key<V>)this.parent;
	}

	/**
	 * Gets the root of a parent graph of keys.  If a Key has no parent, it is the root.
	 *
	 * @return the topmost parent key, or this object itself if it is the root.
	 * Note that the root key could potentially have any type.
	 */
	@SuppressWarnings("unchecked")
	public <V> Key<V> getRoot() {
		if (this.getParent() == null)
			return (Key<V>)this;
		else
			return this.getParent().getRoot();
	}

	/**
	 *
The new cloud sdk Key doesn't have compareTo(), so we reimplement the logic from the old GAE SDK.

	 */
	@Override
	public int compareTo(final Key<?> other) {
		if (this.raw == other.raw) {
			return 0;
		}

		{
			final int result = this.raw.getProjectId().compareTo(other.raw.getProjectId());
			if (result != 0)
				return result;
		}

		{
			final int result = this.raw.getNamespace().compareTo(other.raw.getNamespace());
			if (result != 0)
				return result;
		}

		{
			final int result = this.compareAncestors(other);
			if (result != 0)
				return result;
		}

		{
			// Too bad PathElement and Key don't share any kind of interface grrr
			final int result = this.getRaw().getKind().compareTo(other.getRaw().getKind());
			if (result != 0) {
				return result;
			}
			else if (this.raw.getNameOrId() == null && other.raw.getNameOrId() == null) {
				return compareToWithIdentityHash(this.raw, other.raw);
			}
			else if (this.raw.hasId()) {
				return other.raw.hasId() ? Long.compare(this.raw.getId(), other.raw.getId()) : -1;
			}
			else {
				return other.raw.hasId() ? 1 : this.raw.getName().compareTo(other.raw.getName());
			}
		}
	}

	private int compareAncestors(final Key<?> other) {
		final Iterator<PathElement> thisPath = this.raw.getAncestors().iterator();
		final Iterator<PathElement> otherPath = other.raw.getAncestors().iterator();

		int result;
		do {
			if (!thisPath.hasNext()) {
				return otherPath.hasNext() ? -1 : 0;
			}

			if (!otherPath.hasNext()) {
				return 1;
			}

			final PathElement thisKey = thisPath.next();
			final PathElement otherKey = otherPath.next();

			result = comparePathElements(thisKey, otherKey);
		} while (result == 0);

		return result;
	}

	private int comparePathElements(final PathElement here, final PathElement there) {
		final int result = here.getKind().compareTo(there.getKind());
		if (result != 0) {
			return result;
		}
		else if (here.getNameOrId() == null && there.getNameOrId() == null) {
			return compareToWithIdentityHash(here, there);
		}
		else if (here.hasId()) {
			return there.hasId() ? Long.compare(here.getId(), there.getId()) : -1;
		}
		else {
			return there.hasId() ? 1 : here.getName().compareTo(there.getName());
		}
	}

	/** I have no idea what this is about, it was in the old logic */
	private int compareToWithIdentityHash(final Object k1, final Object k2) {
		return Integer.compare(System.identityHashCode(k1), System.identityHashCode(k2));
	}

	/** A type-safe equivalence comparison */
	public boolean equivalent(final Key<T> other) {
		return equals(other);
	}

	/** A type-safe equivalence comparison */
	public boolean equivalent(final Ref<T> other) {
		return (other != null) && equals(other.key());
	}

	/** Creates a human-readable version of this key */
	@Override
	public String toString() {
		return "Key<?>(" + this.raw + ")";
	}

	/**
	 * @deprecated Use toUrlSafe() instead.
	 */
	@Deprecated
	public String getString() {
		return toUrlSafe();
	}

	/**
	 * This is an alias for toLegacyUrlSafe() and exists solely for backwards compatibility.
	 *
	 * @deprecated Use toLegacyUrlSafe(), or if you aren't trying to generate old-style keys, use
	 * toUrlSafe().
	 */
	@Deprecated
	public String toWebSafeString() {
		return this.raw.toUrlSafe();
	}

	/**
	 * Call toUrlSafe() on the underlying Key.  You can reconstitute a {@code Key<?>} using the
	 * constructor that takes a string. Note that toString() is only useful for debugging;
	 * it cannot be used to create a key with Key.create(String).
	 */
	public String toUrlSafe() {
		return this.raw.toUrlSafe();
	}

	/**
	 * Generates the string that would have been generated by the old appengine SDK.
	 * The strings look like 'ag1zfnZvb2Rvb2R5bmUwcgcLEgFCGAEM'. The String constructor
	 * for {@code Key<?>} understands both formats.
	 */
	public String toLegacyUrlSafe() {
		return KeyFormat.INSTANCE.formatOldStyleAppEngineKey(this.raw);
	}

	/**
	 * Easy null-safe conversion of the raw key.
	 */
	public static <V> Key<V> key(final com.google.cloud.datastore.Key raw) {
		if (raw == null)
			return null;
		else
			return new Key<>(raw);
	}

	/**
	 * Easy null-safe conversion of the typed key.
	 */
	public static com.google.cloud.datastore.Key key(final Key<?> typed) {
		if (typed == null)
			return null;
		else
			return typed.getRaw();
	}

	/**
	 *
Determines the kind for a Class, as understood by the datastore.  The first class in a
	 * hierarchy that has @Entity defines the kind (either explicitly or as that class' simplename).

	 *
	 *
If no @Entity annotation is found, just uses the simplename as is.

	 */
	public static String getKind(final Class<?> clazz) {
		final String kind = getKindRecursive(clazz);
		if (kind == null)
			return clazz.getSimpleName();
		else
			return kind;
	}

	/**
	 *
Recursively looks for the @Entity annotation.

	 *
	 * @return null if kind cannot be found
	 */
	private static String getKindRecursive(final Class<?> clazz) {
		if (clazz == Object.class)
			return null;

		final String kind = getKindHere(clazz);
		if (kind != null)
			return kind;
		else
			return getKindRecursive(clazz.getSuperclass());
	}

	/**
	 * Get the kind from the class if the class has an @Entity annotation, otherwise return null.
	 */
	private static String getKindHere(final Class<?> clazz) {
		// @Entity is inherited so we have to be explicit about the declared annotations
		final Entity ourAnn = TypeUtils.getDeclaredAnnotation(clazz, Entity.class);
		if (ourAnn != null)
			if (ourAnn.name().length() != 0)
				return ourAnn.name();
			else
				return clazz.getSimpleName();

		return null;
	}
}


package com.googlecode.objectify.repackaged.gentyref;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Utility class for doing reflection on types.
 *
 * @author Wouter Coekaerts <wouter@coekaerts.be>
 */
public class GenericTypeReflector {
	private static final Type UNBOUND_WILDCARD = new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{});

	private GenericTypeReflector() {
	}

	/**
	 * Returns the erasure of the given type.
	 */
	public static Class<?> erase(Type type) {
		if (type instanceof Class) {
			return (Class<?>)type;
		} else if (type instanceof ParameterizedType) {
			return (Class<?>)((ParameterizedType) type).getRawType();
		} else if (type instanceof TypeVariable) {
			TypeVariable<?> tv = (TypeVariable<?>)type;
			if (tv.getBounds().length == 0)
				return Object.class;
			else
				return erase(tv.getBounds()[0]);
		} else if (type instanceof GenericArrayType) {
			GenericArrayType aType = (GenericArrayType) type;
			return GenericArrayTypeImpl.createArrayType(erase(aType.getGenericComponentType()));
		} else {
			// TODO at least support CaptureType here
			throw new RuntimeException("not supported: " + type.getClass());
		}
	}

	/**
	 * Maps type parameters in a type to their values.
	 * @param toMapType Type possibly containing type arguments
	 * @param typeAndParams must be either ParameterizedType, or (in case there are no type arguments, or it's a raw type) Class
	 * @return toMapType, but with type parameters from typeAndParams replaced.
	 */
	private static Type mapTypeParameters(Type toMapType, Type typeAndParams) {
		if (isMissingTypeParameters(typeAndParams)) {
			return erase(toMapType);
		} else {
			VarMap varMap = new VarMap();
			Type handlingTypeAndParams = typeAndParams;
			while(handlingTypeAndParams instanceof ParameterizedType) {
				ParameterizedType pType = (ParameterizedType)handlingTypeAndParams;
				Class<?> clazz = (Class<?>)pType.getRawType(); // getRawType should always be Class
				varMap.addAll(clazz.getTypeParameters(), pType.getActualTypeArguments());
				handlingTypeAndParams = pType.getOwnerType();
			}
			return varMap.map(toMapType);
		}
	}

	/**
	 * Checks if the given type is a class that is supposed to have type parameters, but doesn't.
	 * In other words, if it's a really raw type.
	 */
	static boolean isMissingTypeParameters(Type type) {
		if (type instanceof Class) {
			for (Class<?> clazz = (Class<?>) type; clazz != null; clazz = clazz.getEnclosingClass()) {
				if (clazz.getTypeParameters().length != 0)
					return true;
			}
			return false;
		} else if (type instanceof ParameterizedType) {
			return false;
		} else {
			throw new AssertionError("Unexpected type " + type.getClass());
		}
	}

	/**
	 * Returns a type representing the class, with all type parameters the unbound wildcard ("?").
	 * For example, <tt>addWildcardParameters(Map.class)</tt> returns a type representing <tt>Map<?,?></tt>.
	 * @return

	 *
If clazz is a class or interface without type parameters, clazz itself is returned.

	 *
If clazz is a class or interface with type parameters, an instance of ParameterizedType is returned.

	 *
if clazz is an array type, an array type is returned with unbound wildcard parameters added in the the component type.
	 *

	 */
	public static Type addWildcardParameters(Class<?> clazz) {
		if (clazz.isArray()) {
			return GenericArrayTypeImpl.createArrayType(addWildcardParameters(clazz.getComponentType()));
		} else if (isMissingTypeParameters(clazz)) {
			TypeVariable<?>[] vars = clazz.getTypeParameters();
			Type[] arguments = new Type[vars.length];
			Arrays.fill(arguments, UNBOUND_WILDCARD);
			Type owner = clazz.getDeclaringClass() == null ? null : addWildcardParameters(clazz.getDeclaringClass());
			return new ParameterizedTypeImpl(clazz, arguments, owner);
		} else {
			return clazz;
		}
	}

	/**
	 * Finds the most specific supertype of <tt>type</tt> whose erasure is <tt>searchClass</tt>.
	 * In other words, returns a type representing the class <tt>searchClass</tt> plus its exact type parameters in <tt>type</tt>.
	 *
	 *

	 *
Returns an instance of {@link ParameterizedType} if <tt>searchClass</tt> is a real class or interface and <tt>type</tt> has parameters for it

	 *
Returns an instance of {@link GenericArrayType} if <tt>searchClass</tt> is an array type, and <tt>type</tt> has type parameters for it

	 *
Returns an instance of {@link Class} if <tt>type</tt> is a raw type, or has no type parameters for <tt>searchClass</tt>

	 *
Returns null if <tt>searchClass</tt> is not a superclass of type.

	 *

	 *
	 *
For example, with <tt>class StringList implements List<String></tt>, <tt>getExactSuperType(StringList.class, Collection.class)</tt>
	 * returns a {@link ParameterizedType} representing <tt>Collection<String></tt>.
	 *

	 */
	public static Type getExactSuperType(Type type, Class<?> searchClass) {
		if (type instanceof ParameterizedType || type instanceof Class || type instanceof GenericArrayType) {
			Class<?> clazz = erase(type);

			if (searchClass == clazz) {
				return type;
			}

			if (! searchClass.isAssignableFrom(clazz))
				return null;
		}

		for (Type superType: getExactDirectSuperTypes(type)) {
			Type result = getExactSuperType(superType, searchClass);
			if (result != null)
				return result;
		}

		return null;
	}

	/**
	 * Gets the type parameter for a given type that is the value for a given type variable.
	 * For example, with <tt>class StringList implements List<String></tt>,
	 * <tt>getTypeParameter(StringList.class, Collection.class.getTypeParameters()[0])</tt>
	 * returns <tt>String</tt>.
	 *
	 * @param type The type to inspect.
	 * @param variable The type variable to find the value for.
	 * @return The type parameter for the given variable. Or null if type is not a subtype of the
	 * 	type that declares the variable, or if the variable isn't known (because of raw types).
	 */
	public static Type getTypeParameter(Type type, TypeVariable<? extends Class<?>> variable) {
		Class<?> clazz = variable.getGenericDeclaration();
		Type superType = getExactSuperType(type, clazz);
		if (superType instanceof ParameterizedType) {
			int index = Arrays.asList(clazz.getTypeParameters()).indexOf(variable);
			return ((ParameterizedType)superType).getActualTypeArguments()[index];
		} else {
			return null;
		}
	}

	/**
	 * Checks if the capture of subType is a subtype of superType
	 */
	public static boolean isSuperType(Type superType, Type subType) {
		if (superType instanceof ParameterizedType || superType instanceof Class || superType instanceof GenericArrayType) {
			Class<?> superClass = erase(superType);
			Type mappedSubType = getExactSuperType(capture(subType), superClass);
			if (mappedSubType == null) {
				return false;
			} else if (superType instanceof Class<?>) {
				return true;
			} else if (mappedSubType instanceof Class<?>) {
				// TODO treat supertype by being raw type differently ("supertype, but with warnings")
				return true; // class has no parameters, or it's a raw type
			} else if (mappedSubType instanceof GenericArrayType) {
				Type superComponentType = getArrayComponentType(superType);
				assert superComponentType != null;
				Type mappedSubComponentType = getArrayComponentType(mappedSubType);
				assert mappedSubComponentType != null;
				return isSuperType(superComponentType, mappedSubComponentType);
			} else {
				assert mappedSubType instanceof ParameterizedType;
				ParameterizedType pMappedSubType = (ParameterizedType) mappedSubType;
				assert pMappedSubType.getRawType() == superClass;
				ParameterizedType pSuperType = (ParameterizedType)superType;

				Type[] superTypeArgs = pSuperType.getActualTypeArguments();
				Type[] subTypeArgs = pMappedSubType.getActualTypeArguments();
				assert superTypeArgs.length == subTypeArgs.length;
				for (int i = 0; i < superTypeArgs.length; i++) {
					if (! contains(superTypeArgs[i], subTypeArgs[i])) {
						return false;
					}
				}
				// params of the class itself match, so if the owner types are supertypes too, it's a supertype.
				return pSuperType.getOwnerType() == null || isSuperType(pSuperType.getOwnerType(), pMappedSubType.getOwnerType());
			}
		} else if (superType instanceof CaptureType) {
			if (superType.equals(subType))
				return true;
			for (Type lowerBound : ((CaptureType) superType).getLowerBounds()) {
				if (isSuperType(lowerBound, subType)) {
					return true;
				}
			}
			return false;
		} else if (superType instanceof GenericArrayType) {
			return isArraySupertype(superType, subType);
		} else {
			throw new RuntimeException("not implemented: " + superType.getClass());
		}
	}

	private static boolean isArraySupertype(Type arraySuperType, Type subType) {
		Type superTypeComponent = getArrayComponentType(arraySuperType);
		assert superTypeComponent != null;
		Type subTypeComponent = getArrayComponentType(subType);
		if (subTypeComponent == null) { // subType is not an array type
			return false;
		} else {
			return isSuperType(superTypeComponent, subTypeComponent);
		}
	}

	/**
	 * If type is an array type, returns the type of the component of the array.
	 * Otherwise, returns null.
	 */
	public static Type getArrayComponentType(Type type) {
		if (type instanceof Class) {
			Class<?> clazz = (Class<?>)type;
			return clazz.getComponentType();
		} else if (type instanceof GenericArrayType) {
			GenericArrayType aType = (GenericArrayType) type;
			return aType.getGenericComponentType();
		} else {
			return null;
		}
	}

	private static boolean contains(Type containingType, Type containedType) {
		if (containingType instanceof WildcardType) {
			WildcardType wContainingType = (WildcardType)containingType;
			for (Type upperBound : wContainingType.getUpperBounds()) {
				if (! isSuperType(upperBound, containedType)) {
					return false;
				}
			}
			for (Type lowerBound : wContainingType.getLowerBounds()) {
				if (! isSuperType(containedType, lowerBound)) {
					return false;
				}
			}
			return true;
		} else {
			return containingType.equals(containedType);
		}
	}

	/**
	 * Returns the direct supertypes of the given type. Resolves type parameters.
	 */
	private static Type[] getExactDirectSuperTypes(Type type) {
		if (type instanceof ParameterizedType || type instanceof Class) {
			Class<?> clazz;
			if (type instanceof ParameterizedType) {
				clazz = (Class<?>)((ParameterizedType)type).getRawType();
			} else {
				// TODO primitive types?
				clazz = (Class<?>)type;
				if (clazz.isArray())
					return getArrayExactDirectSuperTypes(clazz);
			}

			Type[] superInterfaces = clazz.getGenericInterfaces();
			Type superClass = clazz.getGenericSuperclass();

			// the only supertype of an interface without superinterfaces is Object
			if (superClass == null && superInterfaces.length == 0 && clazz.isInterface()) {
				return new Type[] {Object.class};
			}

			Type[] result;
			int resultIndex;
			if (superClass == null) {
				result = new Type[superInterfaces.length];
				resultIndex = 0;
			} else {
				result = new Type[superInterfaces.length + 1];
				resultIndex = 1;
				result[0] = mapTypeParameters(superClass, type);
			}
			for (Type superInterface : superInterfaces) {
				result[resultIndex++] = mapTypeParameters(superInterface, type);
			}

			return result;
		} else if (type instanceof TypeVariable) {
			TypeVariable<?> tv = (TypeVariable<?>) type;
			return tv.getBounds();
		} else if (type instanceof WildcardType) {
			// This should be a rare case: normally this wildcard is already captured.
			// But it does happen if the upper bound of a type variable contains a wildcard
			// TODO shouldn't upper bound of type variable have been captured too? (making this case impossible?)
			return ((WildcardType) type).getUpperBounds();
		} else if (type instanceof CaptureType) {
			return ((CaptureType)type).getUpperBounds();
		} else if (type instanceof GenericArrayType) {
			return getArrayExactDirectSuperTypes(type);
		} else if (type == null) {
			throw new NullPointerException();
		} else {
			throw new RuntimeException("not implemented type: " + type);
		}
	}

	private static Type[] getArrayExactDirectSuperTypes(Type arrayType) {
		// see http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.10.3
		Type typeComponent = getArrayComponentType(arrayType);

		Type[] result;
		int resultIndex;
		if (typeComponent instanceof Class && ((Class<?>)typeComponent).isPrimitive()) {
			resultIndex = 0;
			result = new Type[3];
		} else {
			Type[] componentSupertypes = getExactDirectSuperTypes(typeComponent);
			result = new Type[componentSupertypes.length + 3];
			for (resultIndex = 0; resultIndex < componentSupertypes.length; resultIndex++) {
				result[resultIndex] = GenericArrayTypeImpl.createArrayType(componentSupertypes[resultIndex]);
			}
		}
		result[resultIndex++] = Object.class;
		result[resultIndex++] = Cloneable.class;
		result[resultIndex++] = Serializable.class;
		return result;
	}

	/**
	 * Returns the exact return type of the given method in the given type.
	 * This may be different from <tt>m.getGenericReturnType()</tt> when the method was declared in a superclass,
	 * or <tt>type</tt> has a type parameter that is used in the return type, or <tt>type</tt> is a raw type.
	 */
	public static Type getExactReturnType(Method m, Type type) {
		Type returnType = m.getGenericReturnType();
		Type exactDeclaringType = getExactSuperType(capture(type), m.getDeclaringClass());
		if (exactDeclaringType == null) { // capture(type) is not a subtype of m.getDeclaringClass()
			throw new IllegalArgumentException("The method " + m + " is not a member of type " + type);
		}
		return mapTypeParameters(returnType, exactDeclaringType);
	}

	/**
	 * Returns the exact type of the given field in the given type.
	 * This may be different from <tt>f.getGenericType()</tt> when the field was declared in a superclass,
	 * or <tt>type</tt> has a type parameter that is used in the type of the field, or <tt>type</tt> is a raw type.
	 */
	public static Type getExactFieldType(Field f, Type type) {
		Type returnType = f.getGenericType();
		Type exactDeclaringType = getExactSuperType(capture(type), f.getDeclaringClass());
		if (exactDeclaringType == null) { // capture(type) is not a subtype of f.getDeclaringClass()
			throw new IllegalArgumentException("The field " + f + " is not a member of type " + type);
		}
		return mapTypeParameters(returnType, exactDeclaringType);
	}

	/**
	 * Returns the exact parameter types of the given method in the given type.
	 * This may be different from <tt>m.getGenericParameterTypes()</tt> when the method was declared in a superclass,
	 * or <tt>type</tt> has a type parameter that is used in one of the parameters, or <tt>type</tt> is a raw type.
	 */
	public static Type[] getExactParameterTypes(Method m, Type type) {
		Type[] parameterTypes = m.getGenericParameterTypes();
		Type exactDeclaringType = getExactSuperType(capture(type), m.getDeclaringClass());
		if (exactDeclaringType == null) { // capture(type) is not a subtype of m.getDeclaringClass()
			throw new IllegalArgumentException("The method " + m + " is not a member of type " + type);
		}

		Type[] result = new Type[parameterTypes.length];
		for (int i = 0; i < parameterTypes.length; i++) {
			result[i] = mapTypeParameters(parameterTypes[i], exactDeclaringType);
		}
		return result;
	}

	/**
	 * Applies capture conversion to the given type.
	 */
	public static Type capture(Type type) {
		if (type instanceof ParameterizedType) {
			return capture((ParameterizedType)type);
		} else {
			return type;
		}
	}

	/**
	 * Applies capture conversion to the given type.
	 * @see #capture(Type)
	 */
	public static ParameterizedType capture(ParameterizedType type) {
		// the map from parameters to their captured equivalent

		VarMap varMap = new VarMap();
		// list of CaptureTypes we've created but aren't fully initialized yet
		// we can only initialize them *after* we've fully populated varMap
		List<CaptureTypeImpl> toInit = new ArrayList<>();

		Class<?> clazz = (Class<?>)type.getRawType();
		Type[] arguments = type.getActualTypeArguments();
		TypeVariable<?>[] vars = clazz.getTypeParameters();
		Type[] capturedArguments = new Type[arguments.length];

		assert arguments.length == vars.length; // NICE throw an explaining exception

		for (int i = 0; i < arguments.length; i++) {
			Type argument = arguments[i];
			if (argument instanceof WildcardType) {
				CaptureTypeImpl captured = new CaptureTypeImpl((WildcardType)argument, vars[i]);
				argument = captured;
				toInit.add(captured);
			}
			capturedArguments[i] = argument;
			varMap.add(vars[i], argument);
		}
		for (CaptureTypeImpl captured : toInit) {
			captured.init(varMap);
		}
		Type ownerType = (type.getOwnerType() == null) ? null : capture(type.getOwnerType());
		return new ParameterizedTypeImpl(clazz, capturedArguments, ownerType);
	}

	/**
	 * Returns the display name of a Type.
	 */
	public static String getTypeName(Type type) {
		if(type instanceof Class) {
			Class<?> clazz = (Class<?>) type;
			return clazz.isArray() ? (getTypeName(clazz.getComponentType()) + "[]") : clazz.getName();
		} else {
			return type.toString();
		}
	}

	/**
	 * Returns list of classes and interfaces that are supertypes of the given type.
	 * For example given this class:
	 * <tt>class Foo<A extends Number & Iterable<A>, B extends A></tt>

	 * calling this method on type parameters <tt>B</tt> (<tt>Foo.class.getTypeParameters()[1]</tt>)
	 * returns a list containing <tt>Number</tt> and <tt>Iterable</tt>.
	 *

	 * This is mostly useful if you get a type from one of the other methods in <tt>GenericTypeReflector</tt>,
	 * but you don't want to deal with all the different sorts of types,
	 * and you are only really interested in concrete classes and interfaces.
	 *

	 *
	 * @return A List of classes, each of them a supertype of the given type.
	 * 	If the given type is a class or interface itself, returns a List with just the given type.
	 *  The list contains no duplicates, and is ordered in the order the upper bounds are defined on the type.
	 */
	public static List<Class<?>> getUpperBoundClassAndInterfaces(Type type) {
		LinkedHashSet<Class<?>> result = new LinkedHashSet<>();
		buildUpperBoundClassAndInterfaces(type, result);
		return new ArrayList<>(result);
	}

	/**
	 * Helper method for getUpperBoundClassAndInterfaces, adding the result to the given set.
	 */
	private static void buildUpperBoundClassAndInterfaces(Type type, Set<Class<?>> result) {
		if (type instanceof ParameterizedType || type instanceof Class<?>) {
			result.add(erase(type));
			return;
		}

		for (Type superType: getExactDirectSuperTypes(type)) {
			buildUpperBoundClassAndInterfaces(superType, result);
		}
	}
}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.FullEntity;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.impl.Path;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
 * The context while creating translator factories. Tracks important state as we navigate the class graph.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@RequiredArgsConstructor
public class CreateContext
{
	/** */
	@Getter
	private final ObjectifyFactory factory;

	/**
	 * Get the relevant translator, creating it if necessary.
	 */
	public <P, D> Translator<P, D> getTranslator(final TypeKey<P> tk, final CreateContext ctx, final Path path) {
		return factory.getTranslators().get(tk, ctx, path);
	}

	/**
	 * Get the populator for the specified class. This requires looking up the
	 * translator for that class and then getting the populator from it.
	 *
	 * @param clazz is the class we want a populator for.
	 */
	@SuppressWarnings("unchecked")
	public <P> Populator<P> getPopulator(final Class<P> clazz, final Path path) {
		if (clazz == null || clazz.equals(Object.class)) {
			return (Populator<P>)NullPopulator.INSTANCE;
		} else {
			final ClassTranslator<P> classTranslator = (ClassTranslator<P>)this.<P, FullEntity<?>>getTranslator(new TypeKey<>(clazz), this, path);
			return classTranslator.getPopulator();
		}
	}
}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.FullEntity;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.annotation.AlsoLoad;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.OnSave;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.annotation.Unindex;
import com.googlecode.objectify.impl.FieldProperty;
import com.googlecode.objectify.impl.KeyMetadata;
import com.googlecode.objectify.impl.KeyPopulator;
import com.googlecode.objectify.impl.MethodProperty;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.impl.Property;
import com.googlecode.objectify.impl.PropertyPopulator;
import com.googlecode.objectify.impl.TypeUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 *
Used by translators to populate properties between POJO and PropertiesContainer. Unlike
 * translators, this does not create the POJO or container, it just copies translated properties
 * between them.

 *
 *
Always excludes the key fields, @Id and @Parent.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class ClassPopulator
 implements Populator

{
	/** We do not persist fields with any of these modifiers */
	private static final int NOT_SAVEABLE_MODIFIERS = Modifier.FINAL | Modifier.STATIC;

	/** We don't want to include the key fields in population */
	private static final Predicate<Property> INCLUDED_FIELDS = prop -> prop.getAnnotation(Id.class) == null && prop.getAnnotation(Parent.class) == null;

	/** */
	private final Class
 clazz;

	/** Populator for the superclass */
	private final Populator<? super P> superPopulator;

	/** Only includes fields declared on this class */
	private final List<Populator<Object>> props = new ArrayList<>();

	/** Three-state index instruction for the whole class. Null means "leave it as-is". */
	private final Boolean indexInstruction;

	/** */
	private final List<LifecycleMethod> onSaveMethods = new ArrayList<>();
	private final List<LifecycleMethod> onLoadMethods = new ArrayList<>();

	/**
	 */
	public ClassPopulator(final Class
 clazz, final CreateContext ctx, final Path path) {
		this.clazz = clazz;

		// Recursively climb the superclass chain
		this.superPopulator = ctx.getPopulator(clazz.getSuperclass(), path);

		log.trace("Creating class translator for {} at path '{}'", clazz.getName(), path);

		if (clazz.isAnnotationPresent(Entity.class)) {
			@SuppressWarnings("unchecked")
			final Populator<Object> keyPopulator = (Populator<Object>)new KeyPopulator<>(clazz, ctx, path);
			props.add(keyPopulator);
		}

		indexInstruction = getIndexInstruction(clazz);

		// Find all the basic properties
		for (final Property prop: getDeclaredProperties(ctx.getFactory(), clazz)) {
			if (INCLUDED_FIELDS.apply(prop)) {
				final Path propPath = path.extend(prop.getName());
				try {
					final Translator<Object, Object> translator = ctx.getTranslator(new TypeKey<>(prop), ctx, propPath);
					final PropertyPopulator<Object, Object> tprop = new PropertyPopulator<>(prop, translator);
					props.add(tprop);
				} catch (Exception ex) {
					// Catch any errors during this process and wrap them in an exception that exposes more useful information.
					propPath.throwIllegalState("Error registering " + clazz.getName(), ex);
				}
			}
		}

		// Find the @OnSave methods
		for (final Method method: clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(OnSave.class))
				onSaveMethods.add(new LifecycleMethod(method));

			if (method.isAnnotationPresent(OnLoad.class))
				onLoadMethods.add(new LifecycleMethod(method));
		}
	}

	/* */
	@Override
	public void load(FullEntity<?> node, LoadContext ctx, Path path, final P into) {
		superPopulator.load(node, ctx, path, into);

		ctx.enterContainerContext(into);
		try {
			for (final Populator<Object> prop: props) {
				prop.load(node, ctx, path, into);
			}
		} finally {
			ctx.exitContainerContext(into);
		}

		// If there are any @OnLoad methods, call them after everything else
		if (!onLoadMethods.isEmpty()) {
			ctx.defer(new Runnable() {
				@Override
				public void run() {
					for (LifecycleMethod method: onLoadMethods)
						method.execute(into);
				}

				@Override
				public String toString() {
					return "(deferred invoke " + clazz + " @OnLoad callbacks on " + into + ")";
				}
			});
		}
	}

	/* */
	@Override
	public void save(P pojo, boolean index, SaveContext ctx, Path path, FullEntity.Builder<?> into) {

		superPopulator.save(pojo, index, ctx, path, into);

		// Must do @OnSave methods after superclass but before actual population
		if (!ctx.skipLifecycle() && !onSaveMethods.isEmpty())
			for (LifecycleMethod method: onSaveMethods)
				method.execute(pojo);

		if (indexInstruction != null)
			index = indexInstruction;

		for (final Populator<Object> prop: props) {
			prop.save(pojo, index, ctx, path, into);
		}
	}

	/**
	 * Figure out if there is an index instruction for the whole class.
	 * @return true, false, or null (which means no info)
	 */
	private Boolean getIndexInstruction(Class
 clazz) {
		Index ind = clazz.getAnnotation(Index.class);
		Unindex unind = clazz.getAnnotation(Unindex.class);

		if (ind != null && unind != null)
			throw new IllegalStateException("You cannot have @Index and @Unindex on the same class: " + clazz);

		if (ind != null)
			return true;
		else if (unind != null)
			return false;
		else
			return null;
	}

	/**
	 * Determine if we should create a Property for the field.  Things we ignore:  static, final, @Ignore, synthetic
	 */
	private boolean isOfInterest(Field field) {
		return !field.isAnnotationPresent(Ignore.class)
				&& ((field.getModifiers() & NOT_SAVEABLE_MODIFIERS) == 0)
				&& !field.isSynthetic()
				&& !field.getName().startsWith("bitmap$init");	// Scala adds a field bitmap$init$0 and bitmap$init$1 etc
	}

	/**
	 * Determine if we should create a Property for the method (ie, @AlsoLoad)
	 */
	private  boolean isOfInterest(Method method) {
		for (Annotation[] annos: method.getParameterAnnotations())
			if (TypeUtils.getAnnotation(annos, AlsoLoad.class) != null)
				return true;

		return false;
	}

	/**
	 * Get all the persistable fields and methods declared on a class. Ignores superclasses.
	 *
	 * @return the fields we load and save, including @Id and @Parent fields. All fields will be set accessable
	 *  and returned in order of declaration.
	 */
	private List<Property> getDeclaredProperties(ObjectifyFactory fact, Class<?> clazz) {
		List<Property> good = new ArrayList<>();

		for (Field field: clazz.getDeclaredFields())
			if (isOfInterest(field))
				good.add(new FieldProperty(fact, clazz, field));

		for (Method method: clazz.getDeclaredMethods())
			if (isOfInterest(method))
				good.add(new MethodProperty(method));

		return good;
	}

	/**
	 * Gets the key metadata but only if this was an @Entity annotated class. Should not be called if not.
	 */
	public KeyMetadata
 getKeyMetadata() {
		final Populator<Object> populator = props.get(0);
		Preconditions.checkState(populator instanceof KeyPopulator, "Cannot get KeyMetadata for non-@Entity class " + this.clazz);
		return ((KeyPopulator
)populator).getKeyMetadata();
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Entity;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.impl.translate.SaveContext;
import com.googlecode.objectify.util.ResultCache;
import com.googlecode.objectify.util.ResultNow;
import lombok.extern.slf4j.Slf4j;

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

/**
 * Each round in the series of fetches required to complete a batch.  A round executes when
 * the value is obtained (via now()) for a Result that was created as part of this round.
 * When a round executes, a new round is created.
 */
@Slf4j
class Round {
	/** */
	private final LoadEngine loadEngine;

	/** The depth of the rounds of execution, for debugging. 0 is first round, 1 is second round, etc */
	private final int depth;

	/** The keys we will need to fetch; might not be any if everything came from the session */
	private final Set<com.google.cloud.datastore.Key> pending = new HashSet<>();

	/** Sometimes we get a bunch of Entity data from queries that eliminates our need to go to the backing datastore */
	private final Map<com.google.cloud.datastore.Key, Entity> stuffed = new HashMap<>();

	/** Entities that have been fetched and translated this round. There will be an entry for each pending. */
	private Result<Map<Key<?>, Object>> translated;

	/**
	 */
	Round(LoadEngine loadEngine, int depth) {
		this.loadEngine = loadEngine;
		this.depth = depth;
	}

	/**
	 * Gets a result, using the session cache if possible.
	 */
	public <T> Result<T> get(final Key<T> key) {
		assert !isExecuted();

		SessionValue<T> sv = getSession().get(key);
		if (sv == null) {
			log.trace("Adding to round (session miss): {}", key);

			this.pending.add(key.getRaw());

			Result<T> result = new ResultCache<T>() {
				@Override
				@SuppressWarnings("unchecked")
				public T nowUncached() {
					// Because clients could conceivably get() in the middle of our operations (see LoadCollectionRefsTest.specialListWorks()),
					// we need to check for early execution. This will perform poorly, but at least it will work.
					//assert Round.this.isExecuted();
					loadEngine.execute();
					return (T)translated.now().get(key);
				}

				@Override
				public String toString() {
					return "(Fetch result for " + key + ")";
				}
			};

			sv = new SessionValue<>(result, getLoadArrangement());
			getSession().add(key, sv);

		} else {
			log.trace("Adding to round (session hit): {}", key);

			if (sv.loadWith(getLoadArrangement())) {
				log.trace("New load group arrangement, checking for upgrades: {}", getLoadArrangement());

				// We are looking at a brand-new arrangement for something that already existed in the session.
				// We need to go through any Ref<?>s that might be in need of loading. We find those refs by
				// actually saving the entity into a custom SaveContext.
				T thing = sv.getResult().now();
				if (thing != null) {
					SaveContext saveCtx = new SaveContext() {
						@Override
						public boolean skipLifecycle() {
							return true;
						}

						@Override
						public com.google.cloud.datastore.Key saveRef(Ref<?> value, LoadConditions loadConditions) {
							com.google.cloud.datastore.Key key = super.saveRef(value, loadConditions);

							if (loadEngine.shouldLoad(loadConditions)) {
								log.trace("Upgrading key {}", key);
								loadEngine.load(value.key());
							}

							return key;
						}
					};

					// We throw away the saved entity and we are done
					loadEngine.ofy.factory().getMetadataForEntity(thing).save(thing, saveCtx);
				}
			}
		}

		return sv.getResult();
	}

	/** @return true if this round needs execution */
	public boolean needsExecution() {
		return translated == null && !pending.isEmpty();
	}

	/** Turn this into a result set */
	public void execute() {
		if (needsExecution()) {
			log.trace("Executing round: {}", pending);

			Result<Map<com.google.cloud.datastore.Key, Entity>> fetched = fetchPending();
			translated = loadEngine.translate(fetched);

			// If we're in a transaction (and beyond the first round), force all subsequent rounds to complete.
			// This effectively means that only the first round can be asynchronous; all other rounds are
			// materialized immediately. The reason for this is that there are some nasty edge cases with @Load
			// annotations in transactions getting called after the transaction closes. This is possibly not the
			// best solution to the problem, but it solves the problem now.
			if (loadEngine.ofy.getTransaction() != null && depth > 0)
				translated.now();
		}
	}

	/** Possibly pulls some values from the stuffed collection */
	private Result<Map<com.google.cloud.datastore.Key, Entity>> fetchPending() {
		// We don't need to fetch anything that has been stuffed

		final Map<com.google.cloud.datastore.Key, Entity> combined = new HashMap<>();
		Set<com.google.cloud.datastore.Key> fetch = new HashSet<>();

		for (com.google.cloud.datastore.Key key: pending) {
			Entity ent = stuffed.get(key);
			if (ent == null)
				fetch.add(key);
			else
				combined.put(key, ent);
		}

		if (fetch.isEmpty()) {
			return new ResultNow<>(combined);
		} else {
			final Result<Map<com.google.cloud.datastore.Key, Entity>> fetched = loadEngine.fetch(fetch);

			return () -> {
				combined.putAll(fetched.now());
				return combined;
			};
		}
	}

	/** */
	@Override
	public String toString() {
		return (isExecuted() ? "pending" : "executed") + ", depth=" + depth + ", pending="+ pending.toString();
	}

	/**
	 * @return true if the round has been executed already
	 */
	public boolean isExecuted() {
		return translated != null;
	}

	/** Create the next round */
	public Round next() {
		log.trace("Creating new round, going from depth {} to {}", depth, (depth+1));
		return new Round(loadEngine, depth+1);
	}

	/**
	 * 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(Entity ent) {
		stuffed.put(ent.getKey(), ent);
	}

	/** */
	private Session getSession() {
		return loadEngine.getSession();
	}

	/** */
	private LoadArrangement getLoadArrangement() {
		return loadEngine.getLoadArrangement();
	}
}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.EntityValue;
import com.google.cloud.datastore.FullEntity;
import com.google.cloud.datastore.IncompleteKey;
import com.google.cloud.datastore.ListValue;
import com.google.cloud.datastore.StringValue;
import com.google.cloud.datastore.Value;
import com.googlecode.objectify.annotation.Subclass;
import com.googlecode.objectify.impl.Forge;
import com.googlecode.objectify.impl.KeyMetadata;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.util.LogUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

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

/**
 *
Some common code for Translators which know how to convert a POJO type into a PropertiesContainer.
 * This might be polymorphic; we get polymorphism when @Subclasses are registered on this translator.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class ClassTranslator
 extends NullSafeTranslator<P, FullEntity<?>>
{
	/** Name of the out-of-band discriminator property in a PropertyContainer */
	public static final String DISCRIMINATOR_PROPERTY = "^d";

	/** Name of the list property which will hold all indexed discriminator values */
	public static final String DISCRIMINATOR_INDEX_PROPERTY = "^i";

	/** The declared class we are responsible for. */
	@Getter
	private final Class
 declaredClass;

	/** Lets us construct the initial objects */
	@Getter
	private final Forge forge;

	/** Does the heavy lifting of copying properties */
	@Getter
	private final ClassPopulator
 populator;

	/**
	 * The discriminator for this subclass, or null if this is not a @Subclass
	 */
	@Getter
	private final String discriminator;

	/**
	 * The discriminators that will be indexed for this subclass.  Empty for the base class or any
	 * subclasses for which all discriminators are unindexed.
	 */
	private final List<StringValue> indexedDiscriminators = new ArrayList<>();

	/** Keyed by discriminator value, including alsoload discriminators */
	private Map<String, ClassTranslator<? extends P>> byDiscriminator = new HashMap<>();

	/** Keyed by Class, includes the base class */
	private Map<Class<? extends P>, ClassTranslator<? extends P>> byClass = new HashMap<>();

	/** */
	public ClassTranslator(final Class
 declaredClass, final CreateContext ctx, final Path path) {
		log.trace("Creating class translator for {} at path '{}'", declaredClass.getName(), path);

		this.declaredClass = declaredClass;
		this.forge = ctx.getFactory();
		this.populator = new ClassPopulator<>(declaredClass, ctx, path);

		final Subclass sub = declaredClass.getAnnotation(Subclass.class);
		if (sub != null) {
			discriminator = (sub.name().length() > 0) ? sub.name() : declaredClass.getSimpleName();
			addIndexedDiscriminators(declaredClass);
		} else {
			discriminator = null;
		}
	}

	/* */
	@Override
	public P loadSafe(final Value<FullEntity<?>> container, final LoadContext ctx, final Path path) throws SkipException {
		// check if we need to redirect to a different translator
		final String containerDiscriminator = container.get().contains(DISCRIMINATOR_PROPERTY) ? container.get().getString(DISCRIMINATOR_PROPERTY) : null;	// wow no Optional or nullable get
		if (!Objects.equals(discriminator, containerDiscriminator)) {
			final ClassTranslator<? extends P> translator = byDiscriminator.get(containerDiscriminator);
			if (translator == null) {
				throw new IllegalStateException("Datastore object has discriminator value '" + containerDiscriminator + "' but no relevant @Subclass is registered");
			} else {
				// This fixes alsoLoad names in discriminators by changing the discriminator to what the
				// translator expects for loading that subclass. Otherwise we'll get the error above since the
				// translator discriminator and the container discriminator won't match.
				final StringValue discriminatorValue = StringValue.newBuilder(translator.getDiscriminator()).setExcludeFromIndexes(true).build();
				final FullEntity<?> updatedEntity = FullEntity.newBuilder(container.get()).set(DISCRIMINATOR_PROPERTY, discriminatorValue).build();
				return translator.load(EntityValue.of(updatedEntity), ctx, path);
			}
		} else {
			// This is a normal load
			if (log.isTraceEnabled())
				log.trace(LogUtils.msg(path, "Instantiating a " + declaredClass.getName()));

			final P into = forge.construct(declaredClass);

			populator.load(container.get(), ctx, path, into);

			return into;
		}
	}

	/* */
	@Override
	public Value<FullEntity<?>> saveSafe(final P pojo, final boolean index, final SaveContext ctx, final Path path) throws SkipException {
		// check if we need to redirect to a different translator
		if (pojo.getClass() != declaredClass) {
			// Sometimes generics are more of a hindrance than a help
			@SuppressWarnings("unchecked")
			final ClassTranslator
 translator = (ClassTranslator
)byClass.get(pojo.getClass());
			if (translator == null)
				throw new IllegalStateException("Class '" + pojo.getClass() + "' is not a registered @Subclass");
			else
				return translator.save(pojo, index, ctx, path);
		} else {
			// This is a normal save
			final FullEntity.Builder<IncompleteKey> into = FullEntity.newBuilder();

			populator.save(pojo, index, ctx, path, into);

			if (discriminator != null) {
				into.set(DISCRIMINATOR_PROPERTY, StringValue.newBuilder(discriminator).setExcludeFromIndexes(true).build());

				if (!indexedDiscriminators.isEmpty())
					into.set(DISCRIMINATOR_INDEX_PROPERTY, ListValue.of(indexedDiscriminators));
			}

			// The question of whether to index this is weird. In order for subthings to be indexed, the entity needs
			// to be indexed. But then lists with index-heterogeous values (say, nulls) get reordered (!)
			// by the datastore. So we always index EntityValues and force all the list translators homogenize their lists.
			// Gross but seems to be the only way.
			return EntityValue.of(into.build());
		}
	}

	/**
	 * Recursively go through the class hierarchy adding any discriminators that are indexed
	 */
	private void addIndexedDiscriminators(final Class<?> clazz) {
		if (clazz == Object.class)
			return;

		this.addIndexedDiscriminators(clazz.getSuperclass());

		final Subclass sub = clazz.getAnnotation(Subclass.class);
		if (sub != null && sub.index()) {
			final String disc = (sub.name().length() > 0) ? sub.name() : clazz.getSimpleName();
			this.indexedDiscriminators.add(StringValue.of(disc));
		}
	}

	/**
	 * Register a subclass translator with this class translator. That way if we get called upon
	 * to translate an instance of the subclass, we will forward to the correct translator.
	 */
	public void registerSubclass(ClassTranslator<? extends P> translator) {
		byDiscriminator.put(translator.getDiscriminator(), translator);

		Subclass sub = translator.getDeclaredClass().getAnnotation(Subclass.class);
		for (String alsoLoad: sub.alsoLoad())
			byDiscriminator.put(alsoLoad, translator);

		byClass.put(translator.getDeclaredClass(), translator);
	}

	/** Only works on @Entity classes */
	public KeyMetadata
 getKeyMetadata() {
		return populator.getKeyMetadata();
	}
}


package com.googlecode.objectify.util;

import com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

/**
 */
public class GenericUtils
{
	private GenericUtils() {
	}

	/**
	 * Get the component type of a Collection.
	 */
	public static Type getCollectionComponentType(Type collectionType) {
		Type componentType = GenericTypeReflector.getTypeParameter(collectionType, Collection.class.getTypeParameters()[0]);
		if (componentType == null)	// if it was a raw type, just assume Object
			return Object.class;
		else
			return componentType;
	}

	/**
	 * Get the key type of a Map.
	 */
	public static Type getMapKeyType(Type mapType) {
		Type componentType = GenericTypeReflector.getTypeParameter(mapType, Map.class.getTypeParameters()[0]);
		if (componentType == null)	// if it was a raw type, just assume Object
			return Object.class;
		else
			return componentType;
	}

	/**
	 * Get the value type of a Map.
	 */
	public static Type getMapValueType(Type mapType) {
		Type componentType = GenericTypeReflector.getTypeParameter(mapType, Map.class.getTypeParameters()[1]);
		if (componentType == null)	// if it was a raw type, just assume Object
			return Object.class;
		else
			return componentType;
	}
}


package com.googlecode.objectify.impl;

/**
 * This is a wrapper for a Path that makes it backwards; instead of going from tail to head,
 * it has links from head to tail.  This is sometimes useful for forward traversal of a path.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class ForwardPath
{
	/** This path segment. */
	private final Path path;

	/** The next step in the path, null for the end. */
	private ForwardPath next;

	/** */
	public ForwardPath(Path path) {
		this.path = path;
	}

	/**
	 * Recursive method which reverses the path into a ForwardPath.
	 * @param path cannot be the root path.
	 */
	public static ForwardPath of(Path path) {
		ForwardPath next = new ForwardPath(path);
		if (path.getPrevious() == Path.root())
			return next;

		ForwardPath previous = of(path.getPrevious());
		previous.next = next;

		return previous;
	}

	/** @return the real full path to this place */
	public Path getPath() {
		return this.path;
	}

	/** @return the next path in the forward traversal */
	public ForwardPath getNext() {
		return this.next;
	}

	/** Get the complete path in this chain, typically for error messages or debugging */
	public Path getFinalPath() {
		ForwardPath here = this;
		while (here.next != null)
			here = here.next;

		return here.getPath();
	}

	@Override
	public String toString() {
		return path.toPathString();
	}
}


package com.googlecode.objectify.impl;

import java.util.Objects;

/**
 * Path represents the individual steps from the root object to the current property.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class Path
{
	/** */
	private static final Path ROOT = new Path("", null);
	public static Path root() {
		return ROOT;
	}

	/** This path segment. */
	private final String segment;

	/** The previous step in the path, null only for the special {@link #ROOT} element. */
	private final Path previous;

	/** */
	private Path(String name, Path path) {
		segment = name;
		previous = path;
	}

	/** Create the full x.y.z string */
	public String toPathString() {
		if (this == ROOT) {
			return "";
		} else {
			StringBuilder builder = new StringBuilder();
			toPathString(builder);
			return builder.toString();
		}
	}

	/** */
	private void toPathString(StringBuilder builder) {
		if (previous != ROOT) {
			previous.toPathString(builder);
			builder.append('.');
		}

		builder.append(segment);
	}

	/** */
	public Path extend(String name) {
		return new Path(name, this);
	}

	/** Get this segment of the path.  For root this will be null. */
	public String getSegment() {
		return segment;
	}

	/** Get the previous path; for root this will be null */
	public Path getPrevious() {
		return previous;
	}

	/** */
	public boolean isRoot() { return this == ROOT; }

	/** */
	@Override
	public String toString() {
		return toPathString();
	}

	/** Compares on complete path */
	@Override
	public boolean equals(Object obj) {
		if (obj == null || obj.getClass() != this.getClass())
			return false;

		Path other = (Path)obj;

		if (!this.segment.equals(other.segment))
			return false;
		else
			return Objects.equals(this.previous, other.previous);
	}

	/** Generates hash code for complete path */
	@Override
	public int hashCode() {
		int hash = segment.hashCode();

		if (previous == null)
			return hash;
		else
			return hash ^ previous.hashCode();
	}

	/** Convenient way to include path location in the exception message.  Never returns. */
	public Object throwIllegalState(String message) {
		throw new IllegalStateException("At path '" + this + "': " + message);
	}

	/** Convenient way to include path location in the exception message.  Never returns. */
	public Object throwIllegalState(String message, Throwable cause) {
		throw new IllegalStateException("At path '" + this + "': " + message, cause);
	}

	/** Convenient way to include path location in the exception message.  Never returns. */
	public Object throwNullPointer(String message) {
		throw new NullPointerException("At path '" + this + "': " + message);
	}

	/**
	 * ROOT is 0, top level Entity properties are 1, embedded things are higher.
	 */
	public int depth() {
		int depth = 0;

		Path here = this;
		while (here != ROOT) {
			depth++;
			here = here.previous;
		}

		return depth;
	}

	/** Anything with a depth greater than 1 is embedded */
	boolean isEmbedded() {
		return depth() > 1;
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.FullEntity;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.cmd.Saver;
import com.googlecode.objectify.impl.translate.SaveContext;
import com.googlecode.objectify.util.ResultWrapper;

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

/**
 * Implementation of the Put interface.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class SaverImpl implements Saver
{
	/** */
	private final ObjectifyImpl ofy;

	/** */
	public SaverImpl(final ObjectifyImpl ofy) {
		this.ofy = ofy;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Saver#entity(java.lang.Object)
	 */
	@Override
	public <E> Result<Key<E>> entity(final E entity) {
		final Result<Map<Key<E>, E>> base = this.<E>entities(Collections.singleton(entity));

		return new ResultWrapper<Map<Key<E>, E>, Key<E>>(base) {
			private static final long serialVersionUID = 1L;

			@Override
			protected Key<E> wrap(final Map<Key<E>, E> base) {
				return base.keySet().iterator().next();
			}
		};
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Saver#entities(E[])
	 */
	@Override
	public <E> Result<Map<Key<E>, E>> entities(final E... entities) {
		return this.entities(Arrays.asList(entities));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Saver#entities(java.lang.Iterable)
	 */
	@Override
	public <E> Result<Map<Key<E>, E>> entities(final Iterable<E> entities) {
		return ofy.createWriteEngine().save(entities);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Saver#toEntity(java.lang.Object)
	 */
	@Override
	public FullEntity<?> toEntity(final Object pojo) {
		if (pojo instanceof FullEntity<?>) {
			return (FullEntity<?>)pojo;
		} else {
			@SuppressWarnings("unchecked")
			final EntityMetadata<Object> meta = (EntityMetadata<Object>)ofy.factory().getMetadata(pojo.getClass());
			return meta.save(pojo, new SaveContext());
		}
	}

}


package com.googlecode.objectify.cache;

import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.Key;
import com.googlecode.objectify.cache.MemcacheService.CasPut;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 *
This is the facade used by Objectify to cache entities in the MemcacheService.

 *
 *
Entity cacheability and expiration are determined by a {@code CacheControl} object.
 * In addition, hit/miss statistics are tracked in a {@code MemcacheStats}.

 *
 *
In order to guarantee cache synchronization, getAll() *must* be able to return
 * an IdentifiableValue, even for entries not present in the cache.  Because empty cache
 * values cannot be made into IdentifiableValue, we immediately replace them with a
 * null value and refetch (null is a valid cache value).  If this refetch doesn't work,
 * we treat the key as uncacheable for the duration of the request.

 *
 *
The values put in memcache are Key -> Entity, except for negative cache entries,
 * which are Key -> String (the value NEGATIVE).

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class EntityMemcache
{
	/**
	 * A bucket represents memcache information for a particular Key.  It might have an entity,
	 * it might be a negative cache result, it might be empty.
	 *
	 * Buckets can be hash keys; they hash to their Key value.
	 */
	@EqualsAndHashCode(of="key")
	public class Bucket {
		/** Identifies the bucket */
		private final Key key;

		/**
		 * If null, this means the key is uncacheable (possibly because the cache is down).
		 * If not null, the IV holds the Entity or NEGATIVE.
		 */
		private final IdentifiableValue identifiableValue;

		/**
		 * The Entity to store in this bucket in a put().  Can be null to indicate a negative cache
		 * result.  The Entity key *must* match the bucket key.
		 */
		private Entity next;

		/**
		 * Crate a bucket with an uncacheable key.  Same as this(key, null).
		 */
		public Bucket(final Key key)
		{
			this(key, null);
		}

		/**
		 * @param identifiableValue can be null to indicate an uncacheable key
		 */
		public Bucket(final Key key, final IdentifiableValue identifiableValue) {
			this.key = key;
			this.identifiableValue = identifiableValue;
		}

		/** */
		public Key getKey() { return this.key; }

		/** @return true if we can cache this bucket; false if the key isn't cacheable or the memcache was down when we created the bucket */
		public boolean isCacheable() { return this.identifiableValue != null; }

		/** @return true if this is a negative cache result */
		public boolean isNegative() { return this.isCacheable() && NEGATIVE.equals(identifiableValue.getValue()); }

		/**
		 * "Empty" means we don't know the value - it could be null, it could be uncacheable, or we could have some
		 * really weird unknown data in the cache.  Basically, anything other than "yes we have an entity/negative"
		 * is considered empty.
		 *
		 * @return true if this is empty or uncacheable or something other than a nice entity or negative result.
		 */
		public boolean isEmpty() {
			return !this.isCacheable() || (!this.isNegative() && !(identifiableValue.getValue() instanceof Entity));
		}

		/** Get the entity stored at this bucket, possibly the one that was set */
		public Entity getEntity() {
			if (identifiableValue != null && identifiableValue.getValue() instanceof Entity)
				return (Entity)identifiableValue.getValue();
			else
				return null;
		}

		/**
		 * Prepare the value that will be set in memcache in the next putAll().
		 * Null (or not calling this method) will put a negative result in the cache.
		 */
		public void setNext(final Entity value)
		{
			this.next = value;
		}

		/**
		 * @return the actual value we should store in memcache based on the next value, ie possibly NEGATIVE
		 */
		private Object getNextToStore() {
			return (this.next == null) ? NEGATIVE : this.next;
		}
	}

	/**
	 * The value stored in the memcache for a negative cache result.
	 */
	public static final String NEGATIVE = "NEGATIVE";

	/** */
	private final String namespace;

	/** */
	private final KeyMemcacheService memcache;
	private final KeyMemcacheService memcacheWithRetry;

	@Getter
	private final MemcacheStats stats;

	private final CacheControl cacheControl;

	/**
	 * Creates a memcache which caches everything without expiry and doesn't record statistics.
	 */
	public EntityMemcache(final MemcacheService memcache, final String namespace) {
		this(memcache, namespace, key -> 0);
	}

	/**
	 * Creates a memcache which doesn't record stats
	 */
	public EntityMemcache(final MemcacheService memcache, final String namespace, final CacheControl cacheControl) {
		this(memcache, namespace, cacheControl, new MemcacheStats() {
			@Override public void recordHit(Key key) { }
			@Override public void recordMiss(Key key) { }
		});
	}

	public EntityMemcache(
			final MemcacheService memcacheService,
			final String namespace,
			final CacheControl cacheControl,
			final MemcacheStats stats) {

		this.namespace = namespace;
		this.memcache = new KeyMemcacheService(memcacheService);
		this.memcacheWithRetry = new KeyMemcacheService(MemcacheServiceRetryProxy.createProxy(memcacheService));
		this.stats = stats;
		this.cacheControl = cacheControl;
	}

	/**
	 *
Gets the Buckets for the specified keys.  A bucket is built around an IdentifiableValue so you can
	 * putAll() them without the risk of overwriting other threads' changes.  Buckets also hide the
	 * underlying details of storage for negative, empty, and uncacheable results.

	 *
	 *
Note that worst case (a cold cache), obtaining each bucket might require three memcache requests:
	 * a getIdentifiable() which returns null, a put(null), and another getIdentifiable().  Since
	 * there is no batch getIdentifiable(), this is *per key*.

	 *
	 *
When keys are uncacheable (per CacheControl) or the memcache is down, you will still get an empty
	 * bucket back.  The bucket will have null IdentifiableValue so we can identify it as uncacheable.

	 *
	 * @return the buckets requested.  Buckets will never be null.  You will always get a bucket for every key.
	 */
	public Map<Key, Bucket> getAll(final Iterable<Key> keys) {
		final Map<Key, Bucket> result = new HashMap<>();

		// Sort out the ones that are uncacheable
		final Set<Key> potentials = new HashSet<>();

		for (final Key key: keys) {
			if (!cacheControl.isCacheable(key))
				result.put(key, new Bucket(key));
			else
				potentials.add(key);
		}

		Map<Key, IdentifiableValue> casValues;
		try {
			casValues = this.memcache.getIdentifiables(potentials);
		} catch (Exception ex) {
			// This should really only be a problem if the serialization format for an Entity changes,
			// or someone put a badly-serializing object in the cache underneath us.
			log.warn("Error obtaining cache for " + potentials, ex);
			casValues = new HashMap<>();
		}

		// Now create the remaining buckets
		for (final Key key: keys) {
			final IdentifiableValue casValue = casValues.get(key);	// Might be null, which means uncacheable
			final Bucket buck = new Bucket(key, casValue);
			result.put(key, buck);

			if (buck.isEmpty())
				this.stats.recordMiss(buck.getKey());
			else
				this.stats.recordHit(buck.getKey());
		}

		return result;
	}

	/**
	 * Update a set of buckets with new values.  If collisions occur, resets the memcache value to null.
	 *
	 * @param updates can have null Entity values, which will record a negative cache result.  Buckets must have
	 *  been obtained from getAll().
	 */
	public void putAll(final Collection<Bucket> updates) {
		final Set<Key> good = this.cachePutIfUntouched(updates);

		if (good.size() == updates.size())
			return;

		// Figure out which ones were bad
		final List<Key> bad = updates.stream()
				.map(Bucket::getKey)
				.filter(key -> !good.contains(key))
				.collect(Collectors.toList());

		if (!bad.isEmpty()) {
			// So we had some collisions.  We need to reset these back to null, but do it in a safe way - if we
			// blindly set null something already null, it will break any putIfUntouched() which saw the first null.
			// This could result in write contention starving out a real write.  The solution is to only reset things
			// that are not already null.

			final Map<Key, Object> cached = this.cacheGetAll(bad);

			// Remove the stuff we don't care about
			cached.values().removeIf(Objects::isNull);

			this.empty(cached.keySet());
		}
	}

	/**
	 * Revert a set of keys to the empty state.  Will loop on this several times just in case
	 * the memcache write fails - we don't want to leave the cache in a nasty state.
	 */
	public void empty(final Iterable<Key> keys) {
		final Map<Key, Object> updates = new HashMap<>();

		for (final Key key: keys)
			if (cacheControl.isCacheable(key))
				updates.put(key, null);

		this.memcacheWithRetry.putAll(updates);
	}

	/**
	 * Put buckets in the cache, checking for cacheability and collisions.
	 * @return the set of keys that were *successfully* handled. That includes buckets that were put without collision
	 * and buckets that didn't need to be cached.
	 */
	private Set<Key> cachePutIfUntouched(final Iterable<Bucket> buckets) {
		final Map<Key, CasPut> payload = new HashMap<>();
		final Set<Key> successes = new HashSet<>();

		for (final Bucket buck: buckets) {
			if (!buck.isCacheable()) {
				successes.add(buck.getKey());
				continue;
			}

			final Integer expirySeconds = cacheControl.getExpirySeconds(buck.getKey());
			if (expirySeconds == null) {
				successes.add(buck.getKey());
				continue;
			}

			payload.put(buck.getKey(), new CasPut(buck.identifiableValue, buck.getNextToStore(), expirySeconds));
		}

		successes.addAll(this.memcache.putIfUntouched(payload));

		return successes;
	}

	/**
	 * Bulk get on keys, getting the raw objects
	 */
	private Map<Key, Object> cacheGetAll(final Collection<Key> keys) {
		try {
			return this.memcache.getAll(keys);
		} catch (Exception ex) {
			// Some sort of serialization error, just wipe out the values
			log.warn("Error fetching values from memcache, deleting keys", ex);

			this.memcache.deleteAll(keys);

			return new HashMap<>();
		}
	}

	/**
	 * Basically a list comprehension of the keys for convenience.
	 */
	public static Set<Key> keysOf(final Collection<Bucket> buckets) {
		return buckets.stream().map(Bucket::getKey).collect(Collectors.toSet());
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.FullEntity;
import com.google.cloud.datastore.IncompleteKey;
import com.google.cloud.datastore.KeyValue;
import com.google.cloud.datastore.Value;
import com.google.cloud.datastore.ValueType;
import com.google.common.base.Preconditions;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.impl.translate.CreateContext;
import com.googlecode.objectify.impl.translate.LoadContext;
import com.googlecode.objectify.impl.translate.SaveContext;
import com.googlecode.objectify.impl.translate.Translator;
import com.googlecode.objectify.impl.translate.TypeKey;
import com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Set;

/**
 * Figures out what to do with key fields on POJO entities.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@Slf4j
public class KeyMetadata<P>
{
	private final ObjectifyFactory factory;

	/** The @Id field on the pojo - it will be Long, long, or String */
	private PropertyPopulator<Object, Object> idMeta;

	/** The @Parent field on the pojo, or null if there is no parent */
	private PropertyPopulator<Object, Object> parentMeta;

	/** */
	private final Class<P> clazz;

	/** The kind that is associated with the class, ala ObjectifyFactory.getKind(Class<?>) */
	private final String kind;

	/** */
	public KeyMetadata(final Class<P> clazz, final CreateContext ctx, final Path path) {
		this.factory = ctx.getFactory();
		this.clazz = clazz;

		findKeyFields(clazz, ctx, path);

		// There must be some field marked with @Id
		if (this.idMeta == null)
			throw new IllegalStateException("There must be an @Id field (String, Long, or long) for " + clazz.getName());

		this.kind = Key.getKind(clazz);
	}

	/**
	 * Recursively go through the class hierarchy looking for the idMeta and parentMeta fields.
	 * @throws IllegalStateException if @Id or @Parent shows up twice.
	 */
	private void findKeyFields(Class<?> inspect, CreateContext ctx, Path path) {
		if (inspect == Object.class)
			return;

		findKeyFields(inspect.getSuperclass(), ctx, path);

		for (Field field: inspect.getDeclaredFields()) {
			if (field.getAnnotation(Id.class) != null) {
				if (this.idMeta != null)
					throw new IllegalStateException("Multiple @Id fields in the class hierarchy of " + clazz.getName());

				if ((field.getType() != Long.class) && (field.getType() != long.class) && (field.getType() != String.class))
					throw new IllegalStateException("@Id field '" + field.getName() + "' in " + inspect.getName() + " must be of type Long, long, or String");

				Property prop = new FieldProperty(ctx.getFactory(), clazz, field);
				Translator<Object, Object> translator = ctx.getTranslator(new TypeKey<>(prop), ctx, path.extend(prop.getName()));

				this.idMeta = new PropertyPopulator<>(prop, translator);

			} else if (field.getAnnotation(Parent.class) != null) {
				if (this.parentMeta != null)
					throw new IllegalStateException("Multiple @Parent fields in the class hierarchy of " + clazz.getName());

				if (!isAllowedParentFieldType(field.getType()))
					throw new IllegalStateException("@Parent fields must be Ref<?>, Key<?>, or datastore Key. Illegal parent: " + field);

				Property prop = new FieldProperty(ctx.getFactory(), clazz, field);
				Translator<Object, Object> translator = ctx.getTranslator(new TypeKey<>(prop), ctx, path.extend(prop.getName()));

				this.parentMeta = new PropertyPopulator<>(prop, translator);
			}
		}
	}

	/** @return true if the type is an allowed parent type */
	private boolean isAllowedParentFieldType(Type type) {

		Class<?> erased = GenericTypeReflector.erase(type);

		return com.google.cloud.datastore.Key.class.isAssignableFrom(erased)
				|| Key.class.isAssignableFrom(erased)
				|| Ref.class.isAssignableFrom(erased);
	}

	/**
	 * Sets the key (from the container) onto the POJO id/parent fields. Also doublechecks to make sure
	 * that the key fields aren't present in the container, which means we're in a very bad state.
	 */
	public void setKey(final P pojo, final FullEntity<?> container, final LoadContext ctx, final Path containerPath) {
		if (container.contains(idMeta.getProperty().getName()))
			throw new IllegalStateException("Datastore has a property present for the id field " + idMeta.getProperty() + " which would conflict with the key: " + container);

		if (parentMeta != null && container.contains(parentMeta.getProperty().getName()))
			throw new IllegalStateException("Datastore has a property present for the parent field " + parentMeta.getProperty() + " which would conflict with the key: " + container);

		this.setKey(pojo, container.getKey(), ctx, containerPath);
	}

	/**
	 * Sets the key onto the POJO id/parent fields
	 */
	@SuppressWarnings("unchecked")
	private void setKey(final P pojo, final IncompleteKey key, final LoadContext ctx, final Path containerPath) {
		if (!clazz.isAssignableFrom(pojo.getClass()))
			throw new IllegalArgumentException("Trying to use metadata for " + clazz.getName() + " to set key of " + pojo.getClass().getName());

		// If no key, don't need to do anything
		if (key == null)
			return;

		idMeta.setValue(pojo, Keys.getIdValue(key), ctx, containerPath);

		final com.google.cloud.datastore.Key parentKey = key.getParent();
		if (parentKey != null) {
			if (this.parentMeta == null)
				throw new IllegalStateException("Loaded Entity has parent but " + clazz.getName() + " has no @Parent");

			parentMeta.setValue(pojo, (Value)KeyValue.of(parentKey), ctx, containerPath);
		}
	}

	/**
	 * Sets the key on a container from the POJO.
	 */
	@SuppressWarnings("unchecked")
	public <K extends IncompleteKey> void setKey(final FullEntity.Builder<K> container, final P pojo) {
		final IncompleteKey rawKey = getIncompleteKey(pojo);

		if (!(rawKey instanceof com.google.cloud.datastore.Key)) {
			// it's incomplete, make sure we can save it
			Preconditions.checkState(isIdNumeric(), "Cannot save an entity with a null String @Id: %s", pojo);
		}

		container.setKey((K)rawKey);
	}

	/** @return the datastore kind associated with this metadata */
	public String getKind() {
		return kind;
	}

	/**
	 * Gets a key composed of the relevant id and parent fields in the object.
	 *
	 * @param pojo must be of the entityClass type for this metadata.
	 * @return either a Key or IncompleteKey depending on whether the id is null or not
	 */
	public IncompleteKey getIncompleteKey(final P pojo) {
		log.trace("Getting key from {}", pojo);

		if (!clazz.isAssignableFrom(pojo.getClass()))
			throw new IllegalArgumentException("Trying to use metadata for " + clazz.getName() + " to get key of " + pojo.getClass().getName());

		final Object id = getId(pojo);
		final com.google.cloud.datastore.Key parent = getParentRaw(pojo);

		if (id == null)
			return factory.keys().createRawIncomplete(parent, kind);
		else
			return factory.keys().createRawAny(parent, kind, id);
	}

	/**
	 * Gets a key composed of the relevant id and parent fields in the object.
	 *
	 * @param pojo must be of the entityClass type for this metadata.
	 * @throws IllegalArgumentException if pojo has an incomplete key
	 */
	public com.google.cloud.datastore.Key getCompleteKey(final P pojo) {
		final com.google.cloud.datastore.IncompleteKey key = getIncompleteKey(pojo);

		if (key instanceof com.google.cloud.datastore.Key)
			return (com.google.cloud.datastore.Key)key;
		else
			throw new IllegalArgumentException("You cannot create a Key for an object with a null @Id. Object was " + pojo);
	}

	/** @return the name of the parent field, or null if there wasn't one */
	public String getParentFieldName() {
		return parentMeta == null ? null : parentMeta.getProperty().getName();
	}

	/** @return the name of the id field */
	public String getIdFieldName() {
		return idMeta.getProperty().getName();
	}

	/** @return the java type of the id field; it will be either Long.class, Long.TYPE, or String.class */
	public Class<?> getIdFieldType() {
		// The id must be Long, long, or String, therefore the type is always a Class
		return (Class<?>)idMeta.getProperty().getType();
	}

	/**
	 * @return true if the id field is numeric, false if it is String
	 */
	private boolean isIdNumeric() {
		return !(this.idMeta.getProperty().getType() == String.class);
	}

	/**
	 * @return true if the entity has a parent field
	 */
	public boolean hasParentField() {
		return this.parentMeta != null;
	}

	/**
	 * @return true if the parent should be loaded given the enabled fetch groups
	 */
	public boolean shouldLoadParent(Set<Class<?>> enabledGroups) {
		if (this.parentMeta == null)
			return false;

		return parentMeta.getLoadConditions().shouldLoad(enabledGroups, false);
	}

	/**
	 * @return true if the id field is uppercase-Long, which can be genearted.
	 */
	public boolean isIdGeneratable() {
		return this.idMeta.getProperty().getType() == Long.class;
	}

	/**
	 * Sets the numeric id field
	 */
	public void setLongId(P pojo, Long id) {
		if (!clazz.isAssignableFrom(pojo.getClass()))
			throw new IllegalArgumentException("Trying to use metadata for " + clazz.getName() + " to set key of " + pojo.getClass().getName());

		this.idMeta.getProperty().set(pojo, id);
	}

	/**
	 * Get the contents of the @Parent field as a datastore key.
	 * @return null if there was no @Parent field, or the field is null.
	 */
	private com.google.cloud.datastore.Key getParentRaw(P pojo) {
		if (parentMeta == null)
			return null;

		final Value<Object> value = parentMeta.getValue(pojo, new SaveContext(), Path.root());
		return (value == null || value.getType() == ValueType.NULL)
				? null
				: (com.google.cloud.datastore.Key)value.get();
	}

	/**
	 * Get whatever is in the @Id field of the pojo doing no type checking or conversion
	 * @return Long or String or null
	 */
	private Object getId(P pojo) {
		return idMeta.getProperty().get(pojo);
	}

	/**
	 * Change this slightly to just a null check on the id so that null String will be allowed.
	 * The advantage of this is that we'll get a consistent error message when you actually
	 * try to save a null String id, instead of the weird things that will happen when you try
	 * to generate a Key for the object.
	 *
	 * @return true if this entity object has a null id
	 */
	public boolean requiresAutogeneratedId(P entity) {
		return /*isIdGeneratable() &&*/ getId(entity) == null;
	}
}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.FullEntity;
import com.googlecode.objectify.annotation.Subclass;
import com.googlecode.objectify.impl.Path;

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

/**
 *
Translator which maps classes, both normal embedded classes and Entity classes.

 *
 *
Entity classes are just like any other class except they have @Id and @Parent fields and a kind.
 * When translating to native datastore structure (Entity for top level, EmbeddedEntity for an embedded field)
 * then these attributes are stored in the Key structure, not as properties.

 *
 *
An entity class is any class which has the @Entity annotation anywhere in its superclass
 * hierarchy.

 *
 *
Note that entities can be embedded in other objects; they are still entities. The
 * difference between an embedded class and an embedded entity is that the entity has a Key.

 *
 *
One noteworthy issue is that we must ensure there is only one classtranslator for any
 * given class. Normally the discovery process creates a separate translator for each set of
 * annotations, however, this screws up the subclass registration process, which needs to
 * register at each parent class translator. Since field annotations are actually irrelevant to
 * the internal function of a ClassTranslator, we can just cache class translators here
 * in the factory. There will never be more than one translator for a given class, even
 * though many TypeKeys may point at it.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class ClassTranslatorFactory
 implements TranslatorFactory<P, FullEntity<?>>
{
	/** Cache of existing translators, see the class javadoc */
	private final Map<Class
, ClassTranslator
> translators = new HashMap<>();

	@Override
	public ClassTranslator
 create(final TypeKey
 tk, final CreateContext ctx, final Path path) {
		final Class
 clazz = tk.getTypeAsClass();

		ClassTranslator
 classTranslator = translators.get(clazz);
		if (classTranslator == null) {
			classTranslator = new ClassTranslator<>(clazz, ctx, path);

			translators.put(clazz, classTranslator);

			if (clazz.isAnnotationPresent(Subclass.class))
				registerSubclass(classTranslator, new TypeKey<>(clazz.getSuperclass(), tk), ctx, path);
		}

		return classTranslator;
	}

	/**
	 * Recursively register this subclass with all the superclass translators. This works because we cache
	 * translators uniquely in the factory.
	 */
	private void registerSubclass(final ClassTranslator
 translator, final TypeKey<? super P> superclassTypeKey, final CreateContext ctx, final Path path) {
		if (superclassTypeKey.getTypeAsClass() == Object.class)
			return;

		@SuppressWarnings("unchecked")
		final ClassTranslator<? super P> superTranslator = create((TypeKey)superclassTypeKey, ctx, path);
		superTranslator.registerSubclass(translator);

		registerSubclass(translator, new TypeKey<>(superclassTypeKey.getTypeAsClass().getSuperclass()), ctx, path);
	}
}