/** * 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; }