Package org.apache.jackrabbit.core.state

Source Code of org.apache.jackrabbit.core.state.NodeState$ChildNodeEntries$EntriesIterator

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  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.core.state;

import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.OrderedMapIterator;
import org.apache.commons.collections.map.LinkedMap;
import org.apache.jackrabbit.core.NodeId;
import org.apache.jackrabbit.core.ItemId;
import org.apache.jackrabbit.core.nodetype.NodeDefId;
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.util.WeakIdentityCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
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;

/**
* <code>NodeState</code> represents the state of a <code>Node</code>.
*/
public class NodeState extends ItemState {

    /**
     * Serialization UID of this class.
     */
    static final long serialVersionUID = 4392375681805781770L;

    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 Set mixinTypeNames = Collections.EMPTY_SET;

    /**
     * the id of this node.
     */
    private NodeId id;

    /**
     * the id of the parent node or <code>null</code> if this instance
     * represents the root node
     */
    private NodeId parentId;

    /**
     * id of this node's definition
     */
    private NodeDefId defId;

    /**
     * insertion-ordered collection of ChildNodeEntry objects
     */
    private ChildNodeEntries childNodeEntries = new ChildNodeEntries();

    /**
     * Set to <code>true</code> if {@link #childNodeEntries} are shared between
     * different <code>NodeState</code> instance.
     */
    private boolean sharedChildNodeEntries = false;

    /**
     * set of property names (QName objects)
     */
    private HashSet propertyNames = new HashSet();

    /**
     * Set to <code>true</code> if {@link #propertyNames} is shared between
     * different <code>NodeState</code> instances.
     */
    private boolean sharedPropertyNames = false;

    /**
     * Listeners (weak references)
     */
    private final transient Collection listeners = new WeakIdentityCollection(3);

    /**
     * Constructs a new node state 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();
    }

    /**
     * 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) {
        super(initialStatus, isTransient);
        this.id = id;
        this.parentId = parentId;
        this.nodeTypeName = nodeTypeName;
    }

    /**
     * {@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;
            defId = nodeState.defId;
            propertyNames = nodeState.propertyNames;
            sharedPropertyNames = nodeState.sharedPropertyNames = true;
            childNodeEntries = nodeState.childNodeEntries;
            sharedChildNodeEntries = nodeState.sharedChildNodeEntries = true;
        }
    }

    //-------------------------------------------------------< public methods >
    /**
     * {@inheritDoc}
     *
     * @return always true
     */
    public final boolean isNode() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public NodeId getParentId() {
        return parentId;
    }

    /**
     * {@inheritDoc}
     */
    public ItemId getId() {
        return id;
    }

    /**
     * Returns the identifier of this node.
     *
     * @return the id of this node.
     */
    public NodeId getNodeId() {
        return id;
    }

    /**
     * Sets the id of this node's parent.
     *
     * @param parentId the parent node's id or <code>null</code>
     * if either this node state should represent the root node or this node
     * state should be 'free floating', i.e. detached from the workspace'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 Set getMixinTypeNames() {
        return Collections.unmodifiableSet(mixinTypeNames);
    }

    /**
     * Sets the names of this node's mixin types.
     *
     * @param names set of names of mixin types
     */
    public synchronized void setMixinTypeNames(Set names) {
        if (names instanceof HashSet) {
            mixinTypeNames = (Set) ((HashSet) names).clone();
        } else {
            mixinTypeNames = new HashSet(names);
        }
    }

    /**
     * Returns the id of the definition applicable to this node state.
     *
     * @return the id of the definition
     */
    public NodeDefId getDefinitionId() {
        return defId;
    }

    /**
     * Sets the id of the definition applicable to this node state.
     *
     * @param defId the id of the definition
     */
    public void setDefinitionId(NodeDefId defId) {
        this.defId = defId;
    }

    /**
     * Determines if there are any child node entries.
     *
     * @return <code>true</code> if there are child node entries,
     *         <code>false</code> otherwise.
     */
    public boolean hasChildNodeEntries() {
        return !childNodeEntries.isEmpty();
    }

    /**
     * Determines if there is a <code>ChildNodeEntry</code> with the
     * specified <code>name</code>.
     *
     * @param name <code>QName</code> object specifying a node name
     * @return <code>true</code> if there is a <code>ChildNodeEntry</code> with
     *         the specified <code>name</code>.
     */
    public synchronized boolean hasChildNodeEntry(QName name) {
        return !childNodeEntries.get(name).isEmpty();
    }

    /**
     * Determines if there is a <code>ChildNodeEntry</code> with the
     * specified <code>NodeId</code>.
     *
     * @param id the id of the child node
     * @return <code>true</code> if there is a <code>ChildNodeEntry</code> with
     *         the specified <code>name</code>.
     */
    public synchronized boolean hasChildNodeEntry(NodeId id) {
        return childNodeEntries.get(id) != null;
    }

    /**
     * Determines if there is a <code>ChildNodeEntry</code> with the
     * specified <code>name</code> and <code>index</code>.
     *
     * @param name  <code>QName</code> object specifying a node name
     * @param index 1-based index if there are same-name child node entries
     * @return <code>true</code> if there is a <code>ChildNodeEntry</code> with
     *         the specified <code>name</code> and <code>index</code>.
     */
    public synchronized boolean hasChildNodeEntry(QName name, int index) {
        return childNodeEntries.get(name, index) != null;
    }

    /**
     * Determines if there is a property entry with the specified
     * <code>QName</code>.
     *
     * @param propName <code>QName</code> object specifying a property name
     * @return <code>true</code> if there is a property entry with the specified
     *         <code>QName</code>.
     */
    public synchronized boolean hasPropertyName(QName propName) {
        return propertyNames.contains(propName);
    }

    /**
     * Returns the <code>ChildNodeEntry</code> with the specified name and index
     * or <code>null</code> if there's no matching entry.
     *
     * @param nodeName <code>QName</code> object specifying a node name
     * @param index    1-based index if there are same-name child node entries
     * @return the <code>ChildNodeEntry</code> with the specified name and index
     *         or <code>null</code> if there's no matching entry.
     */
    public synchronized ChildNodeEntry getChildNodeEntry(QName nodeName, int index) {
        return childNodeEntries.get(nodeName, index);
    }

    /**
     * Returns the <code>ChildNodeEntry</code> with the specified <code>NodeId</code> or
     * <code>null</code> if there's no matching entry.
     *
     * @param id the id of the child node
     * @return the <code>ChildNodeEntry</code> with the specified <code>NodeId</code> or
     *         <code>null</code> if there's no matching entry.
     * @see #addChildNodeEntry
     * @see #removeChildNodeEntry
     */
    public synchronized ChildNodeEntry getChildNodeEntry(NodeId id) {
        return childNodeEntries.get(id);
    }

    /**
     * Returns a list of <code>ChildNodeEntry</code> objects denoting the
     * child nodes of this node.
     *
     * @return list of <code>ChildNodeEntry</code> objects
     * @see #addChildNodeEntry
     * @see #removeChildNodeEntry
     */
    public synchronized List getChildNodeEntries() {
        return childNodeEntries;
    }

    /**
     * Returns a list of <code>ChildNodeEntry</code>s with the specified name.
     *
     * @param nodeName name of the child node entries that should be returned
     * @return list of <code>ChildNodeEntry</code> objects
     * @see #addChildNodeEntry
     * @see #removeChildNodeEntry
     */
    public synchronized List getChildNodeEntries(QName nodeName) {
        return childNodeEntries.get(nodeName);
    }

    /**
     * Adds a new <code>ChildNodeEntry</code>.
     *
     * @param nodeName <code>QName</code> object specifying the name of the new entry.
     * @param id the id the new entry is refering to.
     * @return the newly added <code>ChildNodeEntry</code>
     */
    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 <code>ChildNodeEntry</code>.
     *
     * @param oldName <code>QName</code> object specifying the entry's old name
     * @param index   1-based index if there are same-name child node entries
     * @param newName <code>QName</code> object specifying the entry's new name
     * @return <code>true</code> if the entry was sucessfully renamed;
     *         otherwise <code>false</code>
     */
    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 <code>ChildNodeEntry</code>.
     *
     * @param nodeName <code>ChildNodeEntry</code> object specifying a node name
     * @param index    1-based index if there are same-name child node entries
     * @return <code>true</code> 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 <code>ChildNodeEntry</code>.
     *
     * @param id the id of the entry to be removed
     * @return <code>true</code> 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 <code>ChildNodeEntry</code>s.
     */
    public synchronized void removeAllChildNodeEntries() {
        if (sharedChildNodeEntries) {
            childNodeEntries = (ChildNodeEntries) childNodeEntries.clone();
            sharedChildNodeEntries = false;
        }
        childNodeEntries.removeAll();
    }

    /**
     * Sets the list of <code>ChildNodeEntry</code> 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
     * <code>QNames</code> objects.
     *
     * @return set of <code>QNames</code> objects
     * @see #addPropertyName
     * @see #removePropertyName
     */
    public synchronized Set getPropertyNames() {
        return Collections.unmodifiableSet(propertyNames);
    }

    /**
     * Adds a property name entry.
     *
     * @param propName <code>QName</code> 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 <code>QName</code> object specifying the property name
     * @return <code>true</code> 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 <code>QName</code> 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 <code>QName</code>s denoting those properties that
     * do not exist in the overlayed node state but have been added to
     * <i>this</i> node state.
     *
     * @return set of <code>QName</code>s 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 <i>this</i> 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 <code>QName</code>s denoting those properties that
     * exist in the overlayed node state but have been removed from
     * <i>this</i> node state.
     *
     * @return set of <code>QName</code>s 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 <i>this</i> 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 <i>this</i> node
     * state and in the overlayed node state but have been reordered.
     * <p/>
     * 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.
     * <p/>
     * Example:<br/>
     * Initial state:
     * <pre>
     *  + node1
     *  + node2
     *  + node3
     * </pre>
     * After reorder:
     * <pre>
     *  + node2
     *  + node3
     *  + node1
     * </pre>
     * All nodes have changed their absolute position. The returned list however
     * may only return that <code>node1</code> 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 <code>i</code> 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 <code>i</code> 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}
     * <p/>
     * If the listener passed is at the same time a <code>NodeStateListener</code>
     * 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}
     * <p/>
     * If the listener passed is at the same time a <code>NodeStateListener</code>
     * 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());
                }
            }
        }
    }

    //-------------------------------------------------< Serializable support >
    private void writeObject(ObjectOutputStream out) throws IOException {
        // delegate to default implementation
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        // delegate to default implementation
        in.defaultReadObject();
    }

    //--------------------------------------------------------< inner classes >
    /**
     * <code>ChildNodeEntries</code> represents an insertion-ordered
     * collection of <code>ChildNodeEntry</code>s that also maintains
     * the index values of same-name siblings on insertion and removal.
     * <p/>
     * <code>ChildNodeEntries</code> also provides an unmodifiable
     * <code>List</code> view.
     */
    private static class ChildNodeEntries implements List, Cloneable, Serializable {

        // insertion-ordered map of entries (key=NodeId, value=entry)
        LinkedMap entries;
        // map used for lookup by name
        // (key=name, value=either a single entry or a list of sns entries)
        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 < 1) {
                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 == 1) {
                    return (ChildNodeEntry) obj;
                }
            }
            return null;
        }

        ChildNodeEntry add(QName nodeName, NodeId id) {
            List siblings = null;
            int index = 0;
            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 < 1) {
                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 != 1) {
                    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 <code>null</code> 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 <code>null</code> 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 <code>ChildNodeEntry</code>s who do only exist in
         * <code>this</code> but not in <code>other</code>.
         * <p/>
         * 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 <code>ChildNodeEntry.equals(Object)</code> also compares
         * the index.
         *
         * @param other entries to be removed
         * @return a new list of those entries that do only exist in
         *         <code>this</code> but not in <code>other</code>
         */
        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 <code>ChildNodeEntry</code>s who do exist in
         * <code>this</code> <i>and</i> in <code>other</code>.
         * <p/>
         * 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 <code>ChildNodeEntry.equals(Object)</code> also compares
         * the index.
         *
         * @param other entries to be retained
         * @return a new list of those entries that do exist in
         *         <code>this</code> <i>and</i> in <code>other</code>
         */
        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();
        }

        //---------------------------------------------< Serializable support >
        private void writeObject(ObjectOutputStream out) throws IOException {
            // important: fields must be written in same order as they are
            // read in readObject(ObjectInputStream)
            out.writeShort(size()); // count
            for (Iterator iter = iterator(); iter.hasNext();) {
                NodeState.ChildNodeEntry entry =
                        (NodeState.ChildNodeEntry) iter.next();
                out.writeUTF(entry.getName().toString());   // name
                out.writeUTF(entry.getId().toString())// id
            }
        }

        private void readObject(ObjectInputStream in) throws IOException {
            entries = new LinkedMap();
            nameMap = new HashMap();
            // important: fields must be read in same order as they are
            // written in writeObject(ObjectOutputStream)
            short count = in.readShort();   // count
            for (int i = 0; i < count; i++) {
                QName name = QName.valueOf(in.readUTF());    // name
                String s = in.readUTF();   // id
                add(name, NodeId.valueOf(s));
            }
        }

        //------------------------------------------------< Cloneable support >
        /**
         * Returns a shallow copy of this <code>ChildNodeEntries</code> 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 {

            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();
            }
        }
    }

    /**
     * <code>ChildNodeEntry</code> specifies the name, index (in the case of
     * same-name siblings) and the UUID of a child node entry.
     * <p/>
     * <code>ChildNodeEntry</code> 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 < 1) {
                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;
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.state.NodeState$ChildNodeEntries$EntriesIterator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.