Package org.apache.jackrabbit.core.state

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

/*
* Copyright 2004-2005 The Apache Software Foundation or its licensors,
*                     as applicable.
*
* Licensed 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.map.LinkedMap;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.core.NodeId;
import org.apache.jackrabbit.core.nodetype.NodeDefId;
import org.apache.jackrabbit.name.QName;

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.Map;
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 = -4116945555530446652L;

    /** the uuid of this node */
    protected String uuid;

    /** the name of this node's primary type */
    protected QName nodeTypeName;

    /** the names of this node's mixin types */
    protected Set mixinTypeNames = new HashSet();

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

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

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

    /**
     * Listeners (weak references)
     */
    private final transient ReferenceMap listeners =
            new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK);

    /**
     * Constructor
     *
     * @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(initialStatus, isTransient);

        connect(overlayedState);
        pull();
    }

    /**
     * Constructor
     *
     * @param uuid          the UUID of the this node
     * @param nodeTypeName  node type of this node
     * @param parentUUID    the UUID 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(String uuid, QName nodeTypeName, String parentUUID,
                     int initialStatus, boolean isTransient) {
        super(parentUUID, new NodeId(uuid), initialStatus, isTransient);

        this.nodeTypeName = nodeTypeName;
        this.uuid = uuid;
    }

    /**
     * {@inheritDoc}
     */
    protected synchronized void copy(ItemState state) {
        synchronized (state) {
            super.copy(state);

            NodeState nodeState = (NodeState) state;
            nodeTypeName = nodeState.getNodeTypeName();
            mixinTypeNames = new HashSet(nodeState.getMixinTypeNames());
            defId = nodeState.getDefinitionId();
            uuid = nodeState.getUUID();
            propertyNames = new HashSet(nodeState.getPropertyNames());
            childNodeEntries = new ChildNodeEntries();
            childNodeEntries.addAll(nodeState.getChildNodeEntries());
        }
    }

    //-------------------------------------------------------< public methods >
    /**
     * Determines if this item state represents a node.
     *
     * @return always true
     * @see ItemState#isNode
     */
    public final boolean isNode() {
        return true;
    }

    /**
     * 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) {
        mixinTypeNames.clear();
        mixinTypeNames.addAll(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;
    }

    /**
     * Returns the UUID of the repository node this node state is representing.
     *
     * @return the UUID
     */
    public String getUUID() {
        return uuid;
    }

    /**
     * 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>uuid</code>.
     *
     * @param uuid UUID 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(String uuid) {
        return childNodeEntries.get(uuid) != 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 such 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 such entry.
     */
    public synchronized ChildNodeEntry getChildNodeEntry(QName nodeName, int index) {
        return childNodeEntries.get(nodeName, index);
    }

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

    /**
     * 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 uuid     UUID the new entry is refering to.
     * @return the newly added <code>ChildNodeEntry</code>
     */
    public synchronized ChildNodeEntry addChildNodeEntry(QName nodeName,
                                                         String uuid) {
        ChildNodeEntry entry = childNodeEntries.add(nodeName, uuid);
        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) {
        ChildNodeEntry oldEntry = childNodeEntries.remove(oldName, index);
        if (oldEntry != null) {
            ChildNodeEntry newEntry =
                    childNodeEntries.add(newName, oldEntry.getUUID());
            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) {
        ChildNodeEntry entry = childNodeEntries.remove(nodeName, index);
        if (entry != null) {
            notifyNodeRemoved(entry);
        }
        return entry != null;
    }

    /**
     * Removes a <code>ChildNodeEntry</code>.
     *
     * @param uuid UUID 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(String uuid) {
        ChildNodeEntry entry = childNodeEntries.remove(uuid);
        if (entry != null) {
            notifyNodeRemoved(entry);
        }
        return entry != null;
    }

    /**
     * Removes all <code>ChildNodeEntry</code>s.
     */
    public synchronized void removeAllChildNodeEntries() {
        childNodeEntries.removeAll();
    }

    /**
     * Sets the list of <code>ChildNodeEntry</code> objects denoting the
     * child nodes of this node.
     */
    public synchronized void setChildNodeEntries(List nodeEntries) {
        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) {
        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) {
        return propertyNames.remove(propName);
    }

    /**
     * Removes all property name entries.
     */
    public synchronized void removeAllPropertyNames() {
        propertyNames.clear();
    }

    /**
     * Sets the set of <code>QName</code> objects denoting the
     * properties of this node.
     */
    public synchronized void setPropertyNames(Set propNames) {
        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;
        }

        List others = new ArrayList();
        others.addAll(((NodeState) getOverlayedState()).getChildNodeEntries());

        List ours = new ArrayList();
        ours.addAll(childNodeEntries);

        // do a lazy init
        List reordered = null;
        // remove added nodes from 'our' entries
        ours.removeAll(getAddedChildNodeEntries());
        // remove all removed nodes from 'other' entries
        others.removeAll(getRemovedChildNodeEntries());
        // 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.getUUID().equals(other.getUUID())) {
                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.getUUID().equals(((ChildNodeEntry) others.get(i + 1)).getUUID())) {
                        // scan for the uuid of the other entry in our list
                        for (int j = i; j < ours.size(); j++) {
                            if (((ChildNodeEntry) ours.get(j)).getUUID().equals(other.uuid)) {
                                // 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)).getUUID().equals(entry.getUUID())) {
                        ours.remove(j);
                    }
                }
                for (int j = i; j < ours.size(); j++) {
                    if (((ChildNodeEntry) others.get(j)).getUUID().equals(entry.getUUID())) {
                        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.
            } else {
                // no reorder, move to next child entry
                i++;
            }
        }
        if (reordered == null) {
            return Collections.EMPTY_LIST;
        } else {
            return reordered;
        }
    }

    //--------------------------------------------------< ItemState overrides >
    /**
     * {@inheritDoc}
     *
     * 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.containsKey(listener)) {
                    listeners.put(listener, listener);
                }
            }
        }
        super.addListener(listener);
    }

    /**
     * {@inheritDoc}
     *
     * 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) {
            MapIterator iter = listeners.mapIterator();
            while (iter.hasNext()) {
                NodeStateListener l = (NodeStateListener) iter.next();
                if (l != null) {
                    l.nodeAdded(this, added.getName(),
                            added.getIndex(), added.getUUID());
                }
            }
        }
    }

    /**
     * Notify the listeners that the child node entries have been replaced
     */
    protected void notifyNodesReplaced() {
        synchronized (listeners) {
            MapIterator iter = listeners.mapIterator();
            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) {
            MapIterator iter = listeners.mapIterator();
            while (iter.hasNext()) {
                NodeStateListener l = (NodeStateListener) iter.next();
                if (l != null) {
                    l.nodeRemoved(this, removed.getName(),
                            removed.getIndex(), removed.getUUID());
                }
            }
        }
    }

    //-------------------------------------------------< 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, Serializable {

        // insertion-ordered map of entries (key=uuid, value=entry)
        LinkedMap entries;
        // map used for lookup by name (key=name, value=1st same-name sibling entry)
        Map nameMap;

        ChildNodeEntries() {
            entries = new LinkedMap();
            nameMap = new HashMap();
        }

        ChildNodeEntry add(QName nodeName, String uuid) {
            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
            while (sibling != null && sibling.getNextSibling() != null) {
                sibling = sibling.getNextSibling();
            }

            int index = (sibling == null) ? 1 : sibling.getIndex() + 1;

            ChildNodeEntry entry = new ChildNodeEntry(nodeName, uuid, index);
            if (sibling == null) {
                nameMap.put(nodeName, entry);
            } else {
                sibling.setNextSibling(entry);
            }
            entries.put(uuid, 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.getUUID());
            }
        }

        public void removeAll() {
            entries.clear();
            nameMap.clear();
        }

        ChildNodeEntry remove(String uuid) {
            ChildNodeEntry entry = (ChildNodeEntry) entries.get(uuid);
            if (entry != null) {
                return remove(entry.getName(), entry.getIndex());
           }
            return entry;
        }

        public ChildNodeEntry remove(ChildNodeEntry entry) {
            return remove(entry.getName(), entry.getIndex());
        }

        public ChildNodeEntry remove(QName nodeName, int index) {
            if (index < 1) {
                throw new IllegalArgumentException("index is 1-based");
            }

            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
            ChildNodeEntry prevSibling = null;
            while (sibling != null) {
                if (sibling.getIndex() == index) {
                    break;
                }
                prevSibling = sibling;
                sibling = sibling.getNextSibling();
            }
            if (sibling == null) {
                return null;
            }

            // remove from entries list
            entries.remove(sibling.getUUID());

            // update linked list of siblings & name map entry
            if (prevSibling != null) {
                prevSibling.setNextSibling(sibling.getNextSibling());
            } else {
                // the head is removed from the linked siblings list,
                // update name map
                if (sibling.getNextSibling() == null) {
                    nameMap.remove(nodeName);
                } else {
                    nameMap.put(nodeName, sibling.getNextSibling());
                }
            }
            // update indices of subsequent same-name siblings
            ChildNodeEntry nextSibling = sibling.getNextSibling();
            while (nextSibling != null) {
                nextSibling.decIndex();
                nextSibling = nextSibling.getNextSibling();
            }

            return sibling;
        }

        List get(QName nodeName) {
            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
            if (sibling == null) {
                return Collections.EMPTY_LIST;
            }
            List siblings = new ArrayList();
            while (sibling != null) {
                siblings.add(sibling);
                sibling = sibling.getNextSibling();
            }
            return siblings;
        }

        ChildNodeEntry get(String uuid) {
            return (ChildNodeEntry) entries.get(uuid);
        }

        ChildNodeEntry get(QName nodeName, int index) {
            if (index < 1) {
                throw new IllegalArgumentException("index is 1-based");
            }

            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
            while (sibling != null) {
                if (sibling.getIndex() == index) {
                    return sibling;
                }
                sibling = sibling.getNextSibling();
            }
            return null;
        }

        /**
         * 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 = (ChildNodeEntry) other.get(entry.uuid);
                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).uuid);
            } 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).uuid);
            } 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 OrderedMapIterator(entries.asList().listIterator(), entries);
        }

        public ListIterator listIterator() {
            return new OrderedMapIterator(entries.asList().listIterator(), entries);
        }

        public ListIterator listIterator(int index) {
            return new OrderedMapIterator(entries.asList().listIterator(index), entries);
        }

        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.getUUID())// uuid
            }
        }

        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();   // uuid
                add(name, s);
            }
        }

        //----------------------------------------------------< inner classes >
        class OrderedMapIterator implements ListIterator {

            final ListIterator keyIter;
            final Map entries;

            OrderedMapIterator(ListIterator keyIter, Map entries) {
                this.keyIter = keyIter;
                this.entries = entries;
            }

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

            public Object next() {
                return entries.get(keyIter.next());
            }

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

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

            public Object previous() {
                return entries.get(keyIter.previous());
            }

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

            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.
     */
    public static class ChildNodeEntry {

        private QName name;
        private int index; // 1-based index for same-name siblings
        private String uuid;
        private ChildNodeEntry nextSibling;

        private ChildNodeEntry(QName name, String uuid, int index) {
            if (name == null) {
                throw new IllegalArgumentException("name can not be null");
            }
            this.name = name;

            if (uuid == null) {
                throw new IllegalArgumentException("uuid can not be null");
            }
            this.uuid = uuid;

            if (index < 1) {
                throw new IllegalArgumentException("index is 1-based");
            }
            this.index = index;

            nextSibling = null;
        }

        public String getUUID() {
            return uuid;
        }

        public QName getName() {
            return name;
        }

        public int getIndex() {
            return index;
        }

        public ChildNodeEntry getNextSibling() {
            return nextSibling;
        }

        void setNextSibling(ChildNodeEntry nextSibling) {
            if (nextSibling != null && !nextSibling.getName().equals(name)) {
                throw new IllegalArgumentException("not a same-name sibling entry");
            }

            this.nextSibling = nextSibling;
        }

        int incIndex() {
            return ++index;
        }

        int decIndex() {
            if (index == 1) {
                throw new IndexOutOfBoundsException();
            }
            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) && uuid.equals(other.uuid)
                        && index == other.index);
            }
            return false;
        }

        public String toString() {
            return name.toString() + "[" + index + "] -> " + uuid;
        }

        /**
         * Returns zero to satisfy the Object equals/hashCode contract.
         * This class is mutable and not meant to be used as a hash key.
         *
         * @return always zero
         * @see Object#hashCode()
         */
        public int hashCode() {
            return 0;
        }
    }
}
TOP

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

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.