Package com.google.code.gaeom.impl

Source Code of com.google.code.gaeom.impl.ObjectEncoder$FieldEntry

package com.google.code.gaeom.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.ConvertUtils;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Category;
import com.google.appengine.api.datastore.Email;
import com.google.appengine.api.datastore.GeoPt;
import com.google.appengine.api.datastore.IMHandle;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Link;
import com.google.appengine.api.datastore.PhoneNumber;
import com.google.appengine.api.datastore.PostalAddress;
import com.google.appengine.api.datastore.Rating;
import com.google.appengine.api.datastore.ShortBlob;
import com.google.appengine.api.users.User;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.code.gaeom.CustomEncoder;
import com.google.code.gaeom.CustomMultiEncoder;
import com.google.code.gaeom.annotation.Cached;
import com.google.code.gaeom.annotation.Child;
import com.google.code.gaeom.annotation.Embedded;
import com.google.code.gaeom.annotation.EmbeddedIn;
import com.google.code.gaeom.annotation.EncodeWith;
import com.google.code.gaeom.annotation.Id;
import com.google.code.gaeom.annotation.NoIndex;
import com.google.code.gaeom.annotation.Parent;
import com.google.code.gaeom.annotation.Serialize;
import com.google.code.gaeom.annotation.Text;
import com.google.code.gaeom.impl.encoder.ArrayEncoder;
import com.google.code.gaeom.impl.encoder.ByteArrayEncoder;
import com.google.code.gaeom.impl.encoder.CharArrayEncoder;
import com.google.code.gaeom.impl.encoder.CharEncoder;
import com.google.code.gaeom.impl.encoder.CollectionEncoder;
import com.google.code.gaeom.impl.encoder.CustomMultiPropertyEncoder;
import com.google.code.gaeom.impl.encoder.CustomSinglePropertyEncoder;
import com.google.code.gaeom.impl.encoder.DirectEncoder;
import com.google.code.gaeom.impl.encoder.EmbeddedEncoder;
import com.google.code.gaeom.impl.encoder.EmbeddedInEncoder;
import com.google.code.gaeom.impl.encoder.EnumEncoder;
import com.google.code.gaeom.impl.encoder.MapEncoder;
import com.google.code.gaeom.impl.encoder.ParentRelationshipEncoder;
import com.google.code.gaeom.impl.encoder.RelationshipEncoder;
import com.google.code.gaeom.impl.encoder.SerializableEncoder;
import com.google.code.gaeom.impl.encoder.TextEncoder;
import com.google.code.gaeom.util.FutureUtils;
import com.google.code.gaeom.util.ObjectUtils;
import com.google.code.gaeom.util.ReflectionUtils;
import com.google.code.gaeom.util.ReflectionUtils.FieldCallback;
import com.google.code.gaeom.util.Stack;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
* @author Peter Murray <gaeom@pmurray.com>
*/
public class ObjectEncoder
{
  private static final ThreadLocal<Stack.Mutable<Object>> threadStack = new ThreadLocal<Stack.Mutable<Object>>();

  private static Stack.Mutable<Object> getMutableObjectStack()
  {
    Stack.Mutable<Object> stack = threadStack.get();
    if (stack == null)
    {
      stack = Stack.Factory.create();
      threadStack.set(stack);
    }
    return stack;
  }

  public static Stack<Object> getObjectStack()
  {
    return getMutableObjectStack();
  }

  private static Set<Class<?>> directFieldTypes = Sets.newHashSet();
  static
  {
    directFieldTypes.add(boolean.class);
    directFieldTypes.add(Boolean.class);
    directFieldTypes.add(ShortBlob.class);
    directFieldTypes.add(Blob.class);
    directFieldTypes.add(Category.class);
    directFieldTypes.add(Date.class);
    directFieldTypes.add(Email.class);
    directFieldTypes.add(float.class);
    directFieldTypes.add(Float.class);
    directFieldTypes.add(double.class);
    directFieldTypes.add(Double.class);
    directFieldTypes.add(GeoPt.class);
    directFieldTypes.add(User.class);
    directFieldTypes.add(byte.class);
    directFieldTypes.add(Byte.class);
    directFieldTypes.add(char.class);
    directFieldTypes.add(Character.class);
    directFieldTypes.add(short.class);
    directFieldTypes.add(Short.class);
    directFieldTypes.add(int.class);
    directFieldTypes.add(Integer.class);
    directFieldTypes.add(long.class);
    directFieldTypes.add(Long.class);
    directFieldTypes.add(BlobKey.class);
    directFieldTypes.add(Key.class);
    directFieldTypes.add(Link.class);
    directFieldTypes.add(IMHandle.class);
    directFieldTypes.add(PostalAddress.class);
    directFieldTypes.add(Rating.class);
    directFieldTypes.add(PhoneNumber.class);
    directFieldTypes.add(String.class);
    directFieldTypes.add(com.google.appengine.api.datastore.Text.class);
  }

  private final ObjectStoreImpl os;
  private final String kind;
  private final Class<?> clazz;
  private final Map<String, FieldEntry> nameToField = Maps.newHashMap();
  private final List<FieldEntry> fields = Lists.newArrayList();
  private final boolean cacheInstances;
  private Field idField;
  private Field parentField;
  private Iterator<Key> keys;

  private static class FieldEntry
  {
    final Field field;
    final FieldEncoder encoder;

    public FieldEntry(Field field, FieldEncoder mapper)
    {
      if (mapper == null)
        throw new NullPointerException("No mapper found for field: " + field);

      this.field = field;
      field.setAccessible(true);

      this.encoder = mapper;
    }
  }

  public ObjectEncoder(ObjectStoreImpl os, Class<?> clazz)
  {
    this(os, clazz, null);
  }

  public ObjectEncoder(ObjectStoreImpl store, final Class<?> clazz, final String prefix)
  {
    this.os = store;
    this.kind = os.typeToKind(clazz);
    this.clazz = clazz;
    this.cacheInstances = clazz.getAnnotation(Cached.class) != null;
    ReflectionUtils.visitFields(clazz, new FieldCallback()
    {
      public boolean doWith(Field field)
      {
        if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()))
        {
          if (field.getAnnotation(Id.class) != null)
          {
            Class<?> fieldType = field.getType();
            if (!String.class.isAssignableFrom(fieldType) && !Long.class.isAssignableFrom(fieldType) && !long.class.isAssignableFrom(fieldType))
              throw new IllegalStateException("Cannot have @Id field of type: " + fieldType);
            if (idField != null)
              throw new IllegalStateException("Cannot have more than one @Id field per class (including superclasses).");
            idField = field;
            idField.setAccessible(true);
          }
          else
          {
            FieldEncoder encoder = getFieldEncoder(prefix == null ? field.getName() : prefix + "." + field.getName(), field, field.getGenericType());

            if (encoder != null)
            {
              FieldEntry entry = new FieldEntry(field, encoder);
              nameToField.put(field.getName(), entry);
              fields.add(entry);
              if (field.isAnnotationPresent(Parent.class))
              {
                if (parentField != null)
                  throw new IllegalStateException("May not have more than one @Parent annotation in a type.");
                if (!(encoder instanceof RelationshipEncoder) && !(encoder instanceof ParentRelationshipEncoder))
                  throw new IllegalStateException("@Parent must be on a to-one relationship");
                parentField = field;
                field.setAccessible(true);
              }
            }
          }
        }
        return true;
      }

      private FieldEncoder getFieldEncoder(final String propertyName, Field field, Type type)
      {
        Class<?> clazz = ReflectionUtils.getBaseClass(type);
        final boolean index = !field.isAnnotationPresent(NoIndex.class);

        if (field.getAnnotation(Serialize.class) != null)
        {
          return new SerializableEncoder(propertyName);
        }
        else if (Map.class.isAssignableFrom(clazz))
        {
          Type[] types = ((ParameterizedType) type).getActualTypeArguments();
          FieldEncoder keyEncoder = getFieldEncoder(propertyName + MapEncoder.cKeyPrefix, field, types[0]);
          FieldEncoder valueEncoder = getFieldEncoder(propertyName + MapEncoder.cValuePrefix, field, types[0]);
          return new MapEncoder(propertyName, clazz, keyEncoder, valueEncoder);
        }
        else if (Collection.class.isAssignableFrom(clazz))
        {
          Type[] types = ((ParameterizedType) type).getActualTypeArguments();
          FieldEncoder valueEncoder = getFieldEncoder(propertyName + CollectionEncoder.cContentsPrefix, field, types[0]);
          return new CollectionEncoder(propertyName, clazz, valueEncoder);
        }
        else if (clazz.isArray())
        {
          if (clazz.getComponentType() == byte.class)
          {
            return new ByteArrayEncoder(propertyName);
          }
          else if (clazz.getComponentType() == char.class)
          {
            return new CharArrayEncoder(propertyName);
          }
          else
          {
            FieldEncoder valueEncoder = getFieldEncoder(propertyName + CollectionEncoder.cContentsPrefix, field, clazz.getComponentType());
            return new ArrayEncoder(propertyName, clazz.getComponentType(), valueEncoder);
          }
        }
        else if (field.getAnnotation(EmbeddedIn.class) != null)
        {
          return new EmbeddedInEncoder(clazz);
        }
        else if (field.isAnnotationPresent(EncodeWith.class) || field.getType().isAnnotationPresent(EncodeWith.class))
        {
          Class<? extends CustomEncoder<?, ?>> encoderClass;
          if (field.isAnnotationPresent(EncodeWith.class))
            encoderClass = field.getAnnotation(EncodeWith.class).value();
          else
            encoderClass = field.getType().getAnnotation(EncodeWith.class).value();
          if (CustomMultiEncoder.class.isAssignableFrom(encoderClass))
          {
            CustomMultiEncoder<?> custom = ObjectUtils.cast(ObjectUtils.newInstance(encoderClass));
            return new CustomMultiPropertyEncoder(propertyName, custom, index);
          }
          else
          {
            CustomEncoder<?, ?> custom = ObjectUtils.newInstance(encoderClass);
            return new CustomSinglePropertyEncoder(propertyName, custom, index);
          }
        }
        else if (Enum.class.isAssignableFrom(clazz))
        {
          return new EnumEncoder(propertyName, clazz, index);
        }
        else if (field.getAnnotation(Text.class) != null)
        {
          return new TextEncoder(propertyName);
        }
        else if (char.class == clazz || Character.class == clazz)
        {
          return new CharEncoder(propertyName, index);
        }
        else if (directFieldTypes.contains(clazz))
        {
          return new DirectEncoder(propertyName, index);
        }
        else if (field.getAnnotation(Embedded.class) != null)
        {
          return new EmbeddedEncoder(propertyName, clazz, os);
        }
        else
        {
          Parent parent = field.getAnnotation(Parent.class);
          if (parent != null && parent.value() != Parent.FilterPolicy.RetainKey)
            return new ParentRelationshipEncoder(field.getName());
          else
            return new RelationshipEncoder(field.getName(), propertyName, field.isAnnotationPresent(Child.class), index);
        }
      }
    });
  }

  public void setId(Object target, Key id)
  {
    if (idField != null)
    {
      if (String.class.isAssignableFrom(idField.getType()))
        ReflectionUtils.set(target, idField, id.getName());
      else
        ReflectionUtils.set(target, idField, id.getId());
    }
  }

  public synchronized long getNextId(AsyncDatastoreService service)
  {
    if (keys == null || !keys.hasNext())
      keys = FutureUtils.safeGet(service.allocateIds(kind, 100)).iterator();
    return keys.next().getId();
  }

  public void encode(final InstanceStore os, final Object object, final PropertyStore store)
  {
    assert clazz == object.getClass();
    Stack.Mutable<Object> stack = getMutableObjectStack();
    stack.push(object);
    try
    {
      for (FieldEntry entry : fields)
        entry.encoder.encode(os, ReflectionUtils.get(object, entry.field), store);
    }
    finally
    {
      stack.pop();
    }
  }

  public void decode(final InstanceSource os, final Object object, final PropertySource source)
  {
    assert clazz == object.getClass();
    Stack.Mutable<Object> stack = getMutableObjectStack();
    stack.push(object);
    try
    {
      for (FieldEntry entry : fields)
      {
        Object value = entry.encoder.decode(os, source);
        if (value != null)
          value = ConvertUtils.convert(value, entry.field.getType());

        ReflectionUtils.set(object, entry.field, value);
      }
    }
    finally
    {
      stack.pop();
    }
  }

  public boolean shouldCache()
  {
    return cacheInstances;
  }

  public Object getId(Object object)
  {
    if (idField != null)
      return ReflectionUtils.get(object, idField);
    else
      return null;
  }

  public Object getParent(Object object)
  {
    if (parentField != null)
      return ReflectionUtils.get(object, parentField);
    else
      return null;
  }

  public FieldEncoder getEncoder(String... field)
  {
    FieldEntry entry = getFieldEntry(0, field);
    return entry != null ? entry.encoder : null;
  }

  public Field getField(String... field)
  {
    FieldEntry entry = getFieldEntry(0, field);
    return entry != null ? entry.field : null;
  }

  public FieldEntry getFieldEntry(int offset, String... field)
  {
    FieldEntry entry = nameToField.get(field[offset]);
    if (entry != null)
    {
      if (offset < field.length - 1)
      {
        FieldEncoder encoder = entry.encoder;
        if (encoder instanceof CollectionEncoder)
          encoder = ((CollectionEncoder) encoder).getDefaultEncoder();

        if (encoder instanceof EmbeddedEncoder)
          return ((EmbeddedEncoder) encoder).getDefaultEncoder().getFieldEntry(offset + 1, field);
        else
          return null;
      }
      else
      {
        return entry;
      }
    }
    else
    {
      return null;
    }
  }
}
TOP

Related Classes of com.google.code.gaeom.impl.ObjectEncoder$FieldEntry

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.