Esempio n. 1
0
  /**
   * Return this node or leaf iff it is dirty (aka mutable) and otherwise return a copy of this node
   * or leaf. If a copy is made of the node, then a copy will also be made of each immutable parent
   * up to the first mutable parent or the root of the tree, which ever comes first. If the root is
   * copied, then the new root will be set on the {@link HTree}. This method must MUST be invoked
   * any time an mutative operation is requested for the leaf.
   *
   * <p>Note: You can not modify a node that has been written onto the store. Instead, you have to
   * clone the node causing it and all nodes up to the root to be dirty and transient. This method
   * handles that cloning process, but the caller MUST test whether or not the node was copied by
   * this method, MUST delegate the mutation operation to the copy iff a copy was made, and MUST be
   * aware that the copy exists and needs to be used in place of the immutable version of the node.
   *
   * @param triggeredByChildId The persistent identity of child that triggered this event if any.
   * @return Either this node or a copy of this node.
   */
  protected AbstractPage copyOnWrite(final long triggeredByChildId) {

    //        if (isPersistent()) {
    if (!isReadOnly()) {

      /*
       * Since a clone was not required, we use this as an opportunity to
       * touch the hard reference queue. This helps us to ensure that
       * nodes which have been touched recently will remain strongly
       * reachable.
       */

      htree.touch(this);

      return this;
    }

    if (log.isInfoEnabled()) {
      log.info("this=" + toShortString() + ", trigger=" + triggeredByChildId);
    }

    // cast to mutable implementation class.
    final HTree htree = (HTree) this.htree;

    // identify of the node that is being copied and deleted.
    final long oldId = this.identity;

    assert oldId != NULL;

    // parent of the node that is being cloned (null iff it is the root).
    DirectoryPage parent = this.getParentDirectory();

    // the new node (mutable copy of the old node).
    final AbstractPage newNode;

    if (isLeaf()) {

      newNode = new BucketPage((BucketPage) this);

      htree.getBtreeCounters().leavesCopyOnWrite++;

    } else {

      newNode = new DirectoryPage((DirectoryPage) this, triggeredByChildId);

      htree.getBtreeCounters().nodesCopyOnWrite++;
    }

    // delete this node now that it has been cloned.
    this.delete();

    if (htree.root == this) {

      assert parent == null;

      // Update the root node on the htree.
      if (log.isInfoEnabled()) log.info("Copy-on-write : replaced root node on htree.");

      final boolean wasDirty = htree.root.dirty;

      assert newNode != null;

      htree.root = (DirectoryPage) newNode;

      if (!wasDirty) {

        htree.fireDirtyEvent();
      }

    } else {

      /*
       * Recursive copy-on-write up the tree. This operations stops as
       * soon as we reach a parent node that is already dirty and
       * grounds out at the root in any case.
       */
      assert parent != null;

      if (!parent.isDirty()) {

        /*
         * Note: pass up the identity of the old child since we want
         * to avoid having its parent reference reset.
         */
        parent = (DirectoryPage) parent.copyOnWrite(oldId);
      }

      /*
       * Replace the reference to this child with the reference to the
       * new child. This makes the old child inaccessible via
       * navigation. It will be GCd once it falls off of the hard
       * reference queue.
       */
      parent.replaceChildRef(oldId, newNode);
    }

    return newNode;
  }