Package com.google.appengine.api.datastore

Source Code of com.google.appengine.api.datastore.DataTypeTranslator$DateType

// Copyright 2007 Google Inc. All rights reserved.

package com.google.appengine.api.datastore;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.datastore.Entity.UnindexedValue;
import com.google.appengine.api.users.User;
import com.google.apphosting.datastore.EntityV4;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.io.protocol.ProtocolSupport;
import com.google.protobuf.ByteString;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import com.google.storage.onestore.v3.OnestoreEntity.Path;
import com.google.storage.onestore.v3.OnestoreEntity.Path.Element;
import com.google.storage.onestore.v3.OnestoreEntity.Property;
import com.google.storage.onestore.v3.OnestoreEntity.Property.Meaning;
import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue;
import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue.ReferenceValue;
import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue.ReferenceValuePathElement;
import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue.UserValue;
import com.google.storage.onestore.v3.OnestoreEntity.Reference;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* {@code DataTypeTranslator} is a utility class for converting
* between the data store's {@code Property} protocol buffers and the
* user-facing classes ({@code String}, {@code User}, etc.).
*
*/
public final class DataTypeTranslator  {

  private static final RawValueType RAW_VALUE_TYPE = new RawValueType();
  /**
   * The list of supported types.
   *
   * Note: If you're going to modify this list, also update
   * DataTypeUtils. We're not building {@link DataTypeUtils#getSupportedTypes}
   * directly from this typeMap, because we want {@link DataTypeUtils} to be
   * translatable by GWT, so that {@link Entity Entities} can be easily sent
   * via GWT RPC.  Also, if you add a type here that is not immutable you'll
   * need to add special handling for it in {@link Entity#clone()}.
   */
  private static final Map<Class<?>, Type<?>> typeMap = Maps.newHashMap();
  static {
    typeMap.put(RawValue.class, RAW_VALUE_TYPE);

    typeMap.put(Float.class, new DoubleType());
    typeMap.put(Double.class, new DoubleType());

    typeMap.put(Byte.class, new Int64Type());
    typeMap.put(Short.class, new Int64Type());
    typeMap.put(Integer.class, new Int64Type());
    typeMap.put(Long.class, new Int64Type());
    typeMap.put(Date.class, new DateType());
    typeMap.put(Rating.class, new RatingType());

    typeMap.put(String.class, new StringType());
    typeMap.put(Link.class, new LinkType());
    typeMap.put(ShortBlob.class, new ShortBlobType());
    typeMap.put(Category.class, new CategoryType());
    typeMap.put(PhoneNumber.class, new PhoneNumberType());
    typeMap.put(PostalAddress.class, new PostalAddressType());
    typeMap.put(Email.class, new EmailType());
    typeMap.put(IMHandle.class, new IMHandleType());
    typeMap.put(BlobKey.class, new BlobKeyType());
    typeMap.put(Blob.class, new BlobType());
    typeMap.put(Text.class, new TextType());
    typeMap.put(EmbeddedEntity.class, new EmbeddedEntityType());

    typeMap.put(Boolean.class, new BoolType());
    typeMap.put(User.class, new UserType());
    typeMap.put(Key.class, new KeyType());
    typeMap.put(GeoPt.class, new GeoPtType());

    assert typeMap.keySet().equals(DataTypeUtils.getSupportedTypes())
        : "Warning:  DataTypeUtils and DataTypeTranslator do not agree "
        + "about supported classes: " + typeMap.keySet() + " vs. "
        + DataTypeUtils.getSupportedTypes();
  }

  /**
   * A map with the {@link Comparable} classes returned by all the instances of
   * {@link Type#asComparable(Object)} as keys and the pb code point as the value.
   * Used for comparing values that don't map to the same pb code point.
   */
  private static final
  Map<Class<? extends Comparable<?>>, Integer> comparableTypeMap =
      new HashMap<Class<? extends Comparable<?>>, Integer>();

  static {
    comparableTypeMap.put(ComparableByteArray.class, PropertyValue.kstringValue);
    comparableTypeMap.put(Long.class, PropertyValue.kint64Value);
    comparableTypeMap.put(Double.class, PropertyValue.kdoubleValue);
    comparableTypeMap.put(Boolean.class, PropertyValue.kbooleanValue);
    comparableTypeMap.put(User.class, PropertyValue.kUserValueGroup);
    comparableTypeMap.put(Key.class, PropertyValue.kReferenceValueGroup);
    comparableTypeMap.put(GeoPt.class, PropertyValue.kPointValueGroup);
  }

  /**
   * Add all of the properties in the specified map to an {@code EntityProto}.
   * This involves determining the type of each property and creating the
   * proper type-specific protocol buffer.
   *
   * If the property value is an {@link UnindexedValue}, or if it's a
   * type that is never indexed, e.g. {@code Text} and {@code Blob}, it's
   * added to {@code EntityProto.raw_property}. Otherwise it's added to
   * {@code EntityProto.property}.
   *
   * @param map A not {@code null} map of all the properties which will
   * be set on {@code proto}
   * @param proto A not {@code null} protocol buffer
   */
  public static void addPropertiesToPb(Map<String, Object> map, EntityProto proto) {
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      String name = entry.getKey();
      boolean indexed = !(entry.getValue() instanceof UnindexedValue);
      Object value = PropertyContainer.unwrapValue(entry.getValue());

      if (value instanceof Collection<?>) {
        Collection<?> values = (Collection<?>) value;
        if (values.isEmpty()) {
          addPropertyToPb(name, null, indexed, false, proto);
        } else {
          for (Object listValue : values) {
            addPropertyToPb(name, listValue, indexed, true, proto);
          }
        }
      } else {
        addPropertyToPb(name, value, indexed, false, proto);
      }
    }
  }

  /**
   * Adds a property to {@code entity}.
   *
   * @param name the property name
   * @param value the property value
   * @param indexed whether this property should be indexed. This may be
   * overriden by property types like Blob and Text that are never indexed.
   * @param multiple whether this property has multiple values
   * @param entity the entity to populate
   */
  private static void addPropertyToPb(String name, Object value, boolean indexed, boolean multiple,
      EntityProto entity) {
    Property property = new Property();
    property.setName(name);
    property.setMultiple(multiple);
    PropertyValue newValue = property.getMutableValue();
    if (value != null) {
      Type<?> type = getType(value.getClass());
      Meaning meaning = type.getV3Meaning();
      if (meaning != property.getMeaningEnum()) {
        property.setMeaning(meaning);
      }
      indexed &= type.toV3Value(value, newValue);
    }
    if (!indexed) {
      entity.addRawProperty(property);
    } else {
      entity.addProperty(property);
    }
  }

  static PropertyValue toV3Value(Object value) {
    PropertyValue propertyValue = new PropertyValue();
    if (value != null) {
      getType(value.getClass()).toV3Value(value, propertyValue);
    }
    return propertyValue;
  }

  /**
   * Copy all of the indexed properties present on {@code proto} into {@code map}.
   */
  public static void extractIndexedPropertiesFromPb(EntityProto proto, Map<String, Object> map) {
    for (Property property : proto.propertys()) {
      addPropertyToMap(property, true, map);
    }
  }

  /**
   * Copy all of the unindexed properties present on {@code proto} into {@code map}.
   */
  private static void extractUnindexedPropertiesFromPb(EntityProto proto, Map<String, Object> map) {
    for (Property property : proto.rawPropertys()) {
      addPropertyToMap(property, false, map);
    }
  }

  /**
   * Copy all of the properties present on {@code proto} into {@code map}.
   */
  public static void extractPropertiesFromPb(EntityProto proto, Map<String, Object> map) {
    extractIndexedPropertiesFromPb(proto, map);
    extractUnindexedPropertiesFromPb(proto, map);
  }

  /**
   * Copy all of the implicit properties present on {@code proto} into {@code map}.
   */
  public static void extractImplicitPropertiesFromPb(EntityProto proto, Map<String, Object> map) {
    for (Property property : getImplicitProperties(proto)) {
      addPropertyToMap(property, true, map);
    }
  }

  private static Iterable<Property> getImplicitProperties(EntityProto proto) {
    return Collections.singleton(buildImplicitKeyProperty(proto));
  }

  private static Property buildImplicitKeyProperty(EntityProto proto) {
    Property keyProp = new Property();
    keyProp.setName(Entity.KEY_RESERVED_PROPERTY);
    PropertyValue propVal = new PropertyValue();
    propVal.setReferenceValue(KeyType.toReferenceValue(proto.getKey()));
    keyProp.setValue(propVal);
    return keyProp;
  }

  /**
   * Locates and returns all indexed properties with the given name on the
   * given proto. If there are a mix of matching multiple and non-multiple
   * properties, the collection will contain the first non-multiple property.
   * @return A list, potentially empty, containing matching properties.
   */
  public static Collection<Property> findIndexedPropertiesOnPb(
      EntityProto proto, String propertyName) {
    if (propertyName.equals(Entity.KEY_RESERVED_PROPERTY)) {
      return Collections.singleton(buildImplicitKeyProperty(proto));
    }
    List<Property> matchingMultipleProps = new ArrayList<>();
    for (Property prop : proto.propertys()) {
      if (prop.getName().equals(propertyName)) {
        if (!prop.isMultiple()) {
          return Collections.singleton(prop);
        } else {
          matchingMultipleProps.add(prop);
        }
      }
    }
    return matchingMultipleProps;
  }

  private static void addPropertyToMap(Property property, boolean indexed,
      Map<String, Object> map) {
    String name = property.getName();
    Object value = getPropertyValue(property);

    if (property.isMultiple()) {
      @SuppressWarnings({"unchecked"})
      List<Object> results = (List<Object>) PropertyContainer.unwrapValue(map.get(name));
      if (results == null) {
        results = new ArrayList<Object>();
        map.put(name, indexed ? results : new UnindexedValue(results));
      }
      results.add(value);
    } else {
      map.put(name, indexed ? value : new UnindexedValue(value));
    }
  }

  /**
   * Returns the value for the property as its canonical type.
   *
   * @param property a not {@code null} property
   * @return {@code null} if no value was set for {@code property}
   */
  public static Object getPropertyValue(Property property) {
    PropertyValue value = property.getValue();
    for (Type<?> type : typeMap.values()) {
      if (type.isType(property.getMeaningEnum(), value)) {
        return type.getValue(value);
      }
    }
    return null;
  }

  /**
   * @see #addPropertiesToPb(Map, EntityProto)
   */
  static void addPropertiesToPb(Map<String, Object> map, EntityV4.Entity.Builder proto) {
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      proto.addProperty(toV4Property(entry.getKey(), entry.getValue()));
    }
  }

  /**
   * Copy all of the properties present on {@code proto} into {@code map}.
   *
   * EntityV4 must know if the proto came from an index-only query as User and GeoPt types
   * overwrite the INDEX_ONLY meaning.
   *
   * @param proto the proto from which to extract properties
   * @param indexOnly if the proto is from an index only query (a projection query)
   * @param map the map to populate
   */
  static void extractPropertiesFromPb(EntityV4.EntityOrBuilder proto, boolean indexOnly,
      Map<String, Object> map) {
    if (indexOnly) {
      for (EntityV4.PropertyOrBuilder prop : proto.getPropertyOrBuilderList()) {
        map.put(prop.getName(), new RawValue(prop.getValue()));
      }
    } else {
      for (EntityV4.PropertyOrBuilder prop : proto.getPropertyOrBuilderList()) {
        addPropertyToMap(prop, map);
      }
    }
  }

  static EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
    if (value == null) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      setIndexed(indexed, builder);
      return builder;
    }
    return getType(value.getClass()).toV4Value(value, indexed);
  }

  private static EntityV4.Property.Builder toV4Property(String name, Object value) {
    EntityV4.Property.Builder builder = EntityV4.Property.newBuilder();
    builder.setName(name);
    boolean indexed = !(value instanceof UnindexedValue);
    value = PropertyContainer.unwrapValue(value);
    if (value instanceof Collection<?>) {
      Collection<?> values = (Collection<?>) value;
      if (values.isEmpty()) {
        builder.setValue(toV4Value(null, indexed));
      } else {
        EntityV4.Value.Builder valueBuilder = builder.getValueBuilder();
        for (Object listValue : values) {
          valueBuilder.addListValue(toV4Value(listValue, indexed));
        }
      }
    } else {
      builder.setValue(toV4Value(value, indexed));
    }
    return builder;
  }

  private static void addPropertyToMap(EntityV4.PropertyOrBuilder prop, Map<String, Object> map) {
    EntityV4.ValueOrBuilder value = prop.getValueOrBuilder();
    boolean indexed = value.getIndexed();
    Object result;
    if (value.getListValueCount() > 0) {
      ArrayList<Object> resultList = new ArrayList<Object>(value.getListValueCount());
      for (EntityV4.ValueOrBuilder subValue : value.getListValueOrBuilderList()) {
        indexed &= subValue.getIndexed();
        if (subValue.getListValueCount() > 0) {
          throw new IllegalArgumentException("Invalid Entity PB: list within a list.");
        }
        resultList.add(getValue(subValue));
      }
      result = resultList;
    } else {
      result = getValue(value);
    }

    if (!indexed) {
      result = new UnindexedValue(result);
    }
    map.put(prop.getName(), result);
  }

  private static Object getValue(EntityV4.ValueOrBuilder value) {
    for (Type<?> type : typeMap.values()) {
      if (type.isType(value)) {
        return type.getValue(value);
      }
    }
    return null;
  }

  private static Meaning getV3MeaningOf(EntityV4.ValueOrBuilder value) {
    return Meaning.valueOf(value.getMeaning());
  }

  private static AppIdNamespace toAppIdNamespace(EntityV4.PartitionIdOrBuilder partitionId) {
    return new AppIdNamespace(partitionId.getDatasetId(),
        partitionId.hasNamespace() ? partitionId.getNamespace() : "");
  }

  private static EntityV4.PartitionId.Builder toV4PartitionId(AppIdNamespace appNs) {
    EntityV4.PartitionId.Builder builder = EntityV4.PartitionId.newBuilder();
    builder.setDatasetId(appNs.getAppId());
    if (!appNs.getNamespace().isEmpty()) {
      builder.setNamespace(appNs.getNamespace());
    }
    return builder;
  }

  static EntityV4.Key.Builder toV4Key(Key key) {
    EntityV4.Key.Builder builder = EntityV4.Key.newBuilder();
    builder.setPartitionId(toV4PartitionId(key.getAppIdNamespace()));
    List<EntityV4.Key.PathElement> pathElementList = new ArrayList<>();
    do {
      EntityV4.Key.PathElement.Builder pathElement = EntityV4.Key.PathElement.newBuilder();
      pathElement.setKind(key.getKind());
      if (key.getName() != null) {
        pathElement.setName(key.getName());
      } else if (key.getId() != Key.NOT_ASSIGNED) {
        pathElement.setId(key.getId());
      }
      pathElementList.add(pathElement.build());
      key = key.getParent();
    } while (key != null);
    builder.addAllPathElement(Lists.reverse(pathElementList));
    return builder;
  }

  static Key toKey(EntityV4.KeyOrBuilder proto) {
    AppIdNamespace appIdNamespace = toAppIdNamespace(proto.getPartitionId());
    if (proto.getPathElementCount() == 0) {
      throw new IllegalArgumentException("Invalid Key PB: no elements.");
    }
    Key key = null;
    for (EntityV4.Key.PathElementOrBuilder e : proto.getPathElementOrBuilderList()) {
      String kind = e.getKind();
      if (e.hasName() && e.hasId()) {
        throw new IllegalArgumentException("Invalid Key PB: both id and name are set.");
      }
      key = new Key(kind, key, e.getId(), e.hasName() ? e.getName() : null, appIdNamespace);
    }
    return key;
  }

  static Entity toEntity(EntityV4.EntityOrBuilder v4Entity) {
    Entity entity = new Entity(DataTypeTranslator.toKey(v4Entity.getKey()));
    DataTypeTranslator.extractPropertiesFromPb(v4Entity, false,
        entity.getPropertyMap());
    return entity;
  }

  static Entity toEntity(EntityV4.EntityOrBuilder v4Entity, Collection<Projection> projections) {
    Entity entity = new Entity(DataTypeTranslator.toKey(v4Entity.getKey()));

    Map<String, Object> values = Maps.newHashMap();
    DataTypeTranslator.extractPropertiesFromPb(v4Entity, true, values);
    for (Projection projection : projections) {
      entity.setProperty(projection.getName(), projection.getValue(values));
    }
    return entity;
  }

  static EntityV4.Entity.Builder toV4Entity(Entity entity) {
    EntityV4.Entity.Builder v4Entity = EntityV4.Entity.newBuilder();
    v4Entity.setKey(toV4Key(entity.getKey()));
    addPropertiesToPb(entity.getPropertyMap(), v4Entity);
    return v4Entity;
  }

  /**
   * Returns the value for the property as its comparable representation type.
   *
   * @param property a not {@code null} property
   * @return {@code null} if no value was set for {@code property}
   */
  @SuppressWarnings("unchecked")
  public static Comparable<Object> getComparablePropertyValue(Property property) {
    return (Comparable<Object>) RAW_VALUE_TYPE.asComparable(new RawValue(property.getValue()));
  }

  /**
   * Converts the given {@link Object} into a supported value then returns it as
   * a comparable object so it can be compared to other data types.
   *
   * @param value any Object that can be converted into a supported DataType
   * @return {@code null} if value is null
   * @throws UnsupportedOperationException if value is not supported
   */
  @SuppressWarnings("unchecked")
  static Comparable<Object> getComparablePropertyValue(Object value) {
    return value == null ? null
        : (Comparable<Object>) getType(value.getClass()).asComparable(value);
  }

  /**
   * Get the rank of the given datastore type relative to other datastore
   * types.  Note that datastore types do not necessarily have unique ranks.
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public static int getTypeRank(Class<? extends Comparable> datastoreType) {
    return comparableTypeMap.get(datastoreType);
  }

  /**
   * Gets the {@link Type} that knows how to translate objects of
   * type {@code clazz} into protocol buffers that the data store can
   * handle.
   * @throws UnsupportedOperationException if clazz is not supported
   */
  @SuppressWarnings("unchecked")
  private static <T> Type<T> getType(Class<T> clazz) {
    if (typeMap.containsKey(clazz)) {
      return (Type<T>) typeMap.get(clazz);
    } else {
      throw new UnsupportedOperationException("Unsupported data type: " + clazz.getName());
    }
  }

  /**
   * {@code Type} is an abstract class that knows how to convert Java
   * objects of one or more types into datastore representations.
   *
   * @param <T> The canonical Java class for this type.
   */
  abstract static class Type<T> {
    /**
     * @returns {@code true} if the given meaning and property value matches this {@link Type}.
     */
    public final boolean isType(Meaning meaning, PropertyValue propertyValue) {
      return meaning == getV3Meaning() && hasValue(propertyValue);
    }

    /**
     * @returns {@code true} if the given value matches this {@link Type}.
     */
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      return getV3MeaningOf(propertyValue) == getV3Meaning() && hasValue(propertyValue);
    }

    /**
     * Returns the {@link Comparable} for the given value, or
     * {@code null} if values of this type are not comparable.
     */
    public abstract Comparable<?> asComparable(Object value);

    /**
     * Sets the value of {@code propertyValue} to {@code value}.
     * @returns if the value is indexable
     */
    public abstract boolean toV3Value(Object value, PropertyValue propertyValue);

    /**
     * Returns a new V4 Value for the given parameters.
     *
     * @param value the Java native value to convert
     * @param indexed the desired indexing, ignored for types that are not indexable
     * @returns the EntityV4 representation of the given value and desired indexing
     */
    public abstract EntityV4.Value.Builder toV4Value(Object value, boolean indexed);

    /**
     * Returns the value of {@code propertyValue} as its canonical Java type.
     *
     * Use {@link #isType} first to determine if the property has a value of the given type.
     *
     * @param propertyValue a not {@code null} value representing this {@code Type}
     * @return the canonical Java representation of {@code propertyValue}.
     */
    public abstract T getValue(PropertyValue propertyValue);

    /**
     * @see Type#getValue(PropertyValue)
     */
    public abstract T getValue(EntityV4.ValueOrBuilder propertyValue);

    /**
     * @returns {@code true} if a value of this {@code Type} is set on the given propertyValue.
     */
    public abstract boolean hasValue(PropertyValue propertyValue);

    /**
     * @returns {@code true} if a value of this {@code Type} is set on the given propertyValue.
     */
    public abstract boolean hasValue(EntityV4.ValueOrBuilder propertyValue);

    /**
     * @returns the {@link Meaning} for this {@link Type}
     */
    protected Meaning getV3Meaning() {
      return Meaning.NO_MEANING;
    }
  }

  /**
   * A base class with common functions for types that have the same datastore representation.
   *
   * @param <S> the datastore type
   * @param <T> the canonical Java class for this type
   */
  private abstract static class BaseVariantType<S, T> extends Type<T> {
    /**
     * @returns the datastore representation of the given value
     */
    protected abstract S toDatastoreValue(Object value);
    /**
     * @returns the native representation of the given value
     */
    protected abstract T fromDatastoreValue(S datastoreValue);
  }

  /**
   * Base class for types that store strings in the datastore.
   *
   * @param <T> the canonical Java class for this type
   */
  private abstract static class BaseStringType<T> extends BaseVariantType<String, T> {
    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      propertyValue.setStringValue(toDatastoreValue(value));
      return true;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setStringValue(toDatastoreValue(value));
      setIndexed(indexed, builder);
      setMeaning(getV3Meaning().getValue(), builder);
      return builder;
    }

    @Override
    public final T getValue(PropertyValue propertyValue) {
      return fromDatastoreValue(propertyValue.getStringValue());
    }

    @Override
    public T getValue(EntityV4.ValueOrBuilder propertyValue) {
      return fromDatastoreValue(propertyValue.getStringValue());
    }

    @Override
    public final boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasStringValue();
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasStringValue();
    }

    @Override
    public ComparableByteArray asComparable(Object value) {
      return new ComparableByteArray(ProtocolSupport.toBytesUtf8(toDatastoreValue(value)));
    }
  }

  /**
   * Base class for types that store bytes in the datastore.
   *
   * @param <T> the canonical Java class for this type
   */
  private abstract static class BaseBlobType<T> extends BaseVariantType<byte[], T> {
    protected abstract boolean isIndexable();

    @Override
    public final boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasStringValue();
    }

    @Override
    public final boolean toV3Value(Object value, PropertyValue propertyValue) {
      propertyValue.setStringValueAsBytes(toDatastoreValue(value));
      return isIndexable();
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setBlobValue(ByteString.copyFrom(toDatastoreValue(value)));
      setIndexed(indexed && isIndexable(), builder);
      return builder;
    }

    @Override
    public final T getValue(PropertyValue propertyValue) {
      return fromDatastoreValue(propertyValue.getStringValueAsBytes());
    }

    @Override
    public final ComparableByteArray asComparable(Object value) {
      return isIndexable() ? new ComparableByteArray(toDatastoreValue(value)) : null;
    }
  }

  /**
   * Base class for types that store predefined entities in V4.
   *
   * @param <T> the canonical Java class for this type
   */
  private abstract static class BasePredefinedEntityType<T> extends Type<T> {
    /**
     * @returns the predefined entity meaning to use in V4
     */
    protected abstract int getV4Meaning();

    /**
     * @returns the V4 Entity representation for the given value
     */
    protected abstract EntityV4.Entity getEntity(Object value);

    @Override
    public final boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.getMeaning() == getV4Meaning() && hasValue(propertyValue);
    }

    @Override
    public final boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasEntityValue();
    }

    @Override
    public final EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setEntityValue(getEntity(value));
      setIndexed(indexed, builder);
      setMeaning(getV4Meaning(), builder);
      return builder;
    }
  }

  /**
   * Returns the V4 property representation for the given name and value, unindexed.
   */
  private static EntityV4.Property makeUnindexedProperty(String name, double value) {
    return EntityV4.Property.newBuilder()
        .setName(name)
        .setValue(EntityV4.Value.newBuilder().setDoubleValue(value).setIndexed(false))
        .build();
  }

  /**
   * Returns the V4 property representation for the given name and value, unindexed.
   */
  private static EntityV4.Property makeUnindexedProperty(String name, String value) {
    return EntityV4.Property.newBuilder()
        .setName(name)
        .setValue(EntityV4.Value.newBuilder().setStringValue(value).setIndexed(false))
        .build();
  }

  /**
   * Base class for types that int64 values in the datastore.
   *
   * @param <T> the canonical Java class for this type
   */
  private abstract static class BaseInt64Type<T> extends BaseVariantType<Long, T> {
    @Override
    public final boolean toV3Value(Object value, PropertyValue propertyValue) {
      propertyValue.setInt64Value(toDatastoreValue(value));
      return true;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setIntegerValue(toDatastoreValue(value));
      setIndexed(indexed, builder);
      setMeaning(getV3Meaning().getValue(), builder);
      return builder;
    }

    @Override
    public T getValue(PropertyValue propertyValue) {
      return fromDatastoreValue(propertyValue.getInt64Value());
    }

    @Override
    public T getValue(EntityV4.ValueOrBuilder propertyValue) {
      return fromDatastoreValue(propertyValue.getIntegerValue());
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasInt64Value();
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasIntegerValue();
    }

    @Override
    public Long asComparable(Object value) {
      return toDatastoreValue(value);
    }
  }

  /**
   * The type for projected index values.
   */
  private static final class RawValueType extends Type<RawValue> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.INDEX_VALUE;
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return true;
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return true;
    }

    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      throw new UnsupportedOperationException();
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      throw new UnsupportedOperationException();
    }

    @Override
    public RawValue getValue(PropertyValue propertyValue) {
      return new RawValue(propertyValue);
    }

    @Override
    public RawValue getValue(EntityV4.ValueOrBuilder propertyValue) {
      EntityV4.Value value = null;
      if (propertyValue instanceof EntityV4.Value) {
        value = (EntityV4.Value) propertyValue;
      } else {
        value = ((EntityV4.Value.Builder) propertyValue).build();
      }
      return new RawValue(value);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Comparable<?> asComparable(Object value) {
      value = ((RawValue) value).getValue();
      if (value instanceof byte[]) {
        return new ComparableByteArray((byte[]) value);
      }
      return (Comparable<?>) value;
    }
  }

  /**
   * The raw String type.
   */
  private static final class StringType extends BaseStringType<String> {
    @Override
    protected String toDatastoreValue(Object value) {
      return value.toString();
    }

    @Override
    protected String fromDatastoreValue(String datastoreValue) {
      return datastoreValue;
    }
  }

  /**
   * The raw int64 type.
   */
  private static final class Int64Type extends BaseInt64Type<Long> {
    @Override
    protected Long toDatastoreValue(Object value) {
      return ((Number) value).longValue();
    }

    @Override
    protected Long fromDatastoreValue(Long datastoreValue) {
      return datastoreValue;
    }
  }

  /**
   * The raw double type.
   */
  private static final class DoubleType extends Type<Double> {
    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      propertyValue.setDoubleValue(((Number) value).doubleValue());
      return true;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setDoubleValue(((Number) value).doubleValue());
      setIndexed(indexed, builder);
      return builder;
    }

    @Override
    public Double getValue(PropertyValue propertyValue) {
      return propertyValue.getDoubleValue();
    }

    @Override
    public Double getValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.getDoubleValue();
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasDoubleValue();
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasDoubleValue();
    }

    @Override
    public Double asComparable(Object value) {
      return ((Number) value).doubleValue();
    }
  }

  /**
   * The raw boolean type.
   */
  private static final class BoolType extends Type<Boolean> {
    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      propertyValue.setBooleanValue((Boolean) value);
      return true;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setBooleanValue((Boolean) value);
      setIndexed(indexed, builder);
      return builder;
    }

    @Override
    public Boolean getValue(PropertyValue propertyValue) {
      return propertyValue.isBooleanValue();
    }

    @Override
    public Boolean getValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.getBooleanValue();
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasBooleanValue();
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasBooleanValue();
    }

    @Override
    public Boolean asComparable(Object value) {
      return (Boolean) value;
    }
  }

  /**
   * The user type.
   *
   * Stored as an entity with a special meaning in V4.
   */
  private static final class UserType extends BasePredefinedEntityType<User> {
    public static final int MEANING_PREDEFINED_ENTITY_USER = 20;
    public static final String PROPERTY_NAME_EMAIL = "email";
    public static final String PROPERTY_NAME_AUTH_DOMAIN = "auth_domain";
    public static final String PROPERTY_NAME_USER_ID = "user_id";

    @Override
    public int getV4Meaning() {
      return MEANING_PREDEFINED_ENTITY_USER;
    }

    @Override
    public EntityV4.Entity getEntity(Object value) {
      User user = (User) value;
      EntityV4.Entity.Builder builder = EntityV4.Entity.newBuilder();
      builder.addProperty(makeUnindexedProperty(PROPERTY_NAME_EMAIL, user.getEmail()));
      builder.addProperty(makeUnindexedProperty(PROPERTY_NAME_AUTH_DOMAIN, user.getAuthDomain()));
      if (user.getUserId() != null) {
        builder.addProperty(makeUnindexedProperty(PROPERTY_NAME_USER_ID, user.getUserId()));
      }
      return builder.build();
    }

    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      User user = (User) value;
      UserValue userValue = new UserValue();
      userValue.setEmail(user.getEmail());
      userValue.setAuthDomain(user.getAuthDomain());
      if (user.getUserId() != null) {
        userValue.setObfuscatedGaiaid(user.getUserId());
      }
      userValue.setGaiaid(0);
      propertyValue.setUserValue(userValue);
      return true;
    }

    @Override
    public User getValue(PropertyValue propertyValue) {
      UserValue userValue = propertyValue.getUserValue();
      String userId = userValue.hasObfuscatedGaiaid() ? userValue.getObfuscatedGaiaid() : null;
      return new User(userValue.getEmail(), userValue.getAuthDomain(), userId);
    }

    @Override
    public User getValue(EntityV4.ValueOrBuilder propertyValue) {
      String email = "";
      String authDomain = "";
      String userId = null;
      for (EntityV4.PropertyOrBuilder prop :
          propertyValue.getEntityValueOrBuilder().getPropertyOrBuilderList()) {
        if (prop.getName().equals(PROPERTY_NAME_EMAIL)) {
          email = prop.getValueOrBuilder().getStringValue();
        } else if (prop.getName().equals(PROPERTY_NAME_AUTH_DOMAIN)) {
          authDomain = prop.getValueOrBuilder().getStringValue();
        } else if (prop.getName().equals(PROPERTY_NAME_USER_ID)) {
          userId = prop.getValueOrBuilder().getStringValue();
        }
      }
      return new User(email, authDomain, userId);
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasUserValue();
    }

    @Override
    public final Comparable<User> asComparable(Object value) {
      return (User) value;
    }
  }

  /**
   * The GeoPt type.
   *
   * Stored as a GeoPoint value with no meaning in V4.
   *
   * TODO(user): The above comment isn't completely accurate until we have switched the
   * internal format to V1BETA3.
   */
  private static class GeoPtType extends Type<GeoPt> {
    public static final int MEANING_PREDEFINED_ENTITY_GEORSS_POINT = 9;
    public static final String PROPERTY_NAME_X = "x";
    public static final String PROPERTY_NAME_Y = "y";

    @Override
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      if (propertyValue.hasGeoPointValue() && !propertyValue.hasMeaning()) {
        return true;
      } else if (propertyValue.hasEntityValue()
          && propertyValue.getMeaning() == MEANING_PREDEFINED_ENTITY_GEORSS_POINT) {
        return true;
      } else {
        return false;
      }
    }

    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      GeoPt geoPt = (GeoPt) value;
      PropertyValue.PointValue pv = new PropertyValue.PointValue()
          .setX(geoPt.getLatitude())
          .setY(geoPt.getLongitude());
      propertyValue.setPointValue(pv);
      return true;
    }

    @Override
    public final EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      GeoPt geoPt = (GeoPt) value;
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.getEntityValueBuilder()
          .addProperty(makeUnindexedProperty(PROPERTY_NAME_X, geoPt.getLatitude()))
          .addProperty(makeUnindexedProperty(PROPERTY_NAME_Y, geoPt.getLongitude()))
          .build();
      setIndexed(indexed, builder);
      setMeaning(MEANING_PREDEFINED_ENTITY_GEORSS_POINT, builder);
      return builder;
    }

    @Override
    public GeoPt getValue(PropertyValue propertyValue) {
      PropertyValue.PointValue pv = propertyValue.getPointValue();
      return new GeoPt((float) pv.getX(), (float) pv.getY());
    }

    @Override
    public GeoPt getValue(EntityV4.ValueOrBuilder propertyValue) {
      double x = 0;
      double y = 0;
      for (EntityV4.PropertyOrBuilder prop :
          propertyValue.getEntityValueOrBuilder().getPropertyOrBuilderList()) {
        if (prop.getName().equals(PROPERTY_NAME_X)) {
          x = prop.getValueOrBuilder().getDoubleValue();
        } else if (prop.getName().equals(PROPERTY_NAME_Y)) {
          y = prop.getValueOrBuilder().getDoubleValue();
        }
      }
      if (propertyValue.hasGeoPointValue()) {
        x = propertyValue.getGeoPointValue().getLatitude();
        y = propertyValue.getGeoPointValue().getLongitude();
      }
      return new GeoPt((float) x, (float) y);
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasPointValue();
    }

    @Override
    public final boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasGeoPointValue() || propertyValue.hasEntityValue();
    }

    @Override
    public Meaning getV3Meaning() {
      return Meaning.GEORSS_POINT;
    }

    @Override
    public final Comparable<GeoPt> asComparable(Object value) {
      return (GeoPt) value;
    }
  }

  /**
   * The key/reference type.
   */
  private static final class KeyType extends Type<Key> {
    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      Reference keyRef = KeyTranslator.convertToPb((Key) value);
      propertyValue.setReferenceValue(toReferenceValue(keyRef));
      return true;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setKeyValue(toV4Key((Key) value));
      setIndexed(indexed, builder);
      return builder;
    }

    @Override
    public Key getValue(PropertyValue propertyValue) {
      return KeyTranslator.createFromPb(toReference(propertyValue.getReferenceValue()));
    }

    @Override
    public Key getValue(EntityV4.ValueOrBuilder propertyValue) {
      return toKey(propertyValue.getKeyValue());
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasReferenceValue();
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasKeyValue();
    }

    @Override
    public Key asComparable(Object value) {
      return (Key) value;
    }

    private static ReferenceValue toReferenceValue(Reference keyRef) {
      ReferenceValue refValue = new ReferenceValue();
      refValue.setApp(keyRef.getApp());
      if (keyRef.hasNameSpace()) {
        refValue.setNameSpace(keyRef.getNameSpace());
      }
      Path path = keyRef.getPath();
      for (Element element : path.elements()) {
        ReferenceValuePathElement newElement = new ReferenceValuePathElement();
        newElement.setType(element.getType());
        if (element.hasName()) {
          newElement.setName(element.getName());
        }
        if (element.hasId()) {
          newElement.setId(element.getId());
        }
        refValue.addPathElement(newElement);
      }

      return refValue;
    }

    private static Reference toReference(ReferenceValue refValue) {
      Reference reference = new Reference();
      reference.setApp(refValue.getApp());
      if (refValue.hasNameSpace()) {
        reference.setNameSpace(refValue.getNameSpace());
      }
      Path path = new Path();
      for (ReferenceValuePathElement element : refValue.pathElements()) {
        Element newElement = new Element();
        newElement.setType(element.getType());
        if (element.hasName()) {
          newElement.setName(element.getName());
        }
        if (element.hasId()) {
          newElement.setId(element.getId());
        }
        path.addElement(newElement);
      }
      reference.setPath(path);
      return reference;
    }
  }

  /**
   * The non-indexable blob type.
   */
  private static class BlobType extends BaseBlobType<Blob> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.BLOB;
    }

    @Override
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      return getV3MeaningOf(propertyValue) == Meaning.NO_MEANING
          && propertyValue.getIndexed() == false
          && hasValue(propertyValue);
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasBlobValue();
    }

    @Override
    public Blob getValue(EntityV4.ValueOrBuilder propertyValue) {
      return fromDatastoreValue(propertyValue.getBlobValue().toByteArray());
    }

    @Override
    protected Blob fromDatastoreValue(byte[] datastoreValue) {
      return new Blob(datastoreValue);
    }

    @Override
    protected byte[] toDatastoreValue(Object value) {
      return ((Blob) value).getBytes();
    }

    @Override
    public boolean isIndexable() {
      return false;
    }
  }

  /**
   * The indexable blob type.
   */
  private static class ShortBlobType extends BaseBlobType<ShortBlob> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.BYTESTRING;
    }

    @Override
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      if (!hasValue(propertyValue)) {
        return false;
      }

      if (propertyValue.getIndexed()) {
        return getV3MeaningOf(propertyValue) == Meaning.NO_MEANING;
      } else {
        return getV3MeaningOf(propertyValue) == getV3Meaning();
      }
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = super.toV4Value(value, indexed);
      if (!indexed) {
        builder.setMeaning(getV3Meaning().getValue());
      }
      return builder;
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasBlobValue()
          || (getV3MeaningOf(propertyValue) == Meaning.INDEX_VALUE
              && propertyValue.hasStringValue());
    }

    @Override
    public ShortBlob getValue(EntityV4.ValueOrBuilder propertyValue) {
      if (getV3MeaningOf(propertyValue) == Meaning.INDEX_VALUE && propertyValue.hasStringValue()) {
        return fromDatastoreValue(propertyValue.getStringValueBytes().toByteArray());
      } else {
        return fromDatastoreValue(propertyValue.getBlobValue().toByteArray());
      }
    }

    @Override
    protected byte[] toDatastoreValue(Object value) {
      return ((ShortBlob) value).getBytes();
    }

    @Override
    protected ShortBlob fromDatastoreValue(byte[] datastoreValue) {
      return new ShortBlob(datastoreValue);
    }

    @Override
    public boolean isIndexable() {
      return true;
    }
  }

  /**
   * The entity type.
   *
   * Stored as a partially serialized EntityProto in V3.
   */
  private static final class EmbeddedEntityType extends Type<EmbeddedEntity> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.ENTITY_PROTO;
    }

     @Override
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      return getV3MeaningOf(propertyValue) == Meaning.NO_MEANING
          && hasValue(propertyValue);
    }

    @Override
    public boolean hasValue(PropertyValue propertyValue) {
      return propertyValue.hasStringValue();
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasEntityValue();
    }

    @Override
    public EmbeddedEntity getValue(PropertyValue propertyValue) {
      EntityProto proto = new EntityProto();
      proto.mergeFrom(propertyValue.getStringValueAsBytes());
      EmbeddedEntity result = new EmbeddedEntity();
      if (proto.hasKey() && !proto.getKey().getApp().isEmpty()) {
        result.setKey(KeyTranslator.createFromPb(proto.getKey()));
      }
      extractPropertiesFromPb(proto, result.getPropertyMap());
      return result;
    }

    @Override
    public EmbeddedEntity getValue(EntityV4.ValueOrBuilder propertyValue) {
      EmbeddedEntity result = new EmbeddedEntity();
      EntityV4.Entity proto = propertyValue.getEntityValue();
      if (proto.hasKey()) {
        result.setKey(toKey(proto.getKey()));
      }
      extractPropertiesFromPb(proto, false, result.getPropertyMap());
      return result;
    }

    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      EmbeddedEntity structProp = (EmbeddedEntity) value;
      EntityProto proto = new EntityProto();
      if (structProp.getKey() != null) {
        proto.setKey(KeyTranslator.convertToPb(structProp.getKey()));
      }
      addPropertiesToPb(structProp.getPropertyMap(), proto);
      propertyValue.setStringValueAsBytes(proto.toByteArray());
      return false;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EmbeddedEntity structProp = (EmbeddedEntity) value;
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      EntityV4.Entity.Builder proto = builder.getEntityValueBuilder();
      if (structProp.getKey() != null) {
        proto.setKey(toV4Key(structProp.getKey()));
      }
      addPropertiesToPb(structProp.getPropertyMap(), proto);
      builder.setIndexed(false);
      return builder;
    }

    @Override
    public Comparable<?> asComparable(Object value) {
      return null;
    }
  }

  /**
   * The non-indexable {@link Text} type.
   */
  private static final class TextType extends BaseStringType<Text> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.TEXT;
    }

    @Override
    public boolean toV3Value(Object value, PropertyValue propertyValue) {
      super.toV3Value(value, propertyValue);
      return false;
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      return super.toV4Value(value, false);
    }

    @Override
    protected Text fromDatastoreValue(String datastoreString) {
      return new Text(datastoreString);
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((Text) value).getValue();
    }

    @Override
    public ComparableByteArray asComparable(Object value) {
      return null;
    }
  }

  /**
   * The {@link BlobKey} type.
   *
   * In V3 dates are just strings with a special meaning.
   */
  private static final class BlobKeyType extends BaseStringType<BlobKey> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.BLOBKEY;
    }

    @Override
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      return getV3MeaningOf(propertyValue) == Meaning.NO_MEANING
          && hasValue(propertyValue);
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasBlobKeyValue()
          || (getV3MeaningOf(propertyValue) == Meaning.INDEX_VALUE
              && propertyValue.hasStringValue());
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setBlobKeyValue(toDatastoreValue(value));
      setIndexed(indexed, builder);
      return builder;
    }

    @Override
    public BlobKey getValue(EntityV4.ValueOrBuilder propertyValue) {
      if (getV3MeaningOf(propertyValue) == Meaning.INDEX_VALUE && propertyValue.hasStringValue()) {
        return fromDatastoreValue(propertyValue.getStringValue());
      } else {
        return fromDatastoreValue(propertyValue.getBlobKeyValue());
      }
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((BlobKey) value).getKeyString();
    }

    @Override
    protected BlobKey fromDatastoreValue(String datastoreString) {
      return new BlobKey(datastoreString);
    }
  }

  /**
   * The date type.
   *
   * In V3 dates are just int64s with a special meaning.
   */
  private static final class DateType extends BaseInt64Type<Date> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.GD_WHEN;
    }

    @Override
    public boolean isType(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.getMeaning() == 0 && hasValue(propertyValue);
    }

    @Override
    public boolean hasValue(EntityV4.ValueOrBuilder propertyValue) {
      return propertyValue.hasTimestampMicrosecondsValue()
          || (getV3MeaningOf(propertyValue) == Meaning.INDEX_VALUE
              && propertyValue.hasIntegerValue());
    }

    @Override
    public EntityV4.Value.Builder toV4Value(Object value, boolean indexed) {
      EntityV4.Value.Builder builder = EntityV4.Value.newBuilder();
      builder.setTimestampMicrosecondsValue(toDatastoreValue(value));
      setIndexed(indexed, builder);
      return builder;
    }

    @Override
    public Date getValue(EntityV4.ValueOrBuilder propertyValue) {
      if (getV3MeaningOf(propertyValue) == Meaning.INDEX_VALUE && propertyValue.hasIntegerValue()) {
        return fromDatastoreValue(propertyValue.getIntegerValue());
      } else {
        return fromDatastoreValue(propertyValue.getTimestampMicrosecondsValue());
      }
    }

    @Override
    protected Long toDatastoreValue(Object value) {
      return ((Date) value).getTime() * 1000L;
    }

    @Override
    protected Date fromDatastoreValue(Long datastoreValue) {
      return new Date(datastoreValue / 1000L);
    }
  }

  /**
   * Internally a link is just a string with a special meaning.
   */
  private static final class LinkType extends BaseStringType<Link> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.ATOM_LINK;
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((Link) value).getValue();
    }

    @Override
    protected Link fromDatastoreValue(String datastoreValue) {
      return new Link(datastoreValue);
    }
  }

  /**
   * Internally a category is just a string with a special meaning.
   */
  private static final class CategoryType extends BaseStringType<Category> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.ATOM_CATEGORY;
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((Category) value).getCategory();
    }

    @Override
    protected Category fromDatastoreValue(String datastoreString) {
      return new Category(datastoreString);
    }
  }

  /**
   * Internally a rating is just an int64 with a special meaning.
   */
  private static final class RatingType extends BaseInt64Type<Rating> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.GD_RATING;
    }

    @Override
    protected Long toDatastoreValue(Object value) {
      return (long) ((Rating) value).getRating();
    }

    @Override
    protected Rating fromDatastoreValue(Long datastoreLong) {
      return new Rating(datastoreLong.intValue());
    }
  }

  /**
   * Internally an email is just a string with a special meaning.
   */
  private static final class EmailType extends BaseStringType<Email> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.GD_EMAIL;
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((Email) value).getEmail();
    }

    @Override
    protected Email fromDatastoreValue(String datastoreString) {
      return new Email(datastoreString);
    }
  }

  /**
   * Internally a postal address is just a string with a special meaning.
   */
  private static final class PostalAddressType extends BaseStringType<PostalAddress> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.GD_POSTALADDRESS;
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((PostalAddress) value).getAddress();
    }

    @Override
    protected PostalAddress fromDatastoreValue(String datastoreString) {
      return new PostalAddress(datastoreString);
    }
  }

  /**
   * Internally a phone number is just a string with a special meaning.
   */
  private static final class PhoneNumberType extends BaseStringType<PhoneNumber> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.GD_PHONENUMBER;
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((PhoneNumber) value).getNumber();
    }

    @Override
    protected PhoneNumber fromDatastoreValue(String datastoreString) {
      return new PhoneNumber(datastoreString);
    }
  }

  /**
   * Internally an IM handle is just a string with a special meaning and a
   * well known format.
   */
  private static final class IMHandleType extends BaseStringType<IMHandle> {
    @Override
    public Meaning getV3Meaning() {
      return Meaning.GD_IM;
    }

    @Override
    protected String toDatastoreValue(Object value) {
      return ((IMHandle) value).toDatastoreString();
    }

    @Override
    protected IMHandle fromDatastoreValue(String datastoreString) {
      return IMHandle.fromDatastoreString(datastoreString);
    }
  }

  static Map<Class<?>, Type<?>> getTypeMap() {
    return typeMap;
  }

  /**
   * A wrapper for a {@code byte[]} that implements {@link Comparable}.
   * Comparison algorithm is the same as the prod datastore.
   */
  public static final class ComparableByteArray implements Comparable<ComparableByteArray> {
    private final byte[] bytes;

    public ComparableByteArray(byte[] bytes) {
      this.bytes = bytes;
    }

    @Override
    public int compareTo(ComparableByteArray other) {
      byte[] otherBytes = other.bytes;
      for (int i = 0; i < Math.min(bytes.length, otherBytes.length); i++) {
        int v1 = bytes[i] & 0xFF;
        int v2 = otherBytes[i] & 0xFF;
        if (v1 != v2) {
          return v1 - v2;
        }
      }
      return bytes.length - otherBytes.length;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == null) {
        return false;
      }
      return Arrays.equals(bytes, ((ComparableByteArray) obj).bytes);
    }

    @Override
    public int hashCode() {
      int result = 1;
      for (byte b : bytes) {
        result = 31 * result + b;
      }
      return result;
    }
  }

  /**
   * Helper function to only assign the value if it is not already the default.
   */
  private static void setIndexed(boolean indexed, EntityV4.Value.Builder builder) {
    if (indexed != builder.getIndexed()) {
      builder.setIndexed(indexed);
    }
  }

  /**
   * Helper function to only assign the value if it is not already the default.
   */
  private static void setMeaning(int meaning, EntityV4.Value.Builder builder) {
    if (meaning != builder.getMeaning()) {
      builder.setMeaning(meaning);
    }
  }

  private DataTypeTranslator() {
  }
}
TOP

Related Classes of com.google.appengine.api.datastore.DataTypeTranslator$DateType

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.