package com.googlecode.objectify.impl.save;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.List;
import com.google.appengine.api.datastore.Entity;
import com.googlecode.objectify.annotation.Indexed;
import com.googlecode.objectify.annotation.NotSaved;
import com.googlecode.objectify.annotation.Unindexed;
import com.googlecode.objectify.condition.Always;
import com.googlecode.objectify.condition.If;
import com.googlecode.objectify.impl.TypeUtils;
/**
* <p>Most savers are related to a particular type of field. This provides
* a convenient base class.</p>
*/
abstract public class FieldSaver implements Saver
{
String path;
Field field;
If<?, ?>[] indexConditions;
If<?, ?>[] unindexConditions;
If<?, ?>[] notSavedConditions;
/**
* @param examinedClass is the class which is being registered (or embedded). It posesses the field,
* but it is not necessarily the declaring class (which could be a base class).
* @param collectionize is whether or not the elements of this field should be stored in a collection;
* this is used for embedded collection class fields.
*/
public FieldSaver(String pathPrefix, Class<?> examinedClass, Field field, boolean collectionize)
{
this.field = field;
this.path = TypeUtils.extendPropertyPath(pathPrefix, field.getName());
// Check @Indexed and @Unindexed conditions
Indexed indexedAnn = field.getAnnotation(Indexed.class);
Unindexed unindexedAnn = field.getAnnotation(Unindexed.class);
if (indexedAnn != null && unindexedAnn != null)
throw new IllegalStateException("Cannot have @Indexed and @Unindexed on the same field: " + field);
if (indexedAnn != null)
this.indexConditions = this.generateIfConditions(indexedAnn.value(), examinedClass);
if (unindexedAnn != null)
this.unindexConditions = this.generateIfConditions(unindexedAnn.value(), examinedClass);
// Now watch out for @NotSaved conditions
NotSaved notSaved = field.getAnnotation(NotSaved.class);
if (notSaved != null)
{
if (collectionize && (notSaved.value().length != 1 || notSaved.value()[0] != Always.class))
throw new IllegalStateException("You cannot use @NotSaved with a condition within @Embedded collections; check the field " + this.field);
this.notSavedConditions = this.generateIfConditions(notSaved.value(), examinedClass);
}
}
/** */
private If<?, ?>[] generateIfConditions(Class<? extends If<?, ?>>[] ifClasses, Class<?> examinedClass)
{
If<?, ?>[] result = new If<?, ?>[ifClasses.length];
for (int i=0; i<ifClasses.length; i++)
{
Class<? extends If<?, ?>> ifClass = ifClasses[i];
result[i] = this.createIf(ifClass, examinedClass);
// Sanity check the generic If class types to ensure that they matches the actual types of the field & entity.
List<Class<?>> typeArguments = TypeUtils.getTypeArguments(If.class, ifClass);
if (!TypeUtils.isAssignableFrom(typeArguments.get(0), field.getType()))
throw new IllegalStateException("Cannot use If class " + ifClass.getName() + " on " + field
+ " because you cannot assign " + field.getType().getName() + " to " + typeArguments.get(0).getName());
if (!TypeUtils.isAssignableFrom(typeArguments.get(1), examinedClass))
throw new IllegalStateException("Cannot use If class " + ifClass.getName() + " on " + field
+ " because the containing class " + examinedClass.getName() + " is not compatible with " + typeArguments.get(1).getName());
}
return result;
}
/** */
private If<?, ?> createIf(Class<? extends If<?, ?>> ifClass, Class<?> examinedClass)
{
try
{
Constructor<? extends If<?, ?>> ctor = TypeUtils.getConstructor(ifClass, Class.class, Field.class);
return TypeUtils.newInstance(ctor, examinedClass, this.field);
}
catch (IllegalStateException ex)
{
try
{
Constructor<? extends If<?, ?>> ctor = TypeUtils.getNoArgConstructor(ifClass);
return TypeUtils.newInstance(ctor);
}
catch (IllegalStateException ex2)
{
throw new IllegalStateException("The If<?> class " + ifClass.getName() + " must have a no-arg constructor or a constructor that takes one argument of type Field.");
}
}
}
/* (non-Javadoc)
* @see com.googlecode.objectify.impl.save.Saver#save(java.lang.Object, com.google.appengine.api.datastore.Entity)
*/
@Override
@SuppressWarnings("unchecked")
final public void save(Object pojo, Entity entity, boolean index)
{
Object value = TypeUtils.field_get(this.field, pojo);
if (this.notSavedConditions != null)
{
for (int i=0; i<this.notSavedConditions.length; i++)
if (((If<Object, Object>)this.notSavedConditions[i]).matches(value, pojo))
return;
}
if (this.indexConditions != null && !index)
{
for (int i=0; i<this.indexConditions.length; i++)
if (((If<Object, Object>)this.indexConditions[i]).matches(value, pojo))
index = true;
}
if (this.unindexConditions != null && index)
{
for (int i=0; i<this.unindexConditions.length; i++)
if (((If<Object, Object>)this.unindexConditions[i]).matches(value, pojo))
index = false;
}
this.saveValue(value, entity, index);
}
/**
* Actually save the value in the entity. This is the real value, already obtained
* from the POJO and checked against the @Unsaved mechanism..
*/
abstract protected void saveValue(Object value, Entity entity, boolean index);
/**
* Sets property on the entity correctly for the values of this.path and this.indexed.
*/
protected void setEntityProperty(Entity entity, Object value, boolean index)
{
if (index)
entity.setProperty(this.path, value);
else
entity.setUnindexedProperty(this.path, value);
}
}