package pt.ist.fenixframework.pstm; import dml.runtime.Relation; import jvstm.util.Cons; import jvstm.util.Pair; import java.io.ObjectStreamException; import java.io.Serializable; /** * The OneBoxDomainObject class is meant to be used as a superclass of * DomainObjects generated by the DML compiler that use a single box * (defined in this class) to hold the entire state of a DomainObject. * * This will reduce the amount of memory needed to store each object * (because there are less overheads caused by the vboxes), at the * potential cost of increasing the likelihood of conflicts, because * now the conflict detection granularity is at the object level, * rather than at the slot level. * * This class uses also a lazy approach to the materialization of * RelationLists for holding the set of objects that relate to an * object, so that unused RelationLists don't consume memory * unnecessary. */ public abstract class OneBoxDomainObject extends AbstractDomainObject { // box holding the entire state of the object private final VState obj$state; private static final String OBJ_STATE_SLOT_NAME = "obj$state"; // we need to know whether this instance of the object was created // to materialize an already existing object that is being read // from the persistent store or if it is a new object private final boolean isNewObject; // Collection of RelationLists that are created lazilly private volatile Cons<Pair<String,RelationList>> relationLists = Cons.empty(); // This is the constructor used when a new DomainObject is created protected OneBoxDomainObject() { super(); this.obj$state = VState.makeNew(this, OBJ_STATE_SLOT_NAME, false); this.obj$state.put(this, OBJ_STATE_SLOT_NAME, make$newState()); this.isNewObject = true; create$allLists(); } // This is the constructor used as part of the allocate-instance // protocol, when a DomainObject is being read from the persistent // store protected OneBoxDomainObject(DomainObjectAllocator.OID oid) { super(oid); this.obj$state = VState.makeNew(this, OBJ_STATE_SLOT_NAME, true); this.isNewObject = false; } // each class will have to implement/override this method to // create an instance of the appropriate subclass of DO_State protected abstract DO_State make$newState(); // each class will have to override this method to create all of // the lists corresponding to relations when a new instance is // created protected void create$allLists() { } protected final DO_State get$obj$state(boolean forWriting) { DO_State currState = (DO_State)this.obj$state.get(this, OBJ_STATE_SLOT_NAME); if (forWriting && currState.committed) { DO_State newState = make$newState(); currState.copyTo(newState); this.obj$state.put(this, OBJ_STATE_SLOT_NAME, newState); return newState; } else { return currState; } } protected void readSlotsFromResultSet(java.sql.ResultSet rs, int txNumber) throws java.sql.SQLException { throw new Error("readSlotsFromResultSet should not be used for OneBoxDomainObjects"); } @Override public final void readFromResultSet(java.sql.ResultSet rs) throws java.sql.SQLException { int txNumber = Transaction.current().getNumber(); DO_State loadedState = make$newState(); readStateFromResultSet(rs, loadedState); // this state was already committed and is being read now loadedState.markCommitted(); obj$state.persistentLoad(loadedState, txNumber); } protected abstract void readStateFromResultSet(java.sql.ResultSet rs, DO_State state) throws java.sql.SQLException; public abstract static class DO_State implements Serializable { private boolean committed = false; void markCommitted() { this.committed = true; } protected void copyTo(DO_State newState) { // there is nothing to copy at this level } // serialization code protected Object writeReplace() throws ObjectStreamException { throw new RuntimeException("writeReplace not implemented at this level"); } protected abstract static class SerializedForm implements Serializable { private static final long serialVersionUID = 1L; protected SerializedForm(DO_State obj) { // there are no slots to serialize at this level } Object readResolve() throws ObjectStreamException { throw new RuntimeException("readResolve not implemented at this level"); } // subclasses must implement this method to set in the parameter the de-serialized // fields from the SerializedForm protected void fillInState(DO_State obj) { obj.markCommitted(); // nothing to fill-in at this level } } } @Override VersionedSubject getSlotNamed(String attrName) { if (attrName.equals(OBJ_STATE_SLOT_NAME)) { return obj$state; } Relation rel = get$$relationFor(attrName); if (rel != null) { return get$$relationList(attrName, rel); } else { return super.getSlotNamed(attrName); } } // In the following, we have the methods that handle the lazy creation of the RelationLists protected RelationList get$$relationList(String attrName, Relation relation) { Cons<Pair<String,RelationList>> allLists = relationLists; RelationList list = findRelationList(allLists, Cons.empty(), attrName); if (list != null) { return list; } // if we haven't found the list, then we need to create it first // but we need to ensure that no one is racing to do the same synchronized (this) { // read the list of relationLists again to see if someone // may have changed it in the meanwhile Cons<Pair<String,RelationList>> currentLists = relationLists; if (currentLists != allLists) { // we need to see again if a new list was created list = findRelationList(currentLists, allLists, attrName); if (list != null) { return list; } } // we really need to create the list RelationList newList = new RelationList(this, relation, attrName, (! isNewObject)); relationLists = currentLists.cons(new Pair<String,RelationList>(attrName, newList)); return newList; } } private static RelationList findRelationList(Cons<Pair<String,RelationList>> allLists, Object lastList, String attrName) { while (allLists != lastList) { Pair<String,RelationList> list = allLists.first(); // it is not safe to use == instead of equals(Object) to // compare Strings here because this method may be // called with non-interned strings (when reading the changelogs from DB) if (list.first.equals(attrName)) { return list.second; } allLists = allLists.rest(); } return null; } protected Relation get$$relationFor(String attrName) { return null; } }