package pt.ist.fenixframework.dml.runtime; import java.util.AbstractSet; import java.util.Iterator; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pt.ist.fenixframework.DomainObject; import pt.ist.fenixframework.core.AbstractDomainObject; public class RelationAwareSet<E1 extends AbstractDomainObject, E2 extends AbstractDomainObject> extends AbstractSet<E2> implements Set<E2>, RelationBaseSet<E2> { private static final Logger logger = LoggerFactory.getLogger(RelationAwareSet.class); // All accesses to the internalMap should go through the getInternalMap() method! private DomainBasedMap<E2> internalMap; protected final DomainBasedMap.Getter<E2> internalMapGetter; protected final KeyFunction<? extends Comparable<?>, E2> mapKey; protected final E1 owner; protected final Relation<E1, E2> relation; /** * Common constructor for the RelationAwareSet. * * @param owner The {@link DomainObject} that owns this collection * @param relation The relation of which this set is a part of * @param mapKey The function that returns the key used to index this collection. * @param internalMap The backing collection for this side of the relation. If this is <code>null</code>, then the * internalMapGetter will be used when the collection is needed. * @param internalMapGetter The function that returns the internalMap when it is needed. Can be <code>null</code> if it is * never needed. */ protected RelationAwareSet(E1 owner, Relation<E1, E2> relation, KeyFunction<? extends Comparable<?>, E2> mapKey, DomainBasedMap<E2> internalMap, DomainBasedMap.Getter<E2> internalMapGetter) { this.owner = owner; this.relation = relation; this.mapKey = mapKey; this.internalMap = internalMap; this.internalMapGetter = internalMapGetter; } /** * Standard creation of a RelationAwareSet when the internalMap instance is known. * * @param owner The {@link DomainObject} that owns this collection * @param relation The relation of which this set is a part of * @param internalMap The backing collection for this side of the relation * @param mapKey The function that returns the key used to index this collection */ public RelationAwareSet(E1 owner, Relation<E1, E2> relation, DomainBasedMap<E2> internalMap, KeyFunction<? extends Comparable<?>, E2> mapKey) { this(owner, relation, mapKey, internalMap, null); } /** * Standard creation of a RelationAwareSet when the internalMap instance is unknown. When using this constructor the * internalMapGetter cannot be <code>null</code>. * * @param owner The {@link DomainObject} that owns this collection * @param relation The relation of which this set is a part of * @param internalMapGetter The function that returns the internalMap when it is needed * @param mapKey The function that returns the key used to index this collection */ public RelationAwareSet(E1 owner, Relation<E1, E2> relation, DomainBasedMap.Getter<E2> internalMapGetter, KeyFunction<? extends Comparable<?>, E2> mapKey) { this(owner, relation, mapKey, null, internalMapGetter); if (internalMapGetter == null) { String errorMsg = "internalMapGetter cannot be null when no internalMap is provided"; logger.error(errorMsg); throw new NullPointerException(errorMsg); } } /** * Provide access to the internalMap. This method should be used by any other method that needs to access the internalMap. * * @return The reference to the internal map to use */ protected DomainBasedMap<E2> getInternalMap() { // the only time this can be null is when this RelationAwareSet belongs to an object that was materialized via the // DomainObjectAllocator. In such case, we ask the object for the required collection. Concurrent programming best // practices state that using the local variable is the correct way to do this, due to possible instruction reordering. DomainBasedMap<E2> localRef = internalMap; if (localRef == null) { localRef = reloadInternalMap(); // here we assume that reloadInternalMap will always return the same instance, so at most we're just setting the // same thing repeatedly. internalMap = localRef; } return localRef; } /** * Ask the owner object to give us the collection. * * @return The reference to the collection that holds the objects in this relation. */ // This method can be synchronized to avoid multiple requests trying to reload the same map into memory, in which case it // should compare internalMap == null before hand. Nevertheless, by not using synchronized we are allowing for a lock-free // algorithm, even though at the hypothetical cost of making additional request to the underlying storage system. private DomainBasedMap<E2> reloadInternalMap() { return internalMapGetter.get(); } @Override public boolean justAdd(E2 elem) { return getInternalMap().putIfMissing(mapKey.getKey(elem), elem); } @Override public boolean justRemove(E2 elem) { return getInternalMap().remove(mapKey.getKey(elem)); } @Override public int size() { return getInternalMap().size(); } public E2 get(Comparable<?> key) { return getInternalMap().get(key); } @Override public boolean contains(Object o) { if (o instanceof AbstractDomainObject) { return getInternalMap().contains(mapKey.getKey((E2) o)); } else { return false; } } @Override public Iterator<E2> iterator() { return new RelationAwareIterator(getInternalMap()); } @Override public boolean add(E2 o) { return relation.add(owner, o); } @Override public boolean remove(Object o) { if (o instanceof AbstractDomainObject) { return relation.remove(owner, (E2) o); } else { return false; } } protected class RelationAwareIterator implements Iterator<E2> { private final Iterator<E2> iterator; private E2 current = null; private boolean canRemove = false; RelationAwareIterator(DomainBasedMap<E2> internalMap) { this.iterator = internalMap.iterator(); } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public E2 next() { E2 result = iterator.next(); canRemove = true; current = result; return current; } @Override public void remove() { if (!canRemove) { throw new IllegalStateException(); } else { canRemove = false; relation.remove(owner, current); } } } }