Package com.vercer.engine.persist.translator

Source Code of com.vercer.engine.persist.translator.ObjectFieldTranslator

package com.vercer.engine.persist.translator;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

import com.vercer.engine.persist.Path;
import com.vercer.engine.persist.Property;
import com.vercer.engine.persist.PropertyTranslator;
import com.vercer.engine.persist.conversion.TypeConverter;
import com.vercer.engine.persist.util.PropertySets;
import com.vercer.engine.persist.util.SimpleProperty;
import com.vercer.engine.persist.util.PropertySets.PrefixPropertySet;
import com.vercer.engine.persist.util.generic.GenericTypeReflector;
import com.vercer.util.Reflection;
import com.vercer.util.collections.MergeSet;

/**
* @author John Patterson <john@vercer.com>
*
*/
public abstract class ObjectFieldTranslator implements PropertyTranslator
{
  private static final Comparator<Field> comparator = new Comparator<Field>()
  {
    public int compare(Field o1, Field o2)
    {
      return o1.getName().compareTo(o2.getName());
    }
  };
  private final TypeConverter converters;

  // permanent cache of class fields to reduce reflection
  private static Map<Class<?>, List<Field>> classFields = new ConcurrentHashMap<Class<?>, List<Field>>();
  private static Map<Class<?>, Constructor<?>> constructors = new ConcurrentHashMap<Class<?>, Constructor<?>>();

  public ObjectFieldTranslator(TypeConverter converters)
  {
    this.converters = converters;
  }

  public final Object propertiesToTypesafe(Set<Property> properties, Path path, Type type)
  {
    Class<?> clazz = GenericTypeReflector.erase(type);
    Object instance = createInstance(clazz);
    activate(properties, instance, path);
    return instance;
  }

  protected void activate(Set<Property> properties, Object instance, Path path)
  {
    // ensure the properties are sorted
    if (properties instanceof SortedSet<?> == false)
    {
      properties = new TreeSet<Property>(properties);
    }

    // both fields and properties are sorted by name
    List<Field> fields = getSortedFields(instance);
    Iterator<PrefixPropertySet> ppss = PropertySets.prefixPropertySets(properties, path).iterator();
    PrefixPropertySet pps = null;
    for (Field field : fields)
    {
      if (stored(field))
      {
        String name = fieldToPartName(field);
        Path fieldPath = new Path.Builder(path).field(name).build();
       
        // handle missing class fields by ignoring the properties
        while (ppss.hasNext() && (pps == null || pps.getPrefix().compareTo(fieldPath) < 0))
        {
          pps = ppss.next();
        }
       
        // if there are no properties for the field we must still
        // run a translator because some translators do not require
        // any fields to set a field value e.g. KeyTranslator
        Set<Property> childProperties;
        if (pps == null || !fieldPath.equals(pps.getPrefix()))
        {
          // there were no properties for this field
          childProperties = Collections.emptySet();
        }
        else
        {
          childProperties = pps.getProperties();
        }

        // get the correct translator for this field
        PropertyTranslator translator = decoder(field, childProperties);

        // get the type that we need to store
        Type type = typeFromField(field);

        onBeforeTranslate(field, childProperties);

        // create instance
        Object value;
        try
        {
          value = translator.propertiesToTypesafe(childProperties, fieldPath, type);
        }
        catch (Exception e)
        {
          // add a bit of context to the trace
          throw new IllegalStateException("Problem translating field " + field, e);
        }

        onAfterTranslate(field, value);

        if (value == null)
        {
          throw new IllegalStateException("Could not translate path " + fieldPath);
        }

        if (value == NULL_VALUE)
        {
          value = null;
        }
       
        setFieldValue(instance, field, value);
      }
    }
  }

  private void setFieldValue(Object instance, Field field, Object value)
  {
    // check for a default implementations of collections and reuse
    if (Collection.class.isAssignableFrom(field.getType()))
    {
      try
      {
        // see if there is a default value
        Collection<?> existing = (Collection<?>) field.get(instance);
        if (existing != null && value!= null && existing.getClass() != value.getClass())
        {
          // make sure the value is a list - could be a blob
          value = converters.convert(value, ArrayList.class);
         
          existing.clear();
          typesafeAddAll((Collection<?>) value, existing);
          return;
        }
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    }
    else if (Map.class.isAssignableFrom(field.getType()))
    {
      try
      {
        // see if there is a default value
        Map<?, ?> existing = (Map<?, ?>) field.get(instance);
        if (existing != null && value!= null && existing.getClass() != value.getClass())
        {
          // make sure the value is a map - could be a blob
          value = converters.convert(value, HashMap.class);
         
          existing.clear();
          typesafePutAll((Map<?, ?>) value, existing);
          return;
        }
      }
      catch (Exception e)
      {
        throw new IllegalStateException(e);
      }
    }

    // the stored type may not be the same as the declared type
    // due to the ability to define what type to store an instance
    // as using FieldTypeStrategy.type(Field) or @Type annotation
   
    // convert value to actual field type before setting
    value = converters.convert(value, field.getGenericType());
   
    try
    {
      field.set(instance, value);
    }
    catch (Exception e)
    {
      throw new IllegalStateException("Could not set value " + value + " to field " + field, e);
    }
  }

  @SuppressWarnings("unchecked")
  private <K, V> void typesafePutAll(Map<?, ?> value, Map<?, ?> existing)
  {
    ((Map<K, V>) existing).putAll((Map<K, V>) value);
  }

  @SuppressWarnings("unchecked")
  private <T> void typesafeAddAll(Collection<?> value, Collection<?> existing)
  {
    ((Collection<T>) existing).addAll((Collection<T>) value);
  }


  protected void onAfterTranslate(Field field, Object value)
  {
  }

  protected void onBeforeTranslate(Field field, Set<Property> childProperties)
  {
  }

  protected String fieldToPartName(Field field)
  {
    return field.getName();
  }

  protected Type typeFromField(Field field)
  {
    return field.getType();
  }

  protected Object createInstance(Class<?> clazz)
  {
    try
    {
      Constructor<?> constructor = getNoArgsConstructor(clazz);
      return constructor.newInstance();
    }
    catch (NoSuchMethodException e)
    {
      throw new IllegalArgumentException("Could not find no args constructor in " + clazz, e);
    }
    catch (Exception e)
    {
      throw new IllegalArgumentException("Could not construct instance of " + clazz, e);
    }
  }

  private Constructor<?> getNoArgsConstructor(Class<?> clazz) throws NoSuchMethodException
  {
    Constructor<?> constructor = constructors.get(clazz);
    if (constructor == null)
    {
      // use no-args constructor
      constructor = clazz.getDeclaredConstructor();
 
      // allow access to private constructor
      if (!constructor.isAccessible())
      {
        constructor.setAccessible(true);
      }
     
      constructors.put(clazz, constructor);
    }
    return constructor;
  }

  public final Set<Property> typesafeToProperties(Object object, Path path, boolean indexed)
  {
    if (object == null)
    {
      return Collections.emptySet();
    }

    try
    {
      List<Field> fields = getSortedFields(object);
      MergeSet<Property> merged = new MergeSet<Property>(fields.size());
      for (Field field : fields)
      {
        if (stored(field))
        {
          // get the type that we need to store
          Type type = typeFromField(field);

          // we may need to convert the object if it is not assignable
          Object value = field.get(object);
          if (value == null)
          {
            if (isNullStored())
            {
              merged.add(new SimpleProperty(path, null, indexed));
            }
            continue;
          }

          value = converters.convert(value, type);

          Path childPath = new Path.Builder(path).field(fieldToPartName(field)).build();

          PropertyTranslator translator = encoder(field, value);
          Set<Property> properties = translator.typesafeToProperties(value, childPath, indexed(field));
          if (properties == null)
          {
            throw new IllegalStateException("Could not translate value to properties: " + value);
          }
          merged.addAll(properties);
        }
      }

      return merged;
    }
    catch (IllegalAccessException e)
    {
      throw new IllegalStateException(e);
    }
  }

  private List<Field> getSortedFields(Object object)
  {
    // fields are cached and stored as a map because reading more common than writing
    List<Field> fields = classFields.get(object.getClass());
    if (fields == null)
    {
      fields = Reflection.getAccessibleFields(object.getClass());

      // sort the fields by name
      Collections.sort(fields, comparator);

      // cache because reflection is costly
      classFields.put(object.getClass(), fields);
    }
    return fields;
  }

  protected boolean isNullStored()
  {
    return false;
  }

  protected abstract boolean indexed(Field field);

  protected abstract boolean stored(Field field);

  protected abstract PropertyTranslator encoder(Field field, Object instance);
 
  protected abstract PropertyTranslator decoder(Field field, Set<Property> properties);

}
TOP

Related Classes of com.vercer.engine.persist.translator.ObjectFieldTranslator

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.