All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dooapp.gaedo.finders.root.ReflectionBackedInformer Maven / Gradle / Ivy

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 java.util.logging.Logger;

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.finders.expressions.InstanceOfExpression;
import com.dooapp.gaedo.properties.Property;
import com.dooapp.gaedo.properties.PropertyProvider;

public class ReflectionBackedInformer implements Informer {
	private static final Logger logger = Logger.getLogger(ReflectionBackedInformer.class.getName());
	/**
	 * Class used when a {@link ReflectionBackedInformer} is seen as a field of another object
	 * @author ndx
	 *
	 */
	public class AsFieldInformer implements Informer {
		/**
		 * Field used to see the containing {@link ReflectionBackedInformer}
		 */
		private Property field;
		/**
		 * Stored parent path
		 */
		private List parentPath = Collections.emptyList();

		public AsFieldInformer(Property field, List parentPath) {
			super();
			this.field = field;
			this.parentPath = parentPath;
		}

		public AsFieldInformer(Property field) {
			this.field = field;
		}

		@Override
		public FieldInformer get(String string, Collection propertyPath) {
			FieldInformer returned = ReflectionBackedInformer.this.get(string);
			if(returned instanceof FieldInformerAPI) {
				// Improve path with this informer one
				Collection newPath = new LinkedList(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 QueryExpression instanceOf(Class type) {
			return new InstanceOfExpression(field, getFieldPath(), type);
		}

		@Override
		public Informer asField(Property field) {
			return ReflectionBackedInformer.this.asField(field);
		}

		@Override
		public Property getField() {
			return field;
		}

		@Override
		public Collection getAllFieldInformers() {
			return ReflectionBackedInformer.this.getAllFieldInformers();
		}

		@Override
		public Collection getAllFields() {
			return ReflectionBackedInformer.this.getAllFields();
		}

		@Override
		public Iterable getFieldPath() {
			List returned  = new LinkedList(parentPath);
			returned.add(field);
			return returned;
		}

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

		@Override
		public FieldInformer with(Collection propertyPath) {
			return new AsFieldInformer(field, new LinkedList(propertyPath));
		}
	}

	/**
	 * Informer for super class
	 */
	private Informer parent;

	/**
	 * Local class, used for getting fields and properties
	 */
	private final Class informedClass;

	/**
	 * Map linking known properties to effective informers. Notice this map is to be lazily loaded by {@link #loadFieldsInformers(ReflectionBackedInformerFactory)}
	 */
	private Map fields;

	private PropertyProvider propertyProvider;

	/**
	 * Informer factory used for lazy loading the field informers
	 */
	private ReflectionBackedInformerFactory informerFactory;

	public ReflectionBackedInformer(Class clazz,
			ReflectionBackedInformerFactory reflectionBackedInformerFactory,
			PropertyProvider provider) {
		// Immediatly load parent infos
		if(!clazz.isAssignableFrom(Object.class)) {
			parent = reflectionBackedInformerFactory.get(clazz.getSuperclass());
		}
		// Now, get all fields
		this.informedClass = clazz;
		this.propertyProvider = provider;
		this.informerFactory = reflectionBackedInformerFactory;
	}

	/**
	 * Load all fields informers from the class fields
	 * @param reflectionBackedInformerFactory
	 */
	private Map loadFieldsInformers(ReflectionBackedInformerFactory reflectionBackedInformerFactory) {
		Map futureFields = new HashMap();
		Class currentClass = informedClass;
		while(!Object.class.equals(currentClass)) {
			Property[] fieldsArray = propertyProvider.get(currentClass);
			for(Property f : fieldsArray) {
				// notice only non-static non-transient fields will have associated informers, as static fields have their values shared amongst all class instances
				// and transient are, by design, transient (and not intended to be persisted)
				if(f.hasModifier(Modifier.STATIC)) {
					logger.fine("field "+f.toGenericString()+" will be ignored as it is a static one");
				} else if(f.hasModifier(Modifier.TRANSIENT)) {
					logger.fine("field "+f.toGenericString()+" will be ignored as it is a transient one");
				} else {
					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 propertyPath) {
		FieldInformer returned = internalGet(string);
		if(returned instanceof FieldInformerAPI) {
			returned = ((FieldInformerAPI) returned).with(new LinkedList(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());
	}


	/**
	 * 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 f : fields.entrySet()) {
			if(f.getKey().getName().equals(string)) {
				return f.getValue();
			} else if((informedClass.getSimpleName()+"."+f.getKey().getName()).equals(string)) {
				return f.getValue();
			}
		}
		if(parent!=null) {
			try {
				return parent.get(string);
			} catch(NoSuchFieldInHierarchyException e) {
				/* we do nothing here. I perfeclty know integrating exception in application logic is not optimal, but it's, to my mind, the best way to achieve the right result here. */
			}
		}
		return informerFactory.noSuchFieldInHiearchy(this, 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(), value);
	}

	@Override
	public QueryExpression instanceOf(Class type) {
		return new InstanceOfExpression(null, new LinkedList(), type);
	}

	@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 getAllFieldInformers() {
		Collection toReturn = new LinkedList();
		if(parent!=null) {
			toReturn.addAll(parent.getAllFieldInformers());
		}
		if(fields==null) {
			fields = loadFieldsInformers(informerFactory);
		}
		toReturn.addAll(fields.values());
		return toReturn;
	}

	@Override
	public Collection getAllFields() {
		Collection toReturn = new LinkedList();
		if(parent!=null) {
			toReturn.addAll(parent.getAllFields());
		}
		if(fields==null) {
			fields = loadFieldsInformers(informerFactory);
		}
		toReturn.addAll(fields.keySet());
		return toReturn;
	}

	/**
	 * As itself, that class has an empty field path
	 * @return
	 * @see com.dooapp.gaedo.finders.FieldInformer#getFieldPath()
	 */
	@Override
	public Iterable getFieldPath() {
		return Collections.emptyList();
	}

	/**
	 * @return
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((informedClass == null) ? 0 : informedClass.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 (informedClass == null) {
			if (other.informedClass != null)
				return false;
		} else if (!informedClass.getCanonicalName().equals(other.informedClass.getCanonicalName()))
			return false;
		return true;
	}

	/**
	 * @return
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("ReflectionBackedInformer [");
		if (informedClass != null) {
			builder.append("clazz=");
			builder.append(informedClass.getName());
			builder.append(", ");
		}
		if (fields != null) {
			builder.append("fields=");
			builder.append(fields.keySet());
		}
		builder.append("]");
		return builder.toString();
	}

	/**
	 * @return the informedClass
	 * @category getter
	 * @category informedClass
	 */
	public Class getInformedClass() {
		return informedClass;
	}

	@Override
	public FieldInformer with(Collection propertyPath) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException("method FieldInformerAPI#with has not yet been implemented AT ALL");
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy