package com.googlecode.objectify.impl.translate;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.impl.LoadConditions;
import com.googlecode.objectify.impl.LoadEngine;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The context of a load operation, which may extend across several entities (for example, a batch).
*/
public class LoadContext
{
/** */
private static final Logger log = Logger.getLogger(LoadContext.class.getName());
/** */
LoadEngine engine;
/** Lazily created, but executed at the end of done() */
List<Runnable> deferred;
/** The key of the current root entity; will change as multiple entities are loaded */
Key<?> currentRoot;
/** As we enter and exit embedded contexts, track the objects */
Deque<Object> containers = new ArrayDeque<>();
/**
* If a translator implements the marker interface Recycles, this will be populated with
* the existing value of a property.
*/
Object recycled;
/** */
public LoadContext(LoadEngine engine) {
this.engine = engine;
}
/** The most recently recycled value. It can be used exactly once. */
public Object useRecycled() {
Object value = recycled;
recycled = null;
return value;
}
/** */
public void recycle(Object value) {
this.recycled = value;
}
/** Sets the current root entity */
public void setCurrentRoot(Key<?> rootEntity) {
this.currentRoot = rootEntity;
}
/**
* Call this when a load process completes. Executes anything in the batch and then executes any delayed operations.
*/
public void done() {
engine.execute();
while (deferred != null) {
List<Runnable> runme = deferred;
deferred = null; // reset this because it might get filled with more
for (Runnable run: runme) {
if (log.isLoggable(Level.FINEST))
log.finest("Executing " + run);
run.run();
}
}
}
/**
* Create a Ref for the key, and maybe start a load operation depending on current load groups.
*/
public <T> Ref<T> loadRef(Key<T> key, LoadConditions loadConditions) {
return engine.makeRef(currentRoot, loadConditions, key);
}
/**
* Delays an operation until the context is done(). Typically this is for lifecycle methods.
*/
public void defer(Runnable runnable) {
if (this.deferred == null)
this.deferred = new ArrayList<>();
if (log.isLoggable(Level.FINEST))
log.finest("Deferring: " + runnable);
this.deferred.add(runnable);
}
/**
* Get the container object which is appropriate for the specified property. Go up the chain looking for a compatible
* type; the first one found is the container. If nothing found, throw an exception.
*/
public Object getContainer(Type containerType, Path path) {
Class<?> containerClass = GenericTypeReflector.erase(containerType);
Iterator<Object> containersIt = containers.descendingIterator();
// We have always entered the current 'this' context when processing properties, so the first thing
// we get will always be 'this'. So skip that and the first matching owner should be what we want.
containersIt.next();
while (containersIt.hasNext()) {
Object potentialContainer = containersIt.next();
if (containerClass.isAssignableFrom(potentialContainer.getClass()))
return potentialContainer;
}
throw new IllegalStateException("No container matching " + containerType + " in " + containers + " at path " + path);
}
/**
* Enter a container context; this is the context of the object that we are processing right now.
*/
public void enterContainerContext(Object container) {
containers.addLast(container);
}
/**
* Exit a container context. The parameter is just a sanity check to make sure that the value popped off is the same
* as the value we expect.
*/
public void exitContainerContext(Object expectedContainer) {
Object popped = containers.removeLast();
assert popped == expectedContainer;
}
}