package com.googlecode.objectify.impl.translate; import com.google.appengine.api.datastore.PropertyContainer; import com.googlecode.objectify.ObjectifyFactory; import com.googlecode.objectify.impl.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * <p>Manages all the translators used to map between POJO fields and the * types that the Datastore can actually persist. Essentially acts as an * aggregator for all the TranslatorFactory objects.</p> * * <p>When Objectify arranges a translator for a type at registration time, it runs * through the available TranslatorFactory instances one at time looking for one that * will provide a Translator. The first one found is kept and used during runtime * assembly and disassembly of entities.</p> * * @author Jeff Schnitzer <jeff@infohazard.org> */ public class Translators { /** */ ObjectifyFactory fact; /** */ List<TranslatorFactory<?, ?>> translatorFactories = new ArrayList<>(); /** Where we should insert new translators */ int insertPoint; /** Where we should insert new early translators */ int earlyInsertPoint; /** */ Map<TypeKey, Translator<?, ?>> translators = new ConcurrentHashMap<>(); /** * Initialize the default set of converters in the proper order. */ public Translators(ObjectifyFactory fact) { this.fact = fact; // The order is CRITICAL! this.translatorFactories.add(new TranslateTranslatorFactory(true)); // Early translators get first shot at everything // Magic inflection point at which we want to prioritize added EARLY translators this.earlyInsertPoint = this.translatorFactories.size(); // Annotation based translators go first this.translatorFactories.add(new ContainerTranslatorFactory()); this.translatorFactories.add(new SerializeTranslatorFactory()); // Serialize has priority over everything this.translatorFactories.add(new MapifyTranslatorFactory()); // Magic inflection point at which we want to prioritize added normal translators this.insertPoint = this.translatorFactories.size(); this.translatorFactories.add(new ByteArrayTranslatorFactory()); this.translatorFactories.add(new ArrayTranslatorFactory()); // AFTER byte array otherwise we will occlude it this.translatorFactories.add(new CollectionTranslatorFactory()); this.translatorFactories.add(new EmbeddedMapTranslatorFactory()); this.translatorFactories.add(new TranslateTranslatorFactory(false)); // Late translators get a shot after collections this.translatorFactories.add(new StringTranslatorFactory()); this.translatorFactories.add(new TextTranslatorFactory()); this.translatorFactories.add(new NumberTranslatorFactory()); this.translatorFactories.add(new KeyTranslatorFactory()); this.translatorFactories.add(new RefTranslatorFactory()); this.translatorFactories.add(new EnumTranslatorFactory()); this.translatorFactories.add(new SqlDateTranslatorFactory()); this.translatorFactories.add(new TimeZoneTranslatorFactory()); this.translatorFactories.add(new URLTranslatorFactory()); // Things that just work as they are (fundamental datastore classes) this.translatorFactories.add(new AsIsTranslatorFactory()); // LAST! It catches everything. this.translatorFactories.add(new ClassTranslatorFactory<>()); } /** * <p>Add a new translator to the list. Translators are added in order after most of the "plumbing" translators * (collections, arrays, maps, serialize, embeds, references) but before any of the standard value conversions * like String, Number, Key, Enum, SqlDate, TimeZone, etc.</p> * * <p>Translators are added in-order so earlier translaters pre-empt later translators.</p> */ public void add(TranslatorFactory<?, ?> trans) { this.translatorFactories.add(insertPoint, trans); insertPoint++; } /** * <p>Add a new translator to the beginning of the list, before all other translators * except other translators that have been added early.</p> */ public void addEarly(TranslatorFactory<?, ?> trans) { this.translatorFactories.add(earlyInsertPoint, trans); earlyInsertPoint++; insertPoint++; } /** * Obtains the Translator appropriate for this type and annotations. May be a cached * translator; if not, one will be discovered and cached. */ public <P, D> Translator<P, D> get(TypeKey tk, CreateContext ctx, Path path) { Translator<?, ?> translator = translators.get(tk); if (translator == null) { translator = create(tk, ctx, path); translators.put(tk, translator); } //noinspection unchecked return (Translator<P, D>)translator; } /** * Get the translator for a root entity class */ public <P> Translator<P, PropertyContainer> getRoot(Class<P> clazz) { return get(new TypeKey(clazz), new CreateContext(fact), Path.root()); } /** * Create a translator from scratch by going through the discovery process. */ private Translator<?, ?> create(TypeKey tk, CreateContext ctx, Path path) { for (TranslatorFactory<?, ?> trans: this.translatorFactories) { @SuppressWarnings("unchecked") Translator<?, ?> soFar = trans.create(tk, ctx, path); if (soFar != null) return soFar; } throw new IllegalArgumentException("Don't know how to translate " + tk.getType() + " with annotations " + Arrays.toString(tk.getAnnotations())); } }