package be.ac.chaq.model.entity; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import be.ac.chaq.change.Change; import be.ac.chaq.model.snapshot.Snapshot; public abstract class EntityState { private static Map<Class<? extends EntityState>, HashMap<String, PropertyDescriptor>> class2propertyDescriptors; static { class2propertyDescriptors = new HashMap<Class<? extends EntityState>, HashMap<String, PropertyDescriptor>>(); } private static void registerPropertyDescriptor(Map<String, PropertyDescriptor> propertyDescriptors, PropertyDescriptor pd) { //"" is default value of name attribute in annotation if(pd.getName().equals("")) pd.setName(pd.getField().getName()); PropertyDescriptor old = propertyDescriptors.put(pd.getName(), pd); if(old != null) throw new RuntimeException("Encountered two property descriptors in a class hierarchy with the same name:" + old + " and " + pd); } public static Map<String, PropertyDescriptor> getPropertyDescriptorsMap(Class<? extends EntityState> entityStateClass) { HashMap<String, PropertyDescriptor> propertyDescriptors = class2propertyDescriptors.get(entityStateClass); if(propertyDescriptors != null) return propertyDescriptors; propertyDescriptors = new HashMap<String,PropertyDescriptor>(); for(Field field : getAllFields(entityStateClass)) { for(Annotation annotation : field.getAnnotations()) { //TODO: refactor to a static method PropertyDescriptor.fromAnnotationOnField(Annotation, Field, Owner) Class<? extends Annotation> annotationType = annotation.annotationType(); if(annotationType == EntityProperty.class) { EntityProperty entityPropertyAnnotation = (EntityProperty) annotation; field.setAccessible(true); EntityPropertyDescriptor entityPropertyDescriptor = new EntityPropertyDescriptor(entityPropertyAnnotation.name(), entityStateClass, entityPropertyAnnotation.value(), field); registerPropertyDescriptor(propertyDescriptors,entityPropertyDescriptor); break; } if(annotationType == SimpleProperty.class) { SimpleProperty simplePropertyAnnotation = (SimpleProperty) annotation; field.setAccessible(true); SimplePropertyDescriptor simplePropertyDescriptor = new SimplePropertyDescriptor(simplePropertyAnnotation.name(), entityStateClass, simplePropertyAnnotation.value(), field); registerPropertyDescriptor(propertyDescriptors,simplePropertyDescriptor); break; } if(annotationType == EntityListProperty.class) { EntityListProperty entityListPropertyAnnotation = (EntityListProperty) annotation; field.setAccessible(true); EntityListPropertyDescriptor entityListPropertyDescriptor = new EntityListPropertyDescriptor(entityListPropertyAnnotation.name(),entityStateClass, entityListPropertyAnnotation.value(), field); registerPropertyDescriptor(propertyDescriptors,entityListPropertyDescriptor); break; } } } class2propertyDescriptors.put(entityStateClass, propertyDescriptors); return propertyDescriptors; } public static Collection<PropertyDescriptor> getPropertyDescriptors(Class<? extends EntityState> entityStateClass) { return getPropertyDescriptorsMap(entityStateClass).values(); } public static PropertyDescriptor getPropertyDescriptor(Class<? extends EntityState> entityStateClass, String name) { return getPropertyDescriptorsMap(entityStateClass).get(name); } private static List<Field> getAllFields(Class<?> type) { List<Field> result = new ArrayList<Field>(); Class<?> i = type; while (i != null && i != Object.class) { for (Field field : i.getDeclaredFields()) { if (!field.isSynthetic()) { result.add(field); } } i = i.getSuperclass(); } return result; } //public abstract Object getProperty(PropertyDescriptor descriptor); //public abstract void setProperty(PropertyDescriptor property, Object newValue); private EntityIdentifier id; private Change appliedChange; private EntityState predecessor; private Snapshot snapshotForLookup; public EntityState() { } public void setAppliedChange(Change c) { if(appliedChange == null) appliedChange = c; else throw new RuntimeException("EntityState already has a Change object as its appliedChange."); } public Change getAppliedChange() { return appliedChange; } public EntityIdentifier getID() { return id; } public void setID(EntityIdentifier id) { this.id = id; } public EntityState lookup(EntityIdentifier id) { return getSnapshotForLookup().lookup(id); } //should be overridden for subclasses that have additional, non-property fields protected void initializeFieldsFrom(EntityState s) { this.appliedChange = null; this.id = s.id; this.predecessor = s.predecessor; this.snapshotForLookup = s.getSnapshotForLookup(); } private void initializePropertiesFrom(EntityState s) { for(PropertyDescriptor pd : EntityState.getPropertyDescriptors(s.getClass())) { this.setProperty(pd, s.getProperty(pd)); } } public EntityState shallowClone() { Class<? extends EntityState> clazz = this.getClass(); EntityState newInstance; try { newInstance = clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } newInstance.initializeFieldsFrom(this); newInstance.initializePropertiesFrom(this); return newInstance; } public Snapshot getSnapshotForLookup() { return snapshotForLookup; } public void setSnapshotForLookup(Snapshot snapshotForLookup) { this.snapshotForLookup = snapshotForLookup; } public PropertyDescriptor getPropertyDescriptorNamed(String name) { PropertyDescriptor pd = getPropertyDescriptor(this.getClass(), name); if(pd == null) throw new RuntimeException("EntityState does not have a property named" + name); return pd; } public Object getPropertyNamed(String name) { PropertyDescriptor pd = getPropertyDescriptorNamed(name); return getProperty(pd); } public void setPropertyNamed(String name, Object value) { PropertyDescriptor pd = getPropertyDescriptor(this.getClass(), name); if(pd == null) throw new RuntimeException("EntityState does not have a property named" + name); setProperty(pd, value); } public Object getProperty(PropertyDescriptor descriptor) { try { Field field = descriptor.getField(); Object value = field.get(this); assert(descriptor.canBeAssigned(value)); return value; } catch(Exception e) { throw new RuntimeException("Cannot access given property " + descriptor); } } public void setProperty(PropertyDescriptor descriptor, Object value) { try { //assert(descriptor.canBeAssigned(value)); Field field = descriptor.getField(); field.set(this, value); } catch(Exception e) { throw new RuntimeException("Cannot set property " + descriptor + "to value " + value); } } public EntityState getPredecessor() { return predecessor; } public void setPredecessor(EntityState predecessor) { this.predecessor = predecessor; } }