Package com.google.appengine.api.datastore

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

// 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.common.collect.Maps;
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 StringType STRING_TYPE = new StringType();

  /**
   * 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, new RawValueType());

    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, STRING_TYPE);
    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 ReferenceType());
    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 AsComparableFunction} 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 key = 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()) {
          addProperty(proto, key, null, indexed, false);
        } else {
          for (Object listValue : values) {
            addProperty(proto, key, listValue, indexed, true);
          }
        }
      } else {
        addProperty(proto, key, value, indexed, false);
      }
    }
  }

  /**
   * Adds a property to {@code entity}.
   *
   * @param entity a not {@code null} {@code EntityProto}
   * @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
   */
  private static void addProperty(EntityProto entity, String name, Object value,
                                  boolean indexed, boolean multiple) {
    Pair<Type<?>, Property> pair = createProperty(name, value, multiple);
    Type<?> type = pair.first();
    Property property = pair.second();

    if (!indexed || (type != null && type.getComparableFunction() == NOT_COMPARABLE)) {
      entity.addRawProperty(property);
    } else {
      entity.addProperty(property);
    }
  }

  /**
   * Creates a new {@link Property} given its {@code name}, {@code value},
   * and {@code multiplicity}.
   *
   * @param name The name used as a key
   * @param value The value for the Property
   * @param multiple true iff there are also other Properties with the same name
   *
   * @return a not {@code null} Pair<Type, Property>. The {@code Type} will be null
   * iff {@code value} is null. {@code Property} will not be null.
   */
  private static Pair<Type<?>, Property> createProperty(String name, Object value,
      boolean multiple) {
    Property property = new Property();
    property.setName(name);
    property.setMultiple(multiple);

    if (value == null) {
      return Pair.of(null, property);
    }

    Pair<Type<?>, PropertyValue> newValue = createPropertyValue(value);
    Type<?> type = newValue.first;
    Meaning meaning = type.getMeaning();
    if (meaning != Meaning.NO_MEANING) {
      property.setMeaning(meaning);
    }
    property.setValue(newValue.second);
    return new Pair<Type<?>, Property>(type, property);
  }

  private static Pair<Type<?>, PropertyValue> createPropertyValue(Object value) {
    if (value == null) {
      return Pair.of(null, null);
    }
    PropertyValue newValue = new PropertyValue();
    Type<?> type = getType(value.getClass());
    type.setPropertyValue(newValue, value);
    return new Pair<Type<?>, PropertyValue>(type, newValue);
  }

  /**
   * 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()) {
      addPropertyValueToMap(property, map, true);
    }
  }

  /**
   * 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()) {
      addPropertyValueToMap(property, map, false);
    }
  }

  /**
   * 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)) {
      addPropertyValueToMap(property, map, true);
    }
  }

  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();
    ReferenceType.setPropertyValue(propVal, proto.getKey());
    keyProp.setValue(propVal);
    return keyProp;
  }

  /**
   * Locates and returns all indexed properties with the given name on the
   * given proto.
   */
  public static Collection<Property> findIndexedPropertiesOnPb(
      EntityProto proto, String propertyName) {
    if (propertyName.equals(Entity.KEY_RESERVED_PROPERTY)) {
      return Collections.singleton(buildImplicitKeyProperty(proto));
    }
    Collection<Property> multipleProps = new ArrayList<Property>();
    Property singleProp = addPropertiesWithName(proto.propertys(), propertyName, multipleProps);
    if (singleProp != null) {
      return Collections.singleton(singleProp);
    }
    return multipleProps;
  }

  /**
   * Helper method that returns the first non-multiple property with the given name.
   * If a multiple property with the given name is encountered it is added to the
   * collection provided as an argument.
   *
   * @return A non-multiple property with the given name, or {@code null} if no
   * such property was found or all properties found were multiple properties.
   * If all properties found were multiple properties, the collection provided
   * as an argument will contain all these properties.
   */
  private static Property addPropertiesWithName(
      Iterable<Property> props, String propName, Collection<Property> matchingMultipleProps) {
    for (Property prop : props) {
      if (prop.getName().equals(propName)) {
        if (!prop.isMultiple()) {
          return prop;
        } else {
          matchingMultipleProps.add(prop);
        }
      }
    }
    return null;
  }

  private static void addPropertyValueToMap(
      Property property, Map<String, Object> map, boolean indexed) {
    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.hasPropertyValue(value) &&
          type.getMeaning() == property.getMeaningEnum()) {
        return type.getPropertyValue(value);
      }
    }
    return null;
  }

  /**
   * 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}
   */
  public static Comparable<Object> getComparablePropertyValue(Property property) {
    PropertyValue value = property.getValue();
    Meaning meaning = property.getMeaningEnum();
    for (Type<?> type : typeMap.values()) {
      if (type.hasPropertyValue(value) &&
          (meaning == Meaning.INDEX_VALUE || meaning == type.getMeaning()) &&
          type.getComparableFunction() != null) {
        return toComparableObject(
            type.getComparableFunction().asComparable(value));
      }
    }
    return null;
  }

  /**
   * 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
   */
  static Comparable<Object> getComparablePropertyValue(Object value) {
    Pair<Type<?>, PropertyValue> newValue = createPropertyValue(value);
    if (newValue.first != null) {
        return toComparableObject(
            newValue.first.getComparableFunction().asComparable(newValue.second));
    }
    return null;
  }

  /**
   * Isolating the warning suppression.
   */
  @SuppressWarnings("unchecked")
  private static <T> Comparable<Object> toComparableObject(final Comparable<T> original) {
    return (Comparable<Object>) original;
  }

  /**
   * 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);
  }

  /**
   * Create a Property protobuffer that contains {@code propertyName}
   * and {@code value}.
   */
  static Property toProperty(String propertyName, Object value) {
    return createProperty(propertyName, value, false).second();
  }

  /**
   * 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());
    }
  }

  /**
   * A function that converts a {@link PropertyValue} to its {@link
   * Comparable} representation.
   */
  private interface AsComparableFunction {
    Comparable<?> asComparable(PropertyValue pv);
  }

  /**
   * {@code Type} is an abstract class that knows how to convert Java
   * objects of one or more types into a {@link PropertyValue}.
   *
   * @param <T> The canonical Java class for this type.
   */
  abstract static class Type<T> {

    private final Meaning meaning;

    protected Type(Meaning meaning) {
      this.meaning = meaning;
    }

    protected Type() {
      this(Meaning.NO_MEANING);
    }

    /**
     * Returns the {@link AsComparableFunction} for this {@code Type}, or
     * {@code null} if values of this type are not comparable.
     */
    public abstract AsComparableFunction getComparableFunction();

    /**
     * Returns the {@link Meaning} for this {@code Type}.
     *
     * @return A return value of {@link Meaning#NO_MEANING} indicates
     * there is no special meaning.
     */
    public Meaning getMeaning() {
      return meaning;
    }

    /**
     * Sets the value of {@code propertyValue} to {@code value}.
     * If {@code value} is null, and the type of its {@link PropertyValue}
     * attribute is primitive, then no update occurs.
     */
    public abstract void setPropertyValue(PropertyValue propertyValue, Object value);

    /**
     * Returns the value of {@code propertyValue} as its canonical Java type.
     *
     * @param propertyValue a not {@code null} value representing this {@code Type}
     * @return the standard representation of {@code value}. Will never be null
     * for attributes of a primitive type.
     * @throws NullPointerException if the property value doesn't exist.
     * Use {@link #hasPropertyValue} first to determine if the property exists.
     */
    public abstract T getPropertyValue(PropertyValue propertyValue);

    /**
     * Returns true if the property value exists.
     *
     * @param propertyValue a not {code null} property.
     * @return true iff the property value has been set.
     */
    public abstract boolean hasPropertyValue(PropertyValue propertyValue);
  }

  /**
   * Used by {@link Type}s that do not support comparison.
   */
  private static final AsComparableFunction NOT_COMPARABLE = null;

  /**
   * Converts a {@link PropertyValue} stored in the string field to a
   * {@link Comparable}.
   */
  private static final AsComparableFunction COMP_BYTE_ARRAY_FUNC = new AsComparableFunction() {
    @Override
    public Comparable<ComparableByteArray> asComparable(PropertyValue pv) {
      return new ComparableByteArray(pv.getStringValueAsBytes());
    }
  };

  private static class StringType extends Type<String> {

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      propertyValue.setStringValue((String) value);
    }

    @Override
    public String getPropertyValue(PropertyValue propertyValue) {
      return propertyValue.getStringValue();
    }

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return COMP_BYTE_ARRAY_FUNC;
    }
  }

  /**
   * Converts a {@link PropertyValue} stored in the string field to a
   * {@link Comparable}.
   */
  private static final AsComparableFunction COMP_RAW_VALUE_FUNC = new AsComparableFunction() {
    @SuppressWarnings("unchecked")
    @Override
    public Comparable<?> asComparable(PropertyValue pv) {
      if (pv.hasStringValue()) {
        return new ComparableByteArray(pv.getStringValueAsBytes());
      }
      return (Comparable<Object>) new RawValue(pv).getValue();
    }
  };

  private static class RawValueType extends Type<RawValue> {

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

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return COMP_RAW_VALUE_FUNC;
    }

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

    @Override
    public Meaning getMeaning() {
      return Meaning.INDEX_VALUE;
    }
  }

  /**
   * Converts a {@link PropertyValue} stored in the int64 field to a
   * {@link Comparable}.
   */
  private static final AsComparableFunction INT_64_COMP_FUNC = new AsComparableFunction() {
    @Override
    public Comparable<Long> asComparable(PropertyValue pv) {
      return pv.getInt64Value();
    }
  };

  private static class Int64Type extends Type<Long> {

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      if (value != null) {
        propertyValue.setInt64Value(((Number) value).longValue());
      }
    }

    @Override
    public Long getPropertyValue(PropertyValue propertyValue) {
      return propertyValue.getInt64Value();
    }

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return INT_64_COMP_FUNC;
    }
  }

  /**
   * Converts a {@link PropertyValue} stored in the double field to a
   * {@link Comparable}.
   */
  private static final AsComparableFunction DOUBLE_COMP_FUNC = new AsComparableFunction() {
    @Override
    public Comparable<Double> asComparable(PropertyValue pv) {
      return pv.getDoubleValue();
    }
  };

  private static class DoubleType extends Type<Double> {

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      if (value != null) {
        propertyValue.setDoubleValue(((Number) value).doubleValue());
      }
    }

    @Override
    public Double getPropertyValue(PropertyValue propertyValue) {
      return propertyValue.hasDoubleValue() ? propertyValue.getDoubleValue() : null;
    }

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return DOUBLE_COMP_FUNC;
    }
  }

  /**
   * Converts a {@link PropertyValue} stored in the boolean field to a
   * {@link Comparable}.
   */
  private static final AsComparableFunction BOOLEAN_COMP_FUNC = new AsComparableFunction() {
    @Override
    public Comparable<Boolean> asComparable(PropertyValue pv) {
      return pv.isBooleanValue();
    }
  };

  private static class BoolType extends Type<Boolean> {

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      if (value != null) {
        propertyValue.setBooleanValue((Boolean) value);
      }
    }

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

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return BOOLEAN_COMP_FUNC;
    }
  }

  private static class UserType extends Type<User> {

    private final AsComparableFunction USER_COMP_FUNC = new AsComparableFunction() {
      @Override
      public Comparable<User> asComparable(PropertyValue pv) {
        return getPropertyValue(pv);
      }
    };

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      User user = (User) value;
      UserValue userValue = new UserValue();
      userValue.setEmail(user.getEmail());
      userValue.setAuthDomain(user.getAuthDomain());
      userValue.setGaiaid(0);
      propertyValue.setUserValue(userValue);
    }

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

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return USER_COMP_FUNC;
    }
  }

  private static class ReferenceType extends Type<Key> {
    private final AsComparableFunction COMP_FUNC = new AsComparableFunction() {
      @Override
      public Comparable<Key> asComparable(PropertyValue pv) {
        return getPropertyValue(pv);
      }
    };

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      Reference keyRef = KeyTranslator.convertToPb((Key) value);
      setPropertyValue(propertyValue, keyRef);
    }

    private static void setPropertyValue(PropertyValue propertyValue, 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);
      }

      propertyValue.setReferenceValue(refValue);
    }

    @Override
    public Key getPropertyValue(PropertyValue propertyValue) {

      Reference reference = new Reference();
      ReferenceValue refValue = propertyValue.getReferenceValue();
      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 KeyTranslator.createFromPb(reference);
    }

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return COMP_FUNC;
    }
  }

  private static class BlobType extends Type<Blob> {
    private BlobType() {
      super(Meaning.BLOB);
    }

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      Blob blob = (Blob) value;
      propertyValue.setStringValueAsBytes(blob.getBytes());
    }

    @Override
    public Blob getPropertyValue(PropertyValue propertyValue) {
      byte[] bytes = propertyValue.getStringValueAsBytes();
      return new Blob(bytes);
    }

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return NOT_COMPARABLE;
    }
  }

  private static class EmbeddedEntityType extends Type<EmbeddedEntity> {
    private EmbeddedEntityType() {
      super(Meaning.ENTITY_PROTO);
    }

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      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());
    }

    @Override
    public EmbeddedEntity getPropertyValue(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 boolean hasPropertyValue(PropertyValue propertyValue) {
      return propertyValue.hasStringValue();
    }

    @Override
    public AsComparableFunction getComparableFunction() {
      return NOT_COMPARABLE;
    }
  }

  private static class TextType extends Type<Text> {
    private TextType() {
      super(Meaning.TEXT);
    }

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      Text text = (Text) value;
      propertyValue.setStringValue(text.getValue());
    }

    @Override
    public Text getPropertyValue(PropertyValue propertyValue) {
      return new Text(propertyValue.getStringValue());
    }

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

    @Override
    public AsComparableFunction getComparableFunction() {
      return NOT_COMPARABLE;
    }
  }

  private static class BlobKeyType extends CustomStringType<BlobKey> {
    private BlobKeyType() {
      super(Meaning.BLOBKEY);
    }

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

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

  private static class DateType extends Type<Date> {
    private DateType() {
      super(Meaning.GD_WHEN);
    }

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      Date date = (Date) value;
      propertyValue.setInt64Value(date.getTime() * 1000L);
    }

    @Override
    public Date getPropertyValue(PropertyValue propertyValue) {
      return new Date(propertyValue.getInt64Value() / 1000L);
    }

    @Override
    public AsComparableFunction getComparableFunction() {
      return INT_64_COMP_FUNC;
    }

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

  private static class LinkType extends Type<Link> {
    private LinkType() {
      super(Meaning.ATOM_LINK);
    }

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      Link link = (Link) value;
      propertyValue.setStringValue(link.getValue());
    }

    @Override
    public Link getPropertyValue(PropertyValue propertyValue) {
      return new Link(propertyValue.getStringValue());
    }

    @Override
    public AsComparableFunction getComparableFunction() {
      return COMP_BYTE_ARRAY_FUNC;
    }

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

  private static class ShortBlobType extends Type<ShortBlob> {
    private ShortBlobType() {
      super(Meaning.BYTESTRING);
    }

    @Override
    public void setPropertyValue(PropertyValue propertyValue, Object value) {
      propertyValue.setStringValueAsBytes(((ShortBlob) value).getBytes());
    }

    @Override
    public ShortBlob getPropertyValue(PropertyValue propertyValue) {
      return new ShortBlob(propertyValue.getStringValueAsBytes());
    }

    @Override
    public AsComparableFunction getComparableFunction() {
      return COMP_BYTE_ARRAY_FUNC;
    }

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

  /**
   * Base class for types that are basically just string wrappers.
   *
   * Delegates to a StringType instance because the parameterized type
   * prevents us from extending StringType.
   */
  private static abstract class CustomStringType<T> extends Type<T> {

    private CustomStringType(Meaning meaning) {
      super(meaning);
    }

    @Override
    public final void setPropertyValue(PropertyValue propertyValue, Object value) {
      STRING_TYPE.setPropertyValue(propertyValue, asDatastoreString(value));
    }

    @Override
    public final T getPropertyValue(PropertyValue propertyValue) {
      return fromDatastoreString(STRING_TYPE.getPropertyValue(propertyValue));
    }

    @Override
    public final AsComparableFunction getComparableFunction() {
      return STRING_TYPE.getComparableFunction();
    }

    @Override
    public final boolean hasPropertyValue(PropertyValue propertyValue) {
      return STRING_TYPE.hasPropertyValue(propertyValue);
    }

    protected abstract String asDatastoreString(Object value);
    protected abstract T fromDatastoreString(String datastoreString);
  }

  /**
   * Base class for types that are basically just int64 wrappers.
   *
   * Delegates to a Int64Type instance because the parameterized type
   * prevents us from extending Int64Type.
   */
  private static abstract class CustomInt64Type<T> extends Type<T> {

    private static final Int64Type INT64_TYPE = new Int64Type();

    private CustomInt64Type(Meaning meaning) {
      super(meaning);
    }

    @Override
    public final void setPropertyValue(PropertyValue propertyValue, Object value) {
      INT64_TYPE.setPropertyValue(propertyValue, asDatastoreLong(value));
    }

    @Override
    public final T getPropertyValue(PropertyValue propertyValue) {
      return fromDatastoreLong(INT64_TYPE.getPropertyValue(propertyValue));
    }

    @Override
    public final AsComparableFunction getComparableFunction() {
      return INT64_TYPE.getComparableFunction();
    }

    @Override
    public final boolean hasPropertyValue(PropertyValue propertyValue) {
      return INT64_TYPE.hasPropertyValue(propertyValue);
    }

    protected abstract Long asDatastoreLong(Object value);
    protected abstract T fromDatastoreLong(Long datastoreLong);
  }

  /**
   * Internally a category is just a string with a special meaning.
   */
  private static class CategoryType extends CustomStringType<Category> {
    private CategoryType() {
      super(Meaning.ATOM_CATEGORY);
    }

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

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

  /**
   * Internally a category is just an int64 with a special meaning.
   */
  private static class RatingType extends CustomInt64Type<Rating> {

    private RatingType() {
      super(Meaning.GD_RATING);
    }

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

    @Override
    protected Rating fromDatastoreLong(Long datastoreLong) {
      if (datastoreLong == null) {
        throw new NullPointerException("rating value cannot be null");
      }
      return new Rating(datastoreLong.intValue());
    }
  }

  /**
   * Internally an email is just a string with a special meaning.
   */
  private static class EmailType extends CustomStringType<Email> {
    private EmailType() {
      super(Meaning.GD_EMAIL);
    }

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

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

  /**
   * Internally a postal address is just a string with a special meaning.
   */
  private static class PostalAddressType extends CustomStringType<PostalAddress> {
    private PostalAddressType() {
      super(Meaning.GD_POSTALADDRESS);
    }

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

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

  /**
   * Internally a phone number is just a string with a special meaning.
   */
  private static class PhoneNumberType extends CustomStringType<PhoneNumber> {
    private PhoneNumberType() {
      super(Meaning.GD_PHONENUMBER);
    }

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

    @Override
    protected PhoneNumber fromDatastoreString(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 class IMHandleType extends CustomStringType<IMHandle> {

    private IMHandleType() {
      super(Meaning.GD_IM);
    }

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

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

  private static class GeoPtType extends Type<GeoPt> {

    private GeoPtType() {
      super(Meaning.GEORSS_POINT);
    }

    /**
     * Converts a {@link PropertyValue} stored in the pointValue field to a
     * {@link Comparable}.
     */
    private final AsComparableFunction POINT_VALUE_COMP_FUNC = new AsComparableFunction() {
      @Override
      public Comparable<GeoPt> asComparable(PropertyValue pv) {
        return getPropertyValue(pv);
      }
    };

    @Override
    public AsComparableFunction getComparableFunction() {
      return POINT_VALUE_COMP_FUNC;
    }

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

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

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

  private static class Pair<T, U> {

    private final T first;

    private final U second;

    Pair(T t, U u) {
      first = t;
      second = u;
    }

    T first() {
      return first;
    }

    U second() {
      return second;
    }

    public static <T, U> Pair<T, U> of(T t, U u) {
      return new Pair<T, U>(t, u);
    }
  }

  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;
    }
  }

  private DataTypeTranslator() {
  }
}
TOP

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

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.