package net.enilink.komma.em; import java.lang.reflect.Field; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import net.enilink.composition.traits.Behaviour; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.MembersInjector; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import net.enilink.komma.core.EntityVar; import net.enilink.komma.core.IReference; import net.enilink.komma.core.IReferenceable; public class EntityVarModule extends AbstractModule { /** * Key for shared variables that are indexed by a {@link Field} and an * {@link IReference}. */ static class EntityVarKey { private Field field; private IReference reference; public EntityVarKey(Field field, IReference reference) { this.field = field; this.reference = reference; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((field == null) ? 0 : field.hashCode()); result = prime * result + ((reference == null) ? 0 : reference.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; EntityVarKey other = (EntityVarKey) obj; if (field == null) { if (other.field != null) return false; } else if (!field.equals(other.field)) return false; if (reference == null) { if (other.reference != null) return false; } else if (!reference.equals(other.reference)) return false; return true; } @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()) .append("(field = "); if (field == null) { sb.append((String) null); } else { sb.append(field.getDeclaringClass()).append("#") .append(field.getName()); } return sb.append(", reference = ").append(reference).append(")") .toString(); } } /** * Stores a shared value that is indexed by a {@link EntityVarKey}. */ static class EntityVarImpl<T> implements EntityVar<T> { private T value; @Override public T get() { return value; } @Override public void remove() { value = null; } @Override public void set(T value) { if (value == null) { remove(); } else { this.value = value; } } } /** * Injects an {@link EntityVar} into an object's field. */ class EntityVarMembersInjector<T> implements MembersInjector<T> { @Inject private Map<EntityVarKey, EntityVar<Object>> varMap; private Field field; EntityVarMembersInjector(Field field) { this.field = field; } @Override public void injectMembers(T target) { Object referenceable = target; if (referenceable instanceof Behaviour<?>) { referenceable = ((Behaviour<?>) target).getBehaviourDelegate(); } if (referenceable instanceof IReferenceable) { IReference reference = ((IReferenceable) referenceable) .getReference(); final EntityVarKey key = new EntityVarKey(field, reference); EntityVar<Object> var = varMap.get(key); if (var == null) { synchronized (field.getDeclaringClass()) { var = varMap.get(key); if (var == null) { var = new EntityVarImpl<Object>() { boolean isValid = true; public void remove() { super.remove(); varMap.remove(key); isValid = false; } @Override public void set(Object value) { if (!isValid && value != null) { // reinsert variable into map if it was // previously removed varMap.put(key, this); isValid = true; } super.set(value); } }; varMap.put(key, var); } } } try { // suppress permission check for non-public fields field.setAccessible(true); field.set(target, var); } catch (Exception e) { throw new ProvisionException( "Error while injecting entity variable.", e); } } else { throw new ProvisionException( "Unable to determine reference for target: " + target); } } } /** * Registers a members injector for fields of type {@link EntityVar}. */ class EntityVarTypeListener implements TypeListener { @Inject Injector injector; @Override public <T> void hear(TypeLiteral<T> typeLiteral, TypeEncounter<T> typeEncounter) { for (Class<?> c = typeLiteral.getRawType(); c != Object.class; c = c .getSuperclass()) { for (Field field : c.getDeclaredFields()) { if (field.getType() == EntityVar.class) { MembersInjector<T> membersInjector = new EntityVarMembersInjector<T>( field); injector.injectMembers(membersInjector); typeEncounter.register(membersInjector); } } } } } @Override protected void configure() { bind(new TypeLiteral<Map<EntityVarKey, EntityVar<Object>>>() { }).toInstance(new ConcurrentHashMap<EntityVarKey, EntityVar<Object>>()); EntityVarTypeListener varTypeListener = new EntityVarTypeListener(); requestInjection(varTypeListener); bindListener(Matchers.any(), varTypeListener); } }