Open Source Repository

Home /hibernate/hibernate-3.2.6.ga | Repository Home



org/hibernate/collection/AbstractPersistentCollection.java
//$Id: AbstractPersistentCollection.java 11301 2007-03-19 20:43:46Z [email protected] $
package org.hibernate.collection;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.LazyInitializationException;
import org.hibernate.engine.CollectionEntry;
import org.hibernate.engine.ForeignKeys;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.TypedValue;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.Type;
import org.hibernate.util.CollectionHelper;
import org.hibernate.util.EmptyIterator;
import org.hibernate.util.MarkerObject;

/**
 * Base class implementing <tt>PersistentCollection</tt>
 @see PersistentCollection
 @author Gavin King
 */
public abstract class AbstractPersistentCollection 
  implements Serializable, PersistentCollection {

  private transient SessionImplementor session;
  private boolean initialized;
  private transient List operationQueue;
  private transient boolean directlyAccessible;
  private transient boolean initializing;
  private Object owner;
  private int cachedSize = -1;
  
  private String role;
  private Serializable key;
  // collections detect changes made via their public interface and mark
  // themselves as dirty as a performance optimization
  private boolean dirty;
  private Serializable storedSnapshot;

  public final String getRole() {
    return role;
  }
  
  public final Serializable getKey() {
    return key;
  }
  
  public final boolean isUnreferenced() {
    return role==null;
  }
  
  public final boolean isDirty() {
    return dirty;
  }
  
  public final void clearDirty() {
    dirty = false;
  }
  
  public final void dirty() {
    dirty = true;
  }
  
  public final Serializable getStoredSnapshot() {
    return storedSnapshot;
  }
  
  //Careful: these methods do not initialize the collection.
  /**
   * Is the initialized collection empty?
   */
  public abstract boolean empty();
  /**
   * Called by any read-only method of the collection interface
   */
  protected final void read() {
    initialize(false);
  }
  /**
   * Called by the <tt>size()</tt> method
   */
  protected boolean readSize() {
    if (!initialized) {
      if cachedSize!=-&& !hasQueuedOperations() ) {
        return true;
      }
      else {
        throwLazyInitializationExceptionIfNotConnected();
        CollectionEntry entry = session.getPersistenceContext().getCollectionEntry(this);
        CollectionPersister persister = entry.getLoadedPersister();
        if persister.isExtraLazy() ) {
          if hasQueuedOperations() ) {
            session.flush();
          }
          cachedSize = persister.getSizeentry.getLoadedKey(), session );
          return true;
        }
      }
    }
    read();
    return false;
  }
  
  protected Boolean readIndexExistence(Object index) {
    if (!initialized) {
      throwLazyInitializationExceptionIfNotConnected();
      CollectionEntry entry = session.getPersistenceContext().getCollectionEntry(this);
      CollectionPersister persister = entry.getLoadedPersister();
      if persister.isExtraLazy() ) {
        if hasQueuedOperations() ) {
          session.flush();
        }
        return new Booleanpersister.indexExistsentry.getLoadedKey(), index, session ) );
      }
    }
    read();
    return null;
    
  }
  
  protected Boolean readElementExistence(Object element) {
    if (!initialized) {
      throwLazyInitializationExceptionIfNotConnected();
      CollectionEntry entry = session.getPersistenceContext().getCollectionEntry(this);
      CollectionPersister persister = entry.getLoadedPersister();
      if persister.isExtraLazy() ) {
        if hasQueuedOperations() ) {
          session.flush();
        }
        return new Booleanpersister.elementExistsentry.getLoadedKey(), element, session ) );
      }
    }
    read();
    return null;
    
  }
  
  protected static final Object UNKNOWN = new MarkerObject("UNKNOWN");
  
  protected Object readElementByIndex(Object index) {
    if (!initialized) {
      throwLazyInitializationExceptionIfNotConnected();
      CollectionEntry entry = session.getPersistenceContext().getCollectionEntry(this);
      CollectionPersister persister = entry.getLoadedPersister();
      if persister.isExtraLazy() ) {
        if hasQueuedOperations() ) {
          session.flush();
        }
        return persister.getElementByIndexentry.getLoadedKey(), index, session, owner );
      }
    }
    read();
    return UNKNOWN;
    
  }
  
  protected int getCachedSize() {
    return cachedSize;
  }
  
  /**
   * Is the collection currently connected to an open session?
   */
  private final boolean isConnectedToSession() {
    return session!=null && 
        session.isOpen() &&
        session.getPersistenceContext().containsCollection(this);
  }

  /**
   * Called by any writer method of the collection interface
   */
  protected final void write() {
    initialize(true);
    dirty();
  }
  /**
   * Is this collection in a state that would allow us to
   * "queue" operations?
   */
  protected boolean isOperationQueueEnabled() {
    return !initialized &&
        isConnectedToSession() &&
        isInverseCollection();
  }
  /**
   * Is this collection in a state that would allow us to
   * "queue" puts? This is a special case, because of orphan
   * delete.
   */
  protected boolean isPutQueueEnabled() {
    return !initialized &&
        isConnectedToSession() &&
        isInverseOneToManyOrNoOrphanDelete();
  }
  /**
   * Is this collection in a state that would allow us to
   * "queue" clear? This is a special case, because of orphan
   * delete.
   */
  protected boolean isClearQueueEnabled() {
    return !initialized &&
        isConnectedToSession() &&
        isInverseCollectionNoOrphanDelete();
  }

  /**
   * Is this the "inverse" end of a bidirectional association?
   */
  private boolean isInverseCollection() {
    CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(this);
    return ce != null && ce.getLoadedPersister().isInverse();
  }

  /**
   * Is this the "inverse" end of a bidirectional association with
   * no orphan delete enabled?
   */
  private boolean isInverseCollectionNoOrphanDelete() {
    CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(this);
    return ce != null && 
        ce.getLoadedPersister().isInverse() &&
        !ce.getLoadedPersister().hasOrphanDelete();
  }

  /**
   * Is this the "inverse" end of a bidirectional one-to-many, or 
   * of a collection with no orphan delete?
   */
  private boolean isInverseOneToManyOrNoOrphanDelete() {
    CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(this);
    return ce != null && ce.getLoadedPersister().isInverse() && (
        ce.getLoadedPersister().isOneToMany() || 
        !ce.getLoadedPersister().hasOrphanDelete() 
      );
  }

  /**
   * Queue an addition
   */
  protected final void queueOperation(Object element) {
    if (operationQueue==nulloperationQueue = new ArrayList(10);
    operationQueue.add(element);
    dirty = true//needed so that we remove this collection from the second-level cache
  }

  /**
   * After reading all existing elements from the database,
   * add the queued elements to the underlying collection.
   */
  protected final void performQueuedOperations() {
    for int i=0; i<operationQueue.size(); i++ ) {
      ( (DelayedOperationoperationQueue.get(i) ).operate();
    }
  }

  /**
   * After flushing, re-init snapshot state.
   */
  public void setSnapshot(Serializable key, String role, Serializable snapshot) {
    this.key = key;
    this.role = role;
    this.storedSnapshot = snapshot;
  }

  /**
   * After flushing, clear any "queued" additions, since the
   * database state is now synchronized with the memory state.
   */
  public void postAction() {
    operationQueue=null;
    cachedSize = -1;
    clearDirty();
  }
  
  /**
   * Not called by Hibernate, but used by non-JDK serialization,
   * eg. SOAP libraries.
   */
  public AbstractPersistentCollection() {}

  protected AbstractPersistentCollection(SessionImplementor session) {
    this.session = session;
  }

  /**
   * return the user-visible collection (or array) instance
   */
  public Object getValue() {
    return this;
  }

  /**
   * Called just before reading any rows from the JDBC result set
   */
  public void beginRead() {
    // override on some subclasses
    initializing = true;
  }

  /**
   * Called after reading all rows from the JDBC result set
   */
  public boolean endRead() {
    //override on some subclasses
    return afterInitialize();
  }
  
  public boolean afterInitialize() {
    setInitialized();
    //do this bit after setting initialized to true or it will recurse
    if (operationQueue!=null) {
      performQueuedOperations();
      operationQueue=null;
      cachedSize = -1;
      return false;
    }
    else {
      return true;
    }
  }

  /**
   * Initialize the collection, if possible, wrapping any exceptions
   * in a runtime exception
   @param writing currently obsolete
   @throws LazyInitializationException if we cannot initialize
   */
  protected final void initialize(boolean writing) {
    if (!initialized) {
      if (initializing) {
        throw new LazyInitializationException("illegal access to loading collection");
      }
      throwLazyInitializationExceptionIfNotConnected();
      session.initializeCollection(this, writing);
    }
  }
  
  private void throwLazyInitializationExceptionIfNotConnected() {
    if !isConnectedToSession() )  {
      throwLazyInitializationException("no session or session was closed");
    }
    if !session.isConnected() ) {
            throwLazyInitializationException("session is disconnected");
    }    
  }
  
  private void throwLazyInitializationException(String message) {
    throw new LazyInitializationException(
        "failed to lazily initialize a collection" 
        role==null ?  "" " of role: " + role 
        ", " + message
      );
  }

  protected final void setInitialized() {
    this.initializing = false;
    this.initialized = true;
  }

  protected final void setDirectlyAccessible(boolean directlyAccessible) {
    this.directlyAccessible = directlyAccessible;
  }

  /**
   * Could the application possibly have a direct reference to
   * the underlying collection implementation?
   */
  public boolean isDirectlyAccessible() {
    return directlyAccessible;
  }

  /**
   * Disassociate this collection from the given session.
   @return true if this was currently associated with the given session
   */
  public final boolean unsetSession(SessionImplementor currentSession) {
    if (currentSession==this.session) {
      this.session=null;
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Associate the collection with the given session.
   @return false if the collection was already associated with the session
   @throws HibernateException if the collection was already associated
   * with another open session
   */
  public final boolean setCurrentSession(SessionImplementor sessionthrows HibernateException {
    if (session==this.session) {
      return false;
    }
    else {
      if isConnectedToSession() ) {
        CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(this);
        if (ce==null) {
          throw new HibernateException(
              "Illegal attempt to associate a collection with two open sessions"
            );
        }
        else {
          throw new HibernateException(
              "Illegal attempt to associate a collection with two open sessions: " +
              MessageHelper.collectionInfoString
                  ce.getLoadedPersister()
                  ce.getLoadedKey()
                  session.getFactory() 
                )
            );
        }
      }
      else {
        this.session = session;
        return true;
      }
    }
  }

  /**
   * Do we need to completely recreate this collection when it changes?
   */
  public boolean needsRecreate(CollectionPersister persister) {
    return false;
  }
  
  /**
   * To be called internally by the session, forcing
   * immediate initialization.
   */
  public final void forceInitialization() throws HibernateException {
    if (!initialized) {
      if (initializing) {
        throw new AssertionFailure("force initialize loading collection");
      }
      if (session==null) {
        throw new HibernateException("collection is not associated with any session");
      }
      if !session.isConnected() ) {
        throw new HibernateException("disconnected session");
      }
      session.initializeCollection(this, false);
    }
  }


  /**
   * Get the current snapshot from the session
   */
  protected final Serializable getSnapshot() {
    return session.getPersistenceContext().getSnapshot(this);
  }

  /**
   * Is this instance initialized?
   */
  public final boolean wasInitialized() {
    return initialized;
  }
  
  public boolean isRowUpdatePossible() {
    return true;
  }

  /**
   * Does this instance have any "queued" additions?
   */
  public final boolean hasQueuedOperations() {
    return operationQueue!=null;
  }
  /**
   * Iterate the "queued" additions
   */
  public final Iterator queuedAdditionIterator() {
    if hasQueuedOperations() ) {
      return new Iterator() {
        int i = 0;
        public Object next() {
          return ( (DelayedOperationoperationQueue.get(i++) ).getAddedInstance();
        }
        public boolean hasNext() {
          return i<operationQueue.size();
        }
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }
    else {
      return EmptyIterator.INSTANCE;
    }
  }
  /**
   * Iterate the "queued" additions
   */
  public final Collection getQueuedOrphans(String entityName) {
    if hasQueuedOperations() ) {
      Collection additions = new ArrayListoperationQueue.size() );
      Collection removals = new ArrayListoperationQueue.size() );
      for int i = 0; i < operationQueue.size(); i++ ) {
        DelayedOperation op = (DelayedOperationoperationQueue.get(i);
        additions.addop.getAddedInstance() );
        removals.addop.getOrphan() );
      }
      return getOrphans(removals, additions, entityName, session);
    }
    else {
      return CollectionHelper.EMPTY_COLLECTION;
    }
  }

  /**
   * Called before inserting rows, to ensure that any surrogate keys
   * are fully generated
   */
  public void preInsert(CollectionPersister persisterthrows HibernateException {}
  /**
   * Called after inserting a row, to fetch the natively generated id
   */
  public void afterRowInsert(CollectionPersister persister, Object entry, int ithrows HibernateException {}
  /**
   * get all "orphaned" elements
   */
  public abstract Collection getOrphans(Serializable snapshot, String entityNamethrows HibernateException;

  /**
   * Get the current session
   */
  public final SessionImplementor getSession() {
    return session;
  }

  final class IteratorProxy implements Iterator {
    private final Iterator iter;
    IteratorProxy(Iterator iter) {
      this.iter=iter;
    }
    public boolean hasNext() {
      return iter.hasNext();
    }

    public Object next() {
      return iter.next();
    }

    public void remove() {
      write();
      iter.remove();
    }

  }

  final class ListIteratorProxy implements ListIterator {
    private final ListIterator iter;
    ListIteratorProxy(ListIterator iter) {
      this.iter = iter;
    }
    public void add(Object o) {
      write();
      iter.add(o);
    }

    public boolean hasNext() {
      return iter.hasNext();
    }

    public boolean hasPrevious() {
      return iter.hasPrevious();
    }

    public Object next() {
      return iter.next();
    }

    public int nextIndex() {
      return iter.nextIndex();
    }

    public Object previous() {
      return iter.previous();
    }

    public int previousIndex() {
      return iter.previousIndex();
    }

    public void remove() {
      write();
      iter.remove();
    }

    public void set(Object o) {
      write();
      iter.set(o);
    }

  }

  class SetProxy implements java.util.Set {

    final Collection set;

    SetProxy(Collection set) {
      this.set=set;
    }
    public boolean add(Object o) {
      write();
      return set.add(o);
    }

    public boolean addAll(Collection c) {
      write();
      return set.addAll(c);
    }

    public void clear() {
      write();
      set.clear();
    }

    public boolean contains(Object o) {
      return set.contains(o);
    }

    public boolean containsAll(Collection c) {
      return set.containsAll(c);
    }

    public boolean isEmpty() {
      return set.isEmpty();
    }

    public Iterator iterator() {
      return new IteratorProxyset.iterator() );
    }

    public boolean remove(Object o) {
      write();
      return set.remove(o);
    }

    public boolean removeAll(Collection c) {
      write();
      return set.removeAll(c);
    }

    public boolean retainAll(Collection c) {
      write();
      return set.retainAll(c);
    }

    public int size() {
      return set.size();
    }

    public Object[] toArray() {
      return set.toArray();
    }

    public Object[] toArray(Object[] array) {
      return set.toArray(array);
    }

  }

  final class ListProxy implements java.util.List {

    private final java.util.List list;

    ListProxy(java.util.List list) {
      this.list = list;
    }

    public void add(int index, Object value) {
      write();
      list.add(index, value);
    }

    /**
     @see java.util.Collection#add(Object)
     */
    public boolean add(Object o) {
      write();
      return list.add(o);
    }

    /**
     @see java.util.Collection#addAll(Collection)
     */
    public boolean addAll(Collection c) {
      write();
      return list.addAll(c);
    }

    /**
     @see java.util.List#addAll(int, Collection)
     */
    public boolean addAll(int i, Collection c) {
      write();
      return list.addAll(i, c);
    }

    /**
     @see java.util.Collection#clear()
     */
    public void clear() {
      write();
      list.clear();
    }

    /**
     @see java.util.Collection#contains(Object)
     */
    public boolean contains(Object o) {
      return list.contains(o);
    }

    /**
     @see java.util.Collection#containsAll(Collection)
     */
    public boolean containsAll(Collection c) {
      return list.containsAll(c);
    }

    /**
     @see java.util.List#get(int)
     */
    public Object get(int i) {
      return list.get(i);
    }

    /**
     @see java.util.List#indexOf(Object)
     */
    public int indexOf(Object o) {
      return list.indexOf(o);
    }

    /**
     @see java.util.Collection#isEmpty()
     */
    public boolean isEmpty() {
      return list.isEmpty();
    }

    /**
     @see java.util.Collection#iterator()
     */
    public Iterator iterator() {
      return new IteratorProxylist.iterator() );
    }

    /**
     @see java.util.List#lastIndexOf(Object)
     */
    public int lastIndexOf(Object o) {
      return list.lastIndexOf(o);
    }

    /**
     @see java.util.List#listIterator()
     */
    public ListIterator listIterator() {
      return new ListIteratorProxylist.listIterator() );
    }

    /**
     @see java.util.List#listIterator(int)
     */
    public ListIterator listIterator(int i) {
      return new ListIteratorProxylist.listIterator(i) );
    }

    /**
     @see java.util.List#remove(int)
     */
    public Object remove(int i) {
      write();
      return list.remove(i);
    }

    /**
     @see java.util.Collection#remove(Object)
     */
    public boolean remove(Object o) {
      write();
      return list.remove(o);
    }

    /**
     @see java.util.Collection#removeAll(Collection)
     */
    public boolean removeAll(Collection c) {
      write();
      return list.removeAll(c);
    }

    /**
     @see java.util.Collection#retainAll(Collection)
     */
    public boolean retainAll(Collection c) {
      write();
      return list.retainAll(c);
    }

    /**
     @see java.util.List#set(int, Object)
     */
    public Object set(int i, Object o) {
      write();
      return list.set(i, o);
    }

    /**
     @see java.util.Collection#size()
     */
    public int size() {
      return list.size();
    }

    /**
     @see java.util.List#subList(int, int)
     */
    public List subList(int i, int j) {
      return list.subList(i, j);
    }

    /**
     @see java.util.Collection#toArray()
     */
    public Object[] toArray() {
      return list.toArray();
    }

    /**
     @see java.util.Collection#toArray(Object[])
     */
    public Object[] toArray(Object[] array) {
      return list.toArray(array);
    }

  }


  protected interface DelayedOperation {
    public void operate();
    public Object getAddedInstance();
    public Object getOrphan();
  }
  
  /**
   * Given a collection of entity instances that used to
   * belong to the collection, and a collection of instances
   * that currently belong, return a collection of orphans
   */
  protected static Collection getOrphans(
      Collection oldElements, 
      Collection currentElements, 
      String entityName, 
      SessionImplementor session)
  throws HibernateException {

    // short-circuit(s)
    if currentElements.size()==return oldElements; // no new elements, the old list contains only Orphans
    if oldElements.size()==0return oldElements; // no old elements, so no Orphans neither
    
    Type idType = session.getFactory().getEntityPersister(entityName).getIdentifierType();

    // create the collection holding the Orphans
    Collection res = new ArrayList();

    // collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access
    java.util.Set currentIds = new HashSet();
    for Iterator it=currentElements.iterator(); it.hasNext()) {
      Object current = it.next();
      if current!=null && ForeignKeys.isNotTransient(entityName, current, null, session) ) {
        Serializable currentId = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, current, session);
        currentIds.addnew TypedValueidType, currentId, session.getEntityMode() ) );
      }
    }

    // iterate over the *old* list
    for Iterator it=oldElements.iterator(); it.hasNext()) {
      Object old = it.next();
      Serializable oldId = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, old, session);
      if !currentIds.containsnew TypedValueidType, oldId, session.getEntityMode() ) ) ) {
        res.add(old);
      }
    }

    return res;
  }

  static void identityRemove(
      Collection list, 
      Object object, 
      String entityName, 
      SessionImplementor session)
  throws HibernateException {

    if object!=null && ForeignKeys.isNotTransient(entityName, object, null, session) ) {
      
      Type idType = session.getFactory().getEntityPersister(entityName).getIdentifierType();

      Serializable idOfCurrent = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, object, session);
      Iterator iter = list.iterator();
      while iter.hasNext() ) {
        Serializable idOfOld = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, iter.next(), session);
        if idType.isEqualidOfCurrent, idOfOld, session.getEntityMode(), session.getFactory() ) ) {
          iter.remove();
          break;
        }
      }

    }
  }
  
  public Object getIdentifier(Object entry, int i) {
    throw new UnsupportedOperationException();
  }
  
  public Object getOwner() {
    return owner;
  }
  
  public void setOwner(Object owner) {
    this.owner = owner;
  }
  
}