package com.googlecode.objectify.impl.translate;
import com.google.appengine.api.datastore.PropertyContainer;
import com.googlecode.objectify.annotation.Subclass;
import com.googlecode.objectify.impl.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>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.</p>
*
* @author Jeff Schnitzer <jeff@infohazard.org>
*/
public class ClassTranslator<P> extends NullSafeTranslator<P, PropertyContainer>
{
private static final Logger log = Logger.getLogger(ClassTranslator.class.getName());
/** 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. */
private final Class<P> declaredClass;
/** Lets us construct the initial objects */
private final Creator<P> creator;
/** Does the heavy lifting of copying properties */
private final Populator<P> populator;
/**
* The discriminator for this subclass, or null for the base class.
*/
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<String> 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(Class<P> declaredClass, Path path, Creator<P> creator, Populator<P> populator) {
if (log.isLoggable(Level.FINEST))
log.finest("Creating class translator for " + declaredClass.getName() + " at path '"+ path + "'");
this.declaredClass = declaredClass;
this.creator = creator;
this.populator = populator;
Subclass sub = declaredClass.getAnnotation(Subclass.class);
if (sub != null) {
discriminator = (sub.name().length() > 0) ? sub.name() : declaredClass.getSimpleName();
addIndexedDiscriminators(declaredClass);
} else {
discriminator = null;
}
}
/**
* @return the class we translate
*/
public Class<P> getDeclaredClass() {
return declaredClass;
}
/**
* @return the discriminator for this class, or null if this is not a @Subclass
*/
public String getDiscriminator() {
return discriminator;
}
/**
* Get the populator associated with this class.
*/
public Populator<P> getPopulator() {
return populator;
}
/**
* Get the creator associated with this class.
*/
public Creator<P> getCreator() { return creator; }
/* */
@Override
public P loadSafe(PropertyContainer container, LoadContext ctx, Path path) throws SkipException {
// check if we need to redirect to a different translator
String containerDiscriminator = (String)container.getProperty(DISCRIMINATOR_PROPERTY);
if (!Objects.equals(discriminator, containerDiscriminator)) {
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
return translator.load(container, ctx, path);
} else {
// This is a normal load
P into = creator.load(container, ctx, path);
populator.load(container, ctx, path, into);
return into;
}
}
/* */
@Override
public PropertyContainer saveSafe(P pojo, boolean index, SaveContext ctx, 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")
ClassTranslator<P> translator = (ClassTranslator<P>)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
PropertyContainer into = creator.save(pojo, index, ctx, path);
populator.save(pojo, index, ctx, path, into);
if (discriminator != null) {
into.setUnindexedProperty(DISCRIMINATOR_PROPERTY, discriminator);
if (!indexedDiscriminators.isEmpty())
into.setProperty(DISCRIMINATOR_INDEX_PROPERTY, indexedDiscriminators);
}
return into;
}
}
/**
* Recursively go through the class hierarchy adding any discriminators that are indexed
*/
private void addIndexedDiscriminators(Class<?> clazz) {
if (clazz == Object.class)
return;
this.addIndexedDiscriminators(clazz.getSuperclass());
Subclass sub = clazz.getAnnotation(Subclass.class);
if (sub != null && sub.index()) {
String disc = (sub.name().length() > 0) ? sub.name() : clazz.getSimpleName();
this.indexedDiscriminators.add(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);
}
}