package com.dooapp.gaedo.finders.root;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.dooapp.gaedo.finders.FieldInformer;
import com.dooapp.gaedo.finders.FieldInformerAPI;
import com.dooapp.gaedo.finders.Informer;
import com.dooapp.gaedo.finders.QueryExpression;
import com.dooapp.gaedo.finders.expressions.EqualsExpression;
import com.dooapp.gaedo.properties.Property;
import com.dooapp.gaedo.properties.PropertyProvider;
public class ReflectionBackedInformer<DataType> implements Informer<DataType> {
/**
* Class used when a {@link ReflectionBackedInformer} is seen as a field of another object
* @author ndx
*
*/
public class AsFieldInformer implements Informer<DataType> {
/**
* Field used to see the containing {@link ReflectionBackedInformer}
*/
private Property field;
/**
* Stored parent path
*/
private List<Property> parentPath = Collections.emptyList();
public AsFieldInformer(Property field) {
this.field = field;
}
@Override
public FieldInformer get(String string, Collection<Property> propertyPath) {
FieldInformer returned = ReflectionBackedInformer.this.get(string);
if(returned instanceof FieldInformerAPI) {
// Improve path with this informer one
Collection<Property> newPath = new LinkedList<Property>(propertyPath);
newPath.add(field);
returned = ((FieldInformerAPI) returned).with(newPath);
}
return returned;
}
@Override
public FieldInformer get(String string) {
return get(string, parentPath);
}
@Override
public QueryExpression equalsTo(Object value) {
return new EqualsExpression(field, getFieldPath(), value);
}
@Override
public Informer asField(Property field) {
return ReflectionBackedInformer.this.asField(field);
}
@Override
public Property getField() {
return field;
}
@Override
public Collection<FieldInformer> getAllFieldInformers() {
return ReflectionBackedInformer.this.getAllFieldInformers();
}
@Override
public Collection<Property> getAllFields() {
return ReflectionBackedInformer.this.getAllFields();
}
@Override
public Iterable<Property> getFieldPath() {
List<Property> returned = new LinkedList<Property>(parentPath);
returned.add(field);
return returned;
}
@Override
public FieldInformer with(Collection<Property> propertyPath) {
return new AsFieldInformer(field);
}
/**
* @return
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((field == null) ? 0 : field.hashCode());
return result;
}
/**
* @param obj
* @return
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AsFieldInformer other = (AsFieldInformer) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (field == null) {
if (other.field != null)
return false;
} else if (!field.equals(other.field))
return false;
return true;
}
private ReflectionBackedInformer getOuterType() {
return ReflectionBackedInformer.this;
}
}
/**
* Informer for super class
*/
private Informer<? super DataType> parent;
/**
* Local class, used for getting fields and properties
*/
private Class<DataType> clazz;
/**
* Map linking known properties to effective informers. Notice this map is to be lazily loaded by {@link #loadFieldsInformers(ReflectionBackedInformerFactory)}
*/
private Map<Property, FieldInformer> fields;
private PropertyProvider propertyProvider;
/**
* Informer factory used for lazy loading the field informers
*/
private ReflectionBackedInformerFactory informerFactory;
public ReflectionBackedInformer(Class<DataType> clazz,
ReflectionBackedInformerFactory reflectionBackedInformerFactory,
PropertyProvider provider) {
// Immediatly load parent infos
if(!clazz.isAssignableFrom(Object.class)) {
parent = reflectionBackedInformerFactory.get(clazz.getSuperclass());
}
// Now, get all fields
this.clazz = clazz;
this.propertyProvider = provider;
this.informerFactory = reflectionBackedInformerFactory;
}
/**
* Load all fields informers from the class fields
* @param reflectionBackedInformerFactory
*/
private Map<Property, FieldInformer> loadFieldsInformers(ReflectionBackedInformerFactory reflectionBackedInformerFactory) {
Map<Property, FieldInformer> futureFields = new HashMap<Property, FieldInformer>();
Class<?> currentClass = clazz;
while(!Object.class.equals(currentClass)) {
Property[] fieldsArray = propertyProvider.get(currentClass);
for(Property f : fieldsArray) {
// notice only non-static fields will have associated informers, as static fields have their values shared amongst all class instances
if(!f.hasModifier(Modifier.STATIC)) {
try {
futureFields.put(f, reflectionBackedInformerFactory.getInformerFor(f));
} catch(UnsupportedOperationException e) {
e.printStackTrace();
}
}
}
currentClass = currentClass.getSuperclass();
}
return futureFields;
}
/**
* Effective implementation obtaining field with given assignated property path
* @param string field name
* @param propertyPath parent path
* @return a {@link FieldInformer} or an exception
* @see #internalGet(String)
*/
@Override
public FieldInformer get(String string, Collection<Property> propertyPath) {
FieldInformer returned = internalGet(string);
if(returned instanceof FieldInformerAPI) {
returned = ((FieldInformerAPI) returned).with(new LinkedList<Property>(propertyPath));
}
return returned;
}
/**
* Obtain field informer for the given property name
* @param string
* @return a {@link FieldInformer} or an exception
* @see #get(String, Collection)
*/
@Override
public FieldInformer get(String string) {
return get(string, new LinkedList<Property>());
}
/**
* Obtain entry by performing an informed lookup consisting into first looking up field name then field name qualified with class simple name, and finally by performing
* lookup in parent class
* @param string field name
* @return a {@link FieldInformer} or an exception
*/
public FieldInformer internalGet(String string) {
if(fields==null) {
fields = loadFieldsInformers(informerFactory);
}
for(Map.Entry<Property, FieldInformer> f : fields.entrySet()) {
if(f.getKey().getName().equals(string)) {
return f.getValue();
} else if((clazz.getSimpleName()+"."+f.getKey().getName()).equals(string)) {
return f.getValue();
}
}
if(parent!=null) {
return parent.get(string);
} else {
throw new NoSuchFieldInHierarchyException(string);
}
}
/**
* The equalsTo method, as implemented by {@link Informer}, checks that the informer reference is equals to the reference given.
* As a consequence, a null value is given for the field (which is an error since it does not allows model navigation)
*/
@Override
public QueryExpression equalsTo(Object value) {
return new EqualsExpression(null, new LinkedList<Property>(), value);
}
@Override
public Informer asField(Property field) {
return new AsFieldInformer(field);
}
/**
* There is no field associated with object informer. As a consequence, an exception is thrown
*/
@Override
public Property getField() {
throw new UnsupportedOperationException("No field can be associated to a ReflectionBackedInformer, which only describes a root object");
}
/**
* Get all fields of this object. This method creates a short lifetime collection containing all fields of this object (coming from this class and from superclass)
* @return
*/
public Collection<FieldInformer> getAllFieldInformers() {
Collection<FieldInformer> toReturn = new LinkedList<FieldInformer>();
if(parent!=null) {
toReturn.addAll(parent.getAllFieldInformers());
}
if(fields==null) {
fields = loadFieldsInformers(informerFactory);
}
toReturn.addAll(fields.values());
return toReturn;
}
@Override
public Collection<Property> getAllFields() {
Collection<Property> toReturn = new LinkedList<Property>();
if(parent!=null) {
toReturn.addAll(parent.getAllFields());
}
if(fields==null) {
fields = loadFieldsInformers(informerFactory);
}
toReturn.addAll(fields.keySet());
return toReturn;
}
@Override
public Iterable<Property> getFieldPath() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("method "+FieldInformer.class.getName()+"#getFieldPath has not yet been implemented AT ALL");
}
@Override
public FieldInformer with(Collection<Property> propertyPath) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("method "+FieldInformerAPI.class.getName()+"#use has not yet been implemented AT ALL");
}
/**
* @return
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((clazz == null) ? 0 : clazz.hashCode());
return result;
}
/**
* @param obj
* @return
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectionBackedInformer other = (ReflectionBackedInformer) obj;
if (clazz == null) {
if (other.clazz != null)
return false;
} else if (!clazz.getCanonicalName().equals(other.clazz.getCanonicalName()))
return false;
return true;
}
}