Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 15866 invoked from network); 12 Jul 2006 13:34:56 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 12 Jul 2006 13:34:56 -0000 Received: (qmail 26064 invoked by uid 500); 12 Jul 2006 13:34:56 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 26019 invoked by uid 500); 12 Jul 2006 13:34:56 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 26010 invoked by uid 99); 12 Jul 2006 13:34:56 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 12 Jul 2006 06:34:56 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 12 Jul 2006 06:34:46 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id EDCBB1A9846; Wed, 12 Jul 2006 06:34:01 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r421270 [15/23] - in /jackrabbit/trunk/contrib/spi: ./ commons/ commons/src/ commons/src/main/ commons/src/main/java/ commons/src/main/java/org/ commons/src/main/java/org/apache/ commons/src/main/java/org/apache/jackrabbit/ commons/src/main... Date: Wed, 12 Jul 2006 13:33:27 -0000 To: commits@jackrabbit.apache.org From: angela@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060712133401.EDCBB1A9846@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java?rev=421270&view=auto ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java (added) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java Wed Jul 12 06:33:19 2006 @@ -0,0 +1,1394 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.jcr2spi.state; + +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.OrderedMapIterator; +import org.apache.commons.collections.map.LinkedMap; +import org.apache.jackrabbit.util.WeakIdentityCollection; +import org.apache.jackrabbit.spi.IdFactory; +import org.apache.jackrabbit.spi.QNodeDefinition; +import org.apache.jackrabbit.name.Path; +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.spi.NodeId; +import org.apache.jackrabbit.spi.ItemId; +import org.apache.jackrabbit.spi.PropertyId; +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +/** + * NodeState represents the state of a Node. + */ +public class NodeState extends ItemState { + + private static Logger log = LoggerFactory.getLogger(NodeState.class); + + /** + * the name of this node's primary type + */ + private QName nodeTypeName; + + /** + * the names of this node's mixin types + */ + private QName[] mixinTypeNames = new QName[0]; + + /** + * The id of this node state. + */ + private NodeId id; + + /** + * The id of the parent NodeState or null if this + * instance represents the root node. + */ + private NodeId parentId; + + /** + * this node's definition + */ + private QNodeDefinition def; + + /** + * insertion-ordered collection of ChildNodeEntry objects + */ + private ChildNodeEntries childNodeEntries = new ChildNodeEntries(); + + /** + * Set to true if {@link #childNodeEntries} are shared between + * different NodeState instance. + */ + private boolean sharedChildNodeEntries = false; + + /** + * set of property names (QName objects) + */ + private HashSet propertyNames = new HashSet(); + + /** + * Set to true if {@link #propertyNames} is shared between + * different NodeState instances. + */ + private boolean sharedPropertyNames = false; + + /** + * Listeners (weak references) + */ + private final transient Collection listeners = new WeakIdentityCollection(3); + + // DIFF JR: limit creation of property-ids to the nodeState + // TODO: check again.... + private final IdFactory idFactory; + + /** + * Constructs a new NodeState that is initially connected to + * an overlayed state. + * + * @param overlayedState the backing node state being overlayed + * @param initialStatus the initial status of the node state object + * @param isTransient flag indicating whether this state is transient or not + */ + public NodeState(NodeState overlayedState, int initialStatus, + boolean isTransient) { + super(overlayedState, initialStatus, isTransient); + pull(); + idFactory = overlayedState.idFactory; + } + + /** + * Constructs a new node state that is not connected. + * + * @param id id of this node + * @param nodeTypeName node type of this node + * @param parentId id of the parent node + * @param initialStatus the initial status of the node state object + * @param isTransient flag indicating whether this state is transient or not + */ + public NodeState(NodeId id, QName nodeTypeName, NodeId parentId, + int initialStatus, boolean isTransient, IdFactory idFactory) { + super(initialStatus, isTransient); + this.id = id; + this.parentId = parentId; + this.nodeTypeName = nodeTypeName; + this.idFactory = idFactory; + } + + /** + * {@inheritDoc} + */ + protected synchronized void copy(ItemState state) { + synchronized (state) { + NodeState nodeState = (NodeState) state; + id = nodeState.id; + parentId = nodeState.parentId; + nodeTypeName = nodeState.nodeTypeName; + mixinTypeNames = nodeState.mixinTypeNames; + def = nodeState.getDefinition(); + propertyNames = nodeState.propertyNames; + sharedPropertyNames = true; + nodeState.sharedPropertyNames = true; + childNodeEntries = nodeState.childNodeEntries; + sharedChildNodeEntries = true; + nodeState.sharedChildNodeEntries = true; + } + } + + //-----------------------------------------------------< public methods >--- + /** + * Determines if this item state represents a node. + * + * @return always true + * @see ItemState#isNode + */ + public final boolean isNode() { + return true; + } + + /** + * {@inheritDoc} + */ + public NodeId getParentId() { + return parentId; + } + + /** + * {@inheritDoc} + */ + public ItemId getId() { + return id; + } + + /** + * Returns the id of this node state. + * @return the id of this node state. + */ + public NodeId getNodeId() { + return id; + } + + /** + * Sets the Id of the parent NodeState. + * + * @param parentId the parent NodeState's Id or null + * if either this node state should represent the root node or this node + * state should be 'free floating', i.e. detached from the repository's + * hierarchy. + */ + public void setParentId(NodeId parentId) { + this.parentId = parentId; + } + + /** + * Returns the name of this node's node type. + * + * @return the name of this node's node type. + */ + public QName getNodeTypeName() { + return nodeTypeName; + } + + /** + * Returns the names of this node's mixin types. + * + * @return a set of the names of this node's mixin types. + */ + public synchronized QName[] getMixinTypeNames() { + return mixinTypeNames; + } + + /** + * Sets the names of this node's mixin types. + * + * @param mixinTypeNames set of names of mixin types + */ + public synchronized void setMixinTypeNames(QName[] mixinTypeNames) { + if (mixinTypeNames != null) { + this.mixinTypeNames = mixinTypeNames; + } else { + this.mixinTypeNames = new QName[0]; + } + } + + /** + * Return all nodetype names that apply to this NodeState + * including the primary nodetype and the mixins. + * + * @return + */ + public synchronized QName[] getNodeTypeNames() { + // mixin types + QName[] types = new QName[mixinTypeNames.length + 1]; + System.arraycopy(mixinTypeNames, 0, types, 0, mixinTypeNames.length); + // primary type + types[types.length - 1] = getNodeTypeName(); + return types; + } + + /** + * Returns the id of the definition applicable to this node state. + * + * @return the id of the definition + */ + public QNodeDefinition getDefinition() { + return def; + } + + /** + * Sets the id of the definition applicable to this node state. + * + * @param def the definition + */ + public void setDefinition(QNodeDefinition def) { + this.def = def; + } + + /** + * Determines if there are any child node entries. + * + * @return true if there are child node entries, + * false otherwise. + */ + public boolean hasChildNodeEntries() { + return !childNodeEntries.isEmpty(); + } + + /** + * Determines if there is a ChildNodeEntry with the + * specified name. + * + * @param name QName object specifying a node name + * @return true if there is a ChildNodeEntry with + * the specified name. + */ + public synchronized boolean hasChildNodeEntry(QName name) { + return !childNodeEntries.get(name).isEmpty(); + } + + /** + * Determines if there is a ChildNodeEntry with the + * specified NodeId. + * + * @param id the id of the child node + * @return true if there is a ChildNodeEntry with + * the specified name. + */ + public synchronized boolean hasChildNodeEntry(NodeId id) { + return childNodeEntries.get(id) != null; + } + + /** + * Determines if there is a ChildNodeEntry with the + * specified name and index. + * + * @param name QName object specifying a node name + * @param index 1-based index if there are same-name child node entries + * @return true if there is a ChildNodeEntry with + * the specified name and index. + */ + public synchronized boolean hasChildNodeEntry(QName name, int index) { + return childNodeEntries.get(name, index) != null; + } + + /** + * Determines if there is a property entry with the specified + * QName. + * + * @param propName QName object specifying a property name + * @return true if there is a property entry with the specified + * QName. + */ + public synchronized boolean hasPropertyName(QName propName) { + return propertyNames.contains(propName); + } + + /** + * Return the id of the Property with the specified name or null + * if no property exists with the given name. + * + * @param propName + * @return + */ + public synchronized PropertyId getPropertyId(QName propName) { + if (hasPropertyName(propName)) { + return idFactory.createPropertyId(id, propName); + } else { + return null; + } + } + + /** + * Returns the ChildNodeEntry with the specified name and index + * or null if there's no matching entry. + * + * @param nodeName QName object specifying a node name + * @param index 1-based index if there are same-name child node entries + * @return the ChildNodeEntry with the specified name and index + * or null if there's no matching entry. + */ + public synchronized ChildNodeEntry getChildNodeEntry(QName nodeName, int index) { + return childNodeEntries.get(nodeName, index); + } + + /** + * Returns the ChildNodeEntry with the specified NodeId or + * null if there's no matching entry. + * + * @param id the id of the child node + * @return the ChildNodeEntry with the specified NodeId or + * null if there's no matching entry. + * @see #addChildNodeEntry + * @see #removeChildNodeEntry + */ + public synchronized ChildNodeEntry getChildNodeEntry(NodeId id) { + return childNodeEntries.get(id); + } + + /** + * Returns a list of ChildNodeEntry objects denoting the + * child nodes of this node. + * + * @return list of ChildNodeEntry objects + * @see #addChildNodeEntry + * @see #removeChildNodeEntry + */ + public synchronized List getChildNodeEntries() { + return childNodeEntries; + } + + /** + * Returns a list of ChildNodeEntrys with the specified name. + * + * @param nodeName name of the child node entries that should be returned + * @return list of ChildNodeEntry objects + * @see #addChildNodeEntry + * @see #removeChildNodeEntry + */ + public synchronized List getChildNodeEntries(QName nodeName) { + return childNodeEntries.get(nodeName); + } + + /** + * Adds a new ChildNodeEntry. + * + * @param nodeName QName object specifying the name of the new entry. + * @param id the id the new entry is refering to. + * @return the newly added ChildNodeEntry + */ + public synchronized ChildNodeEntry addChildNodeEntry(QName nodeName, + NodeId id) { + if (sharedChildNodeEntries) { + childNodeEntries = (ChildNodeEntries) childNodeEntries.clone(); + sharedChildNodeEntries = false; + } + ChildNodeEntry entry = childNodeEntries.add(nodeName, id); + notifyNodeAdded(entry); + return entry; + } + + /** + * Renames a new ChildNodeEntry. + * + * @param oldName QName object specifying the entry's old name + * @param index 1-based index if there are same-name child node entries + * @param newName QName object specifying the entry's new name + * @return true if the entry was sucessfully renamed; + * otherwise false + */ + public synchronized boolean renameChildNodeEntry(QName oldName, int index, + QName newName) { + if (sharedChildNodeEntries) { + childNodeEntries = (ChildNodeEntries) childNodeEntries.clone(); + sharedChildNodeEntries = false; + } + ChildNodeEntry oldEntry = childNodeEntries.remove(oldName, index); + if (oldEntry != null) { + ChildNodeEntry newEntry = + childNodeEntries.add(newName, oldEntry.getId()); + notifyNodeAdded(newEntry); + notifyNodeRemoved(oldEntry); + return true; + } + return false; + } + + /** + * Removes a ChildNodeEntry. + * + * @param nodeName ChildNodeEntry object specifying a node name + * @param index 1-based index if there are same-name child node entries + * @return true if the specified child node entry was found + * in the list of child node entries and could be removed. + */ + public synchronized boolean removeChildNodeEntry(QName nodeName, int index) { + if (sharedChildNodeEntries) { + childNodeEntries = (ChildNodeEntries) childNodeEntries.clone(); + sharedChildNodeEntries = false; + } + ChildNodeEntry entry = childNodeEntries.remove(nodeName, index); + if (entry != null) { + notifyNodeRemoved(entry); + } + return entry != null; + } + + /** + * Removes a ChildNodeEntry. + * + * @param id the id of the entry to be removed + * @return true if the specified child node entry was found + * in the list of child node entries and could be removed. + */ + public synchronized boolean removeChildNodeEntry(NodeId id) { + if (sharedChildNodeEntries) { + childNodeEntries = (ChildNodeEntries) childNodeEntries.clone(); + sharedChildNodeEntries = false; + } + ChildNodeEntry entry = childNodeEntries.remove(id); + if (entry != null) { + notifyNodeRemoved(entry); + } + return entry != null; + } + + /** + * Removes all ChildNodeEntrys. + */ + public synchronized void removeAllChildNodeEntries() { + if (sharedChildNodeEntries) { + childNodeEntries = (ChildNodeEntries) childNodeEntries.clone(); + sharedChildNodeEntries = false; + } + childNodeEntries.removeAll(); + } + + /** + * Sets the list of ChildNodeEntry objects denoting the + * child nodes of this node. + */ + public synchronized void setChildNodeEntries(List nodeEntries) { + if (nodeEntries instanceof ChildNodeEntries) { + // optimization + ChildNodeEntries entries = (ChildNodeEntries) nodeEntries; + childNodeEntries = (ChildNodeEntries) entries.clone(); + sharedChildNodeEntries = false; + } else { + if (sharedChildNodeEntries) { + childNodeEntries = new ChildNodeEntries(); + sharedChildNodeEntries = false; + } else { + childNodeEntries.removeAll(); + } + childNodeEntries.addAll(nodeEntries); + + } + notifyNodesReplaced(); + } + + /** + * Returns the names of this node's properties as a set of + * QNames objects. + * + * @return set of QNames objects + * @see #addPropertyName + * @see #removePropertyName + */ + public synchronized Set getPropertyNames() { + return Collections.unmodifiableSet(propertyNames); + } + + /** + * Adds a property name entry. + * + * @param propName QName object specifying the property name + */ + public synchronized void addPropertyName(QName propName) { + if (sharedPropertyNames) { + propertyNames = (HashSet) propertyNames.clone(); + sharedPropertyNames = false; + } + propertyNames.add(propName); + } + + /** + * Removes a property name entry. + * + * @param propName QName object specifying the property name + * @return true if the specified property name was found + * in the list of property name entries and could be removed. + */ + public synchronized boolean removePropertyName(QName propName) { + if (sharedPropertyNames) { + propertyNames = (HashSet) propertyNames.clone(); + sharedPropertyNames = false; + } + return propertyNames.remove(propName); + } + + /** + * Removes all property name entries. + */ + public synchronized void removeAllPropertyNames() { + if (sharedPropertyNames) { + propertyNames = new HashSet(); + sharedPropertyNames = false; + } else { + propertyNames.clear(); + } + } + + /** + * Sets the set of QName objects denoting the + * properties of this node. + */ + public synchronized void setPropertyNames(Set propNames) { + if (propNames instanceof HashSet) { + HashSet names = (HashSet) propNames; + propertyNames = (HashSet) names.clone(); + sharedPropertyNames = false; + } else { + if (sharedPropertyNames) { + propertyNames = new HashSet(); + sharedPropertyNames = false; + } else { + propertyNames.clear(); + } + propertyNames.addAll(propNames); + } + } + + /** + * Set the node type name. Needed for deserialization and should therefore + * not change the internal status. + * + * @param nodeTypeName node type name + */ + public synchronized void setNodeTypeName(QName nodeTypeName) { + this.nodeTypeName = nodeTypeName; + } + + //---------------------------------------------------------< diff methods > + /** + * Returns a set of QNames denoting those properties that + * do not exist in the overlayed node state but have been added to + * this node state. + * + * @return set of QNames denoting the properties that have + * been added. + */ + public synchronized Set getAddedPropertyNames() { + if (!hasOverlayedState()) { + return Collections.unmodifiableSet(propertyNames); + } + + NodeState other = (NodeState) getOverlayedState(); + HashSet set = new HashSet(propertyNames); + set.removeAll(other.propertyNames); + return set; + } + + /** + * Returns a list of child node entries that do not exist in the overlayed + * node state but have been added to this node state. + * + * @return list of added child node entries + */ + public synchronized List getAddedChildNodeEntries() { + if (!hasOverlayedState()) { + return childNodeEntries; + } + + NodeState other = (NodeState) getOverlayedState(); + return childNodeEntries.removeAll(other.childNodeEntries); + } + + /** + * Returns a set of QNames denoting those properties that + * exist in the overlayed node state but have been removed from + * this node state. + * + * @return set of QNames denoting the properties that have + * been removed. + */ + public synchronized Set getRemovedPropertyNames() { + if (!hasOverlayedState()) { + return Collections.EMPTY_SET; + } + + NodeState other = (NodeState) getOverlayedState(); + HashSet set = new HashSet(other.propertyNames); + set.removeAll(propertyNames); + return set; + } + + /** + * Returns a list of child node entries, that exist in the overlayed node state + * but have been removed from this node state. + * + * @return list of removed child node entries + */ + public synchronized List getRemovedChildNodeEntries() { + if (!hasOverlayedState()) { + return Collections.EMPTY_LIST; + } + + NodeState other = (NodeState) getOverlayedState(); + return other.childNodeEntries.removeAll(childNodeEntries); + } + + /** + * Returns a list of child node entries that exist both in this node + * state and in the overlayed node state but have been reordered. + *

+ * The list may include only the minimal set of nodes that have been + * reordered. That is, even though a certain number of nodes have changed + * their absolute position the list may include less that this number of + * nodes. + *

+ * Example:
+ * Initial state: + *

+     *  + node1
+     *  + node2
+     *  + node3
+     * 
+ * After reorder: + *
+     *  + node2
+     *  + node3
+     *  + node1
+     * 
+ * All nodes have changed their absolute position. The returned list however + * may only return that node1 has been reordered (from the + * first position to the end). + * + * @return list of reordered child node enties. + */ + public synchronized List getReorderedChildNodeEntries() { + if (!hasOverlayedState()) { + return Collections.EMPTY_LIST; + } + + ChildNodeEntries otherChildNodeEntries = + ((NodeState) overlayedState).childNodeEntries; + + if (childNodeEntries.isEmpty() + || otherChildNodeEntries.isEmpty()) { + return Collections.EMPTY_LIST; + } + + // build intersections of both collections, + // each preserving their relative order + List ours = childNodeEntries.retainAll(otherChildNodeEntries); + List others = otherChildNodeEntries.retainAll(childNodeEntries); + + // do a lazy init + List reordered = null; + // both entry lists now contain the set of nodes that have not + // been removed or added, but they may have changed their position. + for (int i = 0; i < ours.size();) { + ChildNodeEntry entry = (ChildNodeEntry) ours.get(i); + ChildNodeEntry other = (ChildNodeEntry) others.get(i); + if (entry == other || entry.getId().equals(other.getId())) { + // no reorder, move to next child entry + i++; + } else { + // reordered entry detected + if (reordered == null) { + reordered = new ArrayList(); + } + // Note that this check will not necessarily find the + // minimal reorder operations required to convert the overlayed + // child node entries into the current. + + // is there a next entry? + if (i + 1 < ours.size()) { + // if entry is the next in the other list then probably + // the other entry at position i was reordered + if (entry.getId().equals(((ChildNodeEntry) others.get(i + 1)).getId())) { + // scan for the uuid of the other entry in our list + for (int j = i; j < ours.size(); j++) { + if (((ChildNodeEntry) ours.get(j)).getId().equals(other.getId())) { + // found it + entry = (ChildNodeEntry) ours.get(j); + break; + } + } + } + } + + reordered.add(entry); + // remove the entry from both lists + // entries > i are already cleaned + for (int j = i; j < ours.size(); j++) { + if (((ChildNodeEntry) ours.get(j)).getId().equals(entry.getId())) { + ours.remove(j); + } + } + for (int j = i; j < ours.size(); j++) { + if (((ChildNodeEntry) others.get(j)).getId().equals(entry.getId())) { + others.remove(j); + } + } + // if a reorder has been detected index i is not + // incremented because entries will be shifted when the + // reordered entry is removed. + } + } + if (reordered == null) { + return Collections.EMPTY_LIST; + } else { + return reordered; + } + } + + //--------------------------------------------------< ItemState overrides > + /** + * {@inheritDoc} + *

+ * If the listener passed is at the same time a NodeStateListener + * we add it to our list of specialized listeners. + */ + public void addListener(ItemStateListener listener) { + if (listener instanceof NodeStateListener) { + synchronized (listeners) { + if (listeners.contains(listener)) { + log.debug("listener already registered: " + listener); + // no need to add to call ItemState.addListener() + return; + } else { + listeners.add(listener); + } + } + } + super.addListener(listener); + } + + /** + * {@inheritDoc} + *

+ * If the listener passed is at the same time a NodeStateListener + * we remove it from our list of specialized listeners. + */ + public void removeListener(ItemStateListener listener) { + if (listener instanceof NodeStateListener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + super.removeListener(listener); + } + + //-------------------------------------------------< misc. helper methods > + /** + * Notify the listeners that a child node entry has been added + */ + protected void notifyNodeAdded(ChildNodeEntry added) { + synchronized (listeners) { + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + NodeStateListener l = (NodeStateListener) iter.next(); + if (l != null) { + l.nodeAdded(this, added.getName(), added.getIndex(), added.getId()); + } + } + } + } + + /** + * Notify the listeners that the child node entries have been replaced + */ + protected void notifyNodesReplaced() { + synchronized (listeners) { + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + NodeStateListener l = (NodeStateListener) iter.next(); + if (l != null) { + l.nodesReplaced(this); + } + } + } + } + + /** + * Notify the listeners that a child node entry has been removed + */ + protected void notifyNodeRemoved(ChildNodeEntry removed) { + synchronized (listeners) { + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + NodeStateListener l = (NodeStateListener) iter.next(); + if (l != null) { + l.nodeRemoved(this, removed.getName(), + removed.getIndex(), removed.getId()); + } + } + } + } + + //--------------------------------------------------------< inner classes > + /** + * ChildNodeEntries represents an insertion-ordered + * collection of ChildNodeEntrys that also maintains + * the index values of same-name siblings on insertion and removal. + *

+ * ChildNodeEntries also provides an unmodifiable + * List view. + */ + private static class ChildNodeEntries implements List, Cloneable { + + // insertion-ordered map of entries (key=NodeId, value=entry) + private LinkedMap entries; + // map used for lookup by name + // (key=name, value=either a single entry or a list of sns entries) + private HashMap nameMap; + + ChildNodeEntries() { + entries = new LinkedMap(); + nameMap = new HashMap(); + } + + ChildNodeEntry get(NodeId id) { + return (ChildNodeEntry) entries.get(id); + } + + List get(QName nodeName) { + Object obj = nameMap.get(nodeName); + if (obj == null) { + return Collections.EMPTY_LIST; + } + if (obj instanceof ArrayList) { + // map entry is a list of siblings + return Collections.unmodifiableList((ArrayList) obj); + } else { + // map entry is a single child node entry + return Collections.singletonList(obj); + } + } + + ChildNodeEntry get(QName nodeName, int index) { + if (index < Path.INDEX_DEFAULT) { + throw new IllegalArgumentException("index is 1-based"); + } + + Object obj = nameMap.get(nodeName); + if (obj == null) { + return null; + } + if (obj instanceof ArrayList) { + // map entry is a list of siblings + ArrayList siblings = (ArrayList) obj; + if (index <= siblings.size()) { + return (ChildNodeEntry) siblings.get(index - 1); + } + } else { + // map entry is a single child node entry + if (index == Path.INDEX_DEFAULT) { + return (ChildNodeEntry) obj; + } + } + return null; + } + + ChildNodeEntry add(QName nodeName, NodeId id) { + List siblings = null; + int index = Path.INDEX_UNDEFINED; + Object obj = nameMap.get(nodeName); + if (obj != null) { + if (obj instanceof ArrayList) { + // map entry is a list of siblings + siblings = (ArrayList) obj; + } else { + // map entry is a single child node entry, + // convert to siblings list + siblings = new ArrayList(); + siblings.add(obj); + nameMap.put(nodeName, siblings); + } + index = siblings.size(); + } + index++; + + ChildNodeEntry entry = new ChildNodeEntry(nodeName, id, index); + if (siblings != null) { + siblings.add(entry); + } else { + nameMap.put(nodeName, entry); + } + entries.put(id, entry); + + return entry; + } + + void addAll(List entriesList) { + Iterator iter = entriesList.iterator(); + while (iter.hasNext()) { + ChildNodeEntry entry = (ChildNodeEntry) iter.next(); + // delegate to add(QName, String) to maintain consistency + add(entry.getName(), entry.getId()); + } + } + + public ChildNodeEntry remove(QName nodeName, int index) { + if (index < Path.INDEX_DEFAULT) { + throw new IllegalArgumentException("index is 1-based"); + } + + Object obj = nameMap.get(nodeName); + if (obj == null) { + return null; + } + + if (obj instanceof ChildNodeEntry) { + // map entry is a single child node entry + if (index != Path.INDEX_DEFAULT) { + return null; + } + ChildNodeEntry removedEntry = (ChildNodeEntry) obj; + nameMap.remove(nodeName); + entries.remove(removedEntry.getId()); + return removedEntry; + } + + // map entry is a list of siblings + List siblings = (ArrayList) obj; + if (index > siblings.size()) { + return null; + } + + // remove from siblings list + ChildNodeEntry removedEntry = (ChildNodeEntry) siblings.remove(index - 1); + // remove from ordered entries map + entries.remove(removedEntry.getId()); + + // update indices of subsequent same-name siblings + for (int i = index - 1; i < siblings.size(); i++) { + ChildNodeEntry oldEntry = (ChildNodeEntry) siblings.get(i); + ChildNodeEntry newEntry = new ChildNodeEntry(nodeName, oldEntry.getId(), oldEntry.getIndex() - 1); + // overwrite old entry with updated entry in siblings list + siblings.set(i, newEntry); + // overwrite old entry with updated entry in ordered entries map + entries.put(newEntry.getId(), newEntry); + } + + // clean up name lookup map if necessary + if (siblings.size() == 0) { + // no more entries with that name left: + // remove from name lookup map as well + nameMap.remove(nodeName); + } else if (siblings.size() == 1) { + // just one entry with that name left: + // discard siblings list and update name lookup map accordingly + nameMap.put(nodeName, siblings.get(0)); + } + + // we're done + return removedEntry; + } + + /** + * Removes the child node entry refering to the node with the given id. + * + * @param id id of node whose entry is to be removed. + * @return the removed entry or null if there is no such entry. + */ + ChildNodeEntry remove(NodeId id) { + ChildNodeEntry entry = (ChildNodeEntry) entries.get(id); + if (entry != null) { + return remove(entry.getName(), entry.getIndex()); + } + return entry; + } + + /** + * Removes the given child node entry. + * + * @param entry entry to be removed. + * @return the removed entry or null if there is no such entry. + */ + public ChildNodeEntry remove(ChildNodeEntry entry) { + return remove(entry.getName(), entry.getIndex()); + } + + /** + * Removes all child node entries + */ + public void removeAll() { + nameMap.clear(); + entries.clear(); + } + + /** + * Returns a list of ChildNodeEntrys who do only exist in + * this but not in other. + *

+ * Note that two entries are considered identical in this context if + * they have the same name and uuid, i.e. the index is disregarded + * whereas ChildNodeEntry.equals(Object) also compares + * the index. + * + * @param other entries to be removed + * @return a new list of those entries that do only exist in + * this but not in other + */ + List removeAll(ChildNodeEntries other) { + if (entries.isEmpty()) { + return Collections.EMPTY_LIST; + } + if (other.isEmpty()) { + return this; + } + + List result = new ArrayList(); + Iterator iter = iterator(); + while (iter.hasNext()) { + ChildNodeEntry entry = (ChildNodeEntry) iter.next(); + ChildNodeEntry otherEntry = other.get(entry.getId()); + if (entry == otherEntry) { + continue; + } + if (otherEntry == null || !entry.getName().equals(otherEntry.getName())) { + result.add(entry); + } + } + + return result; + } + + /** + * Returns a list of ChildNodeEntrys who do exist in + * this and in other. + *

+ * Note that two entries are considered identical in this context if + * they have the same name and uuid, i.e. the index is disregarded + * whereas ChildNodeEntry.equals(Object) also compares + * the index. + * + * @param other entries to be retained + * @return a new list of those entries that do exist in + * this and in other + */ + List retainAll(ChildNodeEntries other) { + if (entries.isEmpty() + || other.isEmpty()) { + return Collections.EMPTY_LIST; + } + + List result = new ArrayList(); + Iterator iter = iterator(); + while (iter.hasNext()) { + ChildNodeEntry entry = (ChildNodeEntry) iter.next(); + ChildNodeEntry otherEntry = other.get(entry.getId()); + if (entry == otherEntry) { + result.add(entry); + } else if (otherEntry != null + && entry.getName().equals(otherEntry.getName())) { + result.add(entry); + } + } + + return result; + } + + //-------------------------------------------< unmodifiable List view > + public boolean contains(Object o) { + if (o instanceof ChildNodeEntry) { + return entries.containsKey(((ChildNodeEntry) o).getId()); + } else { + return false; + } + } + + public boolean containsAll(Collection c) { + Iterator iter = c.iterator(); + while (iter.hasNext()) { + if (!contains(iter.next())) { + return false; + } + } + return true; + } + + public Object get(int index) { + return entries.getValue(index); + } + + public int indexOf(Object o) { + if (o instanceof ChildNodeEntry) { + return entries.indexOf(((ChildNodeEntry) o).getId()); + } else { + return -1; + } + } + + public boolean isEmpty() { + return entries.isEmpty(); + } + + public int lastIndexOf(Object o) { + // entries are unique + return indexOf(o); + } + + public Iterator iterator() { + return new EntriesIterator(); + } + + public ListIterator listIterator() { + return new EntriesIterator(); + } + + public ListIterator listIterator(int index) { + if (index < 0 || index >= entries.size()) { + throw new IndexOutOfBoundsException(); + } + ListIterator iter = new EntriesIterator(); + while (index-- > 0) { + iter.next(); + } + return iter; + } + + public int size() { + return entries.size(); + } + + public List subList(int fromIndex, int toIndex) { + // @todo FIXME does not fulfil the contract of List.subList(int,int) + return Collections.unmodifiableList(new ArrayList(this).subList(fromIndex, toIndex)); + } + + public Object[] toArray() { + ChildNodeEntry[] array = new ChildNodeEntry[size()]; + return toArray(array); + } + + public Object[] toArray(Object[] a) { + if (!a.getClass().getComponentType().isAssignableFrom(ChildNodeEntry.class)) { + throw new ArrayStoreException(); + } + if (a.length < size()) { + a = new ChildNodeEntry[size()]; + } + MapIterator iter = entries.mapIterator(); + int i = 0; + while (iter.hasNext()) { + iter.next(); + a[i] = entries.getValue(i); + i++; + } + while (i < a.length) { + a[i++] = null; + } + return a; + } + + public void add(int index, Object element) { + throw new UnsupportedOperationException(); + } + + public boolean add(Object o) { + throw new UnsupportedOperationException(); + } + + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public Object remove(int index) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public Object set(int index, Object element) { + throw new UnsupportedOperationException(); + } + + //------------------------------------------------< Cloneable support > + /** + * Returns a shallow copy of this ChildNodeEntries instance; + * the entries themselves are not cloned. + * + * @return a shallow copy of this instance. + */ + protected Object clone() { + ChildNodeEntries clone = new ChildNodeEntries(); + clone.entries = (LinkedMap) entries.clone(); + clone.nameMap = new HashMap(nameMap.size()); + for (Iterator it = nameMap.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + Object obj = nameMap.get(key); + if (obj instanceof ArrayList) { + // clone List + obj = ((ArrayList) obj).clone(); + } + clone.nameMap.put(key, obj); + } + return clone; + } + + //----------------------------------------------------< inner classes > + class EntriesIterator implements ListIterator { + + private final OrderedMapIterator mapIter; + + EntriesIterator() { + mapIter = entries.orderedMapIterator(); + } + + public boolean hasNext() { + return mapIter.hasNext(); + } + + public Object next() { + mapIter.next(); + return mapIter.getValue(); + } + + public boolean hasPrevious() { + return mapIter.hasPrevious(); + } + + public int nextIndex() { + return entries.indexOf(mapIter.getKey()) + 1; + } + + public Object previous() { + mapIter.previous(); + return mapIter.getValue(); + } + + public int previousIndex() { + return entries.indexOf(mapIter.getKey()) - 1; + } + + public void add(Object o) { + throw new UnsupportedOperationException(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void set(Object o) { + throw new UnsupportedOperationException(); + } + } + } + + /** + * ChildNodeEntry specifies the name, index (in the case of + * same-name siblings) and the UUID of a child node entry. + *

+ * ChildNodeEntry instances are immutable. + */ + public static final class ChildNodeEntry { + + private int hash = 0; + + private final QName name; + private final int index; // 1-based index for same-name siblings + private final NodeId id; + + private ChildNodeEntry(QName name, NodeId id, int index) { + if (name == null) { + throw new IllegalArgumentException("name can not be null"); + } + this.name = name; + + if (id == null) { + throw new IllegalArgumentException("id can not be null"); + } + this.id = id; + + if (index < Path.INDEX_DEFAULT) { + throw new IllegalArgumentException("index is 1-based"); + } + this.index = index; + } + + public NodeId getId() { + return id; + } + + public QName getName() { + return name; + } + + public int getIndex() { + return index; + } + + //---------------------------------------< java.lang.Object overrides > + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ChildNodeEntry) { + ChildNodeEntry other = (ChildNodeEntry) obj; + return (name.equals(other.name) && id.equals(other.id) + && index == other.index); + } + return false; + } + + public String toString() { + return name.toString() + "[" + index + "] -> " + id; + } + + public int hashCode() { + // ChildNodeEntry is immutable, we can store the computed hash code value + int h = hash; + if (h == 0) { + h = 17; + h = 37 * h + name.hashCode(); + h = 37 * h + id.hashCode(); + h = 37 * h + index; + hash = h; + } + return h; + } + } +} Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeStateListener.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeStateListener.java?rev=421270&view=auto ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeStateListener.java (added) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeStateListener.java Wed Jul 12 06:33:19 2006 @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.jcr2spi.state; + +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.spi.NodeId; + +/** + * Extends the ItemStateListener allowing a client to be + * additionally informed about changes on a NodeState. + * + * @see NodeState#addListener + */ +public interface NodeStateListener extends TransientItemStateListener { + + /** + * Called when a child node has been added + * + * @param state node state that changed + * @param name name of node that was added + * @param index index of new node + * @param id id of new node + */ + void nodeAdded(NodeState state, QName name, int index, NodeId id); + + /** + * Called when the children nodes were replaced by other nodes, typically + * as result of a reorder operation. + * + * @param state node state that changed + */ + void nodesReplaced(NodeState state); + + /** + * Called when a child node has been removed + * + * @param state node state that changed + * @param name name of node that was removed + * @param index index of removed node + * @param id id of removed node + */ + public void nodeRemoved(NodeState state, QName name, int index, NodeId id); +} Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeStateListener.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeStateListener.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java?rev=421270&view=auto ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java (added) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java Wed Jul 12 06:33:19 2006 @@ -0,0 +1,234 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.jcr2spi.state; + +import javax.jcr.PropertyType; +import javax.jcr.ValueFormatException; +import org.apache.jackrabbit.spi.QPropertyDefinition; +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.spi.PropertyId; +import org.apache.jackrabbit.spi.ItemId; +import org.apache.jackrabbit.spi.NodeId; +import org.apache.jackrabbit.value.QValue; + +/** + * PropertyState represents the state of a Property. + */ +public class PropertyState extends ItemState { + + /** + * the id of this property state + */ + private PropertyId id; + + /** + * the internal values + */ + private QValue[] values; + + /** + * the type of this property state + */ + private int type; + + /** + * flag indicating if this is a multivalue property + */ + private boolean multiValued; + + private QPropertyDefinition def; + + /** + * Constructs a new property state that is initially connected to an + * overlayed state. + * + * @param overlayedState the backing property state being overlayed + * @param initialStatus the initial status of the property state object + * @param isTransient flag indicating whether this state is transient or not + */ + public PropertyState(PropertyState overlayedState, int initialStatus, + boolean isTransient) { + super(overlayedState, initialStatus, isTransient); + pull(); + } + + /** + * Create a new PropertyState + * + * @param id id of the property + * @param initialStatus the initial status of the property state object + * @param isTransient flag indicating whether this state is transient or not + */ + public PropertyState(PropertyId id, int initialStatus, boolean isTransient) { + super(initialStatus, isTransient); + this.id = id; + type = PropertyType.UNDEFINED; + values = QValue.EMPTY_ARRAY; + multiValued = false; + } + + /** + * {@inheritDoc} + */ + protected synchronized void copy(ItemState state) { + synchronized (state) { + PropertyState propState = (PropertyState) state; + id = propState.id; + type = propState.type; + def = propState.getDefinition(); + values = propState.values; + multiValued = propState.multiValued; + } + } + + //-------------------------------------------------------< public methods > + /** + * Determines if this item state represents a node. + * + * @return always false + * @see ItemState#isNode + */ + public boolean isNode() { + return false; + } + + /** + * {@inheritDoc} + */ + public ItemId getId() { + return id; + } + + /** + * Returns the identifier of this property. + * + * @return the id of this property. + */ + public PropertyId getPropertyId() { + return id; + } + + /** + * {@inheritDoc} + */ + public NodeId getParentId() { + return id.getParentId(); + } + + /** + * Returns the name of this property. + * + * @return the name of this property. + */ + public QName getName() { + return id.getQName(); + } + + /** + * Sets the type of the property value(s) + * + * @param type the type to be set + * @see PropertyType + */ + public void setType(int type) { + this.type = type; + } + + /** + * Sets the flag indicating whether this property is multi-valued. + * + * @param multiValued flag indicating whether this property is multi-valued + */ + public void setMultiValued(boolean multiValued) { + this.multiValued = multiValued; + } + + /** + * Returns the type of the property value(s). + * + * @return the type of the property value(s). + * @see PropertyType + * @see QPropertyDefinition#getRequiredType() for the type required by the + * property definition. The effective type may differ from the required + * type if the latter is {@link PropertyType#UNDEFINED}. + */ + public int getType() { + return type; + } + + /** + * Returns true if this property is multi-valued, otherwise false. + * + * @return true if this property is multi-valued, otherwise false. + */ + public boolean isMultiValued() { + return multiValued; + } + + /** + * Returns the id of the definition applicable to this property state. + * + * @return the id of the definition + */ + public QPropertyDefinition getDefinition() { + return def; + } + + /** + * Sets the id of the definition applicable to this property state. + * + * @param def the id of the definition + */ + public void setDefinition(QPropertyDefinition def) { + this.def = def; + } + + /** + * Sets the value(s) of this property. + * + * @param values the new values + */ + public void setValues(QValue[] values) { + this.values = values; + } + + /** + * Returns the value(s) of this property. + * + * @return the value(s) of this property. + */ + public QValue[] getValues() { + return values; + } + + /** + * Convenience method for single valued property states. + * + * @return + * @throws ValueFormatException if {@link #isMultiValued()} returns true. + */ + public QValue getValue() throws ValueFormatException { + if (isMultiValued()) { + throw new ValueFormatException("'getValue' may not be called on a multi-valued state."); + } + if (values == null || values.length == 0) { + return null; + } else { + return values[0]; + } + } +} Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url