google.registry.model.translators.ImmutableSortedMapTranslatorFactory.java Source code

Java tutorial

Introduction

Here is the source code for google.registry.model.translators.ImmutableSortedMapTranslatorFactory.java

Source

// Copyright 2016 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package google.registry.model.translators;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector.erase;
import static com.googlecode.objectify.repackaged.gentyref.GenericTypeReflector.getTypeParameter;
import static google.registry.util.CollectionUtils.nullToEmpty;

import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Ordering;
import com.google.common.reflect.TypeToken;
import com.googlecode.objectify.impl.Node;
import com.googlecode.objectify.impl.Path;
import com.googlecode.objectify.impl.Property;
import com.googlecode.objectify.impl.translate.CreateContext;
import com.googlecode.objectify.impl.translate.ListNodeTranslator;
import com.googlecode.objectify.impl.translate.LoadContext;
import com.googlecode.objectify.impl.translate.SaveContext;
import com.googlecode.objectify.impl.translate.SkipException;
import com.googlecode.objectify.impl.translate.Translator;
import com.googlecode.objectify.impl.translate.TranslatorFactory;
import com.googlecode.objectify.impl.translate.TranslatorRegistry;
import java.lang.reflect.Type;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * Abstract Objectify translator for {@link ImmutableSortedMap} fields.
 *
 * <p>This class should be extended for each set of concrete key/value types you wish to support.
 * This translator will only apply to {@code ImmutableSortedMap} model fields that have precicely
 * the same type parameters you specified.
 *
 * <p>This translator serves a similar purpose to
 * {@link com.googlecode.objectify.impl.translate.MapifyTranslatorFactory @Mapify}. Except this
 * maintains perfect immutability of the field value. Subclasses may override the
 * {@link #transformBeforeSave(ImmutableSortedMap)} methods to perform mutation on a per-concrete
 * type basis. This abstraction is also more readable than {@code @Mapify} because it shifts the
 * boilerplate into translator magic, rather than convoluting model data structures.
 *
 * <h3>Entity Data Layout</h3>
 *
 * <p>For example, if you had an {@code ImmutableSortedMap<String, String>} on a field named
 * {@code field}, then this would look like:<pre>   {@code
 *
 *   field.key -> [key1, key2]
 *   field.value -> [value1, value2]}</pre>
 *
 * <p>If you had an {@code ImmutableSortedMap<String, EmbeddedClass>} on a field named
 * {@code field}, where {@code EmbeddedClass} defines two {@code foo} and {@code bar} fields, then
 * the embedded properties might look like:<pre>   {@code
 *
 *   field.key -> [key1, key2]
 *   field.value.foo -> [foo1, foo2]
 *   field.value.bar -> [bar1, bar2]}</pre>
 *
 * @param <K> key type for sorted map which must be {@link Comparable}
 * @param <V> value type for sorted map
 */
abstract class ImmutableSortedMapTranslatorFactory<K extends Comparable<? super K>, V>
        implements TranslatorFactory<ImmutableSortedMap<K, V>> {

    private final TypeToken<K> keyType = new TypeToken<K>(getClass()) {
    };
    private final TypeToken<V> valueType = new TypeToken<V>(getClass()) {
    };
    private final String keyProperty;
    private final String valueProperty;

    ImmutableSortedMapTranslatorFactory() {
        this("key", "value");
    }

    /** Constructs a instance that's compatible with models migrated from {@code @Mapify}. */
    ImmutableSortedMapTranslatorFactory(String keyProperty, String valueProperty) {
        this.keyProperty = checkNotNull(keyProperty);
        this.valueProperty = checkNotNull(valueProperty);
    }

    /** Allows for changing the field data structure before it's written to the raw entity object. */
    ImmutableSortedMap<K, V> transformBeforeSave(ImmutableSortedMap<K, V> map) {
        return map;
    }

    @Override
    public final Translator<ImmutableSortedMap<K, V>> create(Path path, Property property, Type type,
            CreateContext ctx) {
        if (!ImmutableSortedMap.class.equals(erase(type))) {
            return null; // skip me and try to find another matching translator
        }
        Type fieldKeyType = getTypeParameter(type, ImmutableSortedMap.class.getTypeParameters()[0]);
        Type fieldValueType = getTypeParameter(type, ImmutableSortedMap.class.getTypeParameters()[1]);
        if (fieldKeyType == null || fieldValueType == null) {
            return null; // no type information is available
        }
        if (!keyType.isSupertypeOf(fieldKeyType) || !valueType.isSupertypeOf(fieldValueType)) {
            return null; // this ImmutableSortedMap does not have the same concrete component types
        }
        ctx.enterCollection(path);
        ctx.enterEmbed(path);
        try {
            // The component types can also be translated by Objectify!
            TranslatorRegistry translators = ctx.getFactory().getTranslators();
            final Translator<K> keyTranslator = translators.create(path.extend(keyProperty), property, fieldKeyType,
                    ctx);
            final Translator<V> valueTranslator = translators.create(path.extend(valueProperty), property,
                    fieldValueType, ctx);
            return new ListNodeTranslator<ImmutableSortedMap<K, V>>() {
                @Override
                protected ImmutableSortedMap<K, V> loadList(Node node, LoadContext ctx) {
                    ImmutableSortedMap.Builder<K, V> map = new ImmutableSortedMap.Builder<>(Ordering.natural());
                    for (Node child : node) {
                        try {
                            map.put(keyTranslator.load(child.get(keyProperty), ctx),
                                    valueTranslator.load(child.get(valueProperty), ctx));
                        } catch (SkipException e) {
                            // no problem, just skip that one
                        }
                    }
                    return map.build();
                }

                @Override
                protected Node saveList(@Nullable ImmutableSortedMap<K, V> mapFromPojo, Path path, boolean index,
                        SaveContext ctx) {
                    checkState(!index, "At path %s: Index not allowed", path);
                    ImmutableSortedMap<K, V> mapToSave = transformBeforeSave(
                            ImmutableSortedMap.copyOfSorted(nullToEmpty(mapFromPojo)));
                    if (mapToSave.isEmpty()) {
                        throw new SkipException(); // the datastore doesn't store empty lists
                    }
                    Node node = new Node(path);
                    for (Map.Entry<K, V> entry : mapToSave.entrySet()) {
                        Node item = new Node(path);
                        item.put(keyProperty,
                                keyTranslator.save(entry.getKey(), path.extend(keyProperty), index, ctx));
                        item.put(valueProperty,
                                valueTranslator.save(entry.getValue(), path.extend(valueProperty), index, ctx));
                        node.addToList(item);
                    }
                    return node;
                }
            };
        } finally {
            ctx.exitEmbed();
            ctx.exitCollection();
        }
    }
}