package com.googlecode.objectify.impl.translate; import com.google.appengine.api.datastore.EmbeddedEntity; import com.googlecode.objectify.Key; import com.googlecode.objectify.ObjectifyFactory; import com.googlecode.objectify.annotation.Stringify; import com.googlecode.objectify.impl.Path; import com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector; import com.googlecode.objectify.stringifier.EnumStringifier; import com.googlecode.objectify.stringifier.InitializeStringifier; import com.googlecode.objectify.stringifier.KeyStringifier; import com.googlecode.objectify.stringifier.NullStringifier; import com.googlecode.objectify.stringifier.Stringifier; import com.googlecode.objectify.util.DatastoreUtils; import com.googlecode.objectify.util.GenericUtils; import java.lang.reflect.Type; import java.util.Map; /** * <p>Translator which turns a Map<String, ?> into an EmbeddedEntity. As keys in * EmbeddedEntity, the map keys must be String or something that can be converted to/from String via a * Stringifier. The value can be any normal translated value.</p> * * <p>This automatically stringifies Enums and objectify Key<?>s</p> * * @author Jeff Schnitzer <jeff@infohazard.org> */ public class EmbeddedMapTranslatorFactory implements TranslatorFactory<Map<Object, Object>, EmbeddedEntity> { @Override public Translator<Map<Object, Object>, EmbeddedEntity> create(TypeKey<Map<Object, Object>> tk, CreateContext ctx, Path path) { @SuppressWarnings("unchecked") final Class<? extends Map<?, ?>> mapType = tk.getTypeAsClass(); // We apply to any Map if (!Map.class.isAssignableFrom(mapType)) return null; Stringify stringify = tk.getAnnotation(Stringify.class); final Type keyType = GenericUtils.getMapKeyType(tk.getType()); Class<?> keyTypeErased = GenericTypeReflector.erase(keyType); Class<? extends Stringifier> stringifierClass = null; if (stringify != null) stringifierClass = stringify.value(); else if (keyTypeErased == String.class) stringifierClass = NullStringifier.class; else if (Enum.class.isAssignableFrom(keyTypeErased)) stringifierClass = EnumStringifier.class; else if (keyTypeErased == Key.class) stringifierClass = KeyStringifier.class; if (stringifierClass == null) throw new IllegalStateException("Embedded Map keys must be of type String/Enum/Key<?> or field must specify @Stringify"); final ObjectifyFactory fact = ctx.getFactory(); @SuppressWarnings("unchecked") final Stringifier<Object> stringifier = (Stringifier<Object>)fact.construct(stringifierClass); if (stringifier instanceof InitializeStringifier) ((InitializeStringifier)stringifier).init(fact, keyType); Type componentType = GenericUtils.getMapValueType(tk.getType()); final Translator<Object, Object> componentTranslator = fact.getTranslators().get(new TypeKey<>(componentType, tk), ctx, path); return new TranslatorRecycles<Map<Object,Object>, EmbeddedEntity>() { @Override public Map<Object, Object> loadInto(EmbeddedEntity node, LoadContext ctx, Path path, Map<Object, Object> into) { // Make this work more like collections than atomic values if (node == null) throw new SkipException(); if (into == null) //noinspection unchecked into = (Map<Object, Object>)fact.constructMap(mapType); else into.clear(); for (Map.Entry<String, Object> entry: node.getProperties().entrySet()) { Object key = stringifier.fromString(entry.getKey()); Object value = componentTranslator.load(entry.getValue(), ctx, path.extend(entry.getKey())); into.put(key, value); } return into; } @Override public EmbeddedEntity save(Map<Object, Object> pojo, boolean index, SaveContext ctx, Path path) throws SkipException { // Make this work more like collections than atomic values if (pojo == null || pojo.isEmpty()) throw new SkipException(); EmbeddedEntity emb = new EmbeddedEntity(); for (Map.Entry<Object, Object> entry: pojo.entrySet()) { try { String key = stringifier.toString(entry.getKey()); Path propPath = path.extend(key); Object value = componentTranslator.save(entry.getValue(), index, ctx, propPath); DatastoreUtils.setContainerProperty(emb, key, value, index, ctx, propPath); } catch (SkipException e) { // do nothing } } return emb; } }; } }