org.apache.jackrabbit.jcr2spi.hierarchy.ChildNodeEntriesImpl Maven / Gradle / Ivy
/*
* 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.hierarchy;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
import org.apache.commons.collections.list.AbstractLinkedList;
import org.apache.jackrabbit.jcr2spi.state.Status;
import org.apache.jackrabbit.spi.ChildInfo;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.NodeId;
import org.apache.jackrabbit.spi.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ChildNodeEntriesImpl
implements a memory sensitive implementation
* of the ChildNodeEntries
interface.
*/
final class ChildNodeEntriesImpl implements ChildNodeEntries {
private static Logger log = LoggerFactory.getLogger(ChildNodeEntriesImpl.class);
private boolean complete = false;
/**
* Linked list of {@link NodeEntry} instances.
*/
private final LinkedEntries entries = new LinkedEntries();
/**
* Map used for lookup by name.
*/
private final NameMap entriesByName = new NameMap();
private final NodeEntry parent;
private final EntryFactory factory;
/**
* Create a new ChildNodeEntries
collection from the given
* childNodeInfos
instead of retrieving them from the
* persistent layer.
*
* @param parent
* @param factory
* @param childNodeInfos The complete list of child infos or
* null
if an 'empty' ChildNodeEntriesImpl should be created.
* In the latter case, individual child entries will be added on demand
* and the complete list will be retrieved only to answer {@link #iterator()}
* if the passed boolean is true
.
*/
ChildNodeEntriesImpl(NodeEntry parent, EntryFactory factory, Iterator childNodeInfos) {
this.parent = parent;
this.factory = factory;
if (childNodeInfos != null) {
while (childNodeInfos.hasNext()) {
ChildInfo ci = childNodeInfos.next();
NodeEntry entry = factory.createNodeEntry(parent, ci.getName(), ci.getUniqueID());
add(entry, ci.getIndex());
}
complete = true;
} else {
complete = false;
}
}
/**
* @param childEntry
* @return The node entry that directly follows the given childEntry
* or null
if the given childEntry
has no successor
* or was not found in this ChildNodeEntries
.
*/
NodeEntry getNext(NodeEntry childEntry) {
LinkedEntries.LinkNode ln = entries.getLinkNode(childEntry);
LinkedEntries.LinkNode nextLn = (ln == null) ? null : ln.getNextLinkNode();
return (nextLn == null) ? null : nextLn.getNodeEntry();
}
/**
* @param childEntry
* @return The node entry that directly precedes the given childEntry
* or null
if the given childEntry
is the first
* or was not found in this ChildNodeEntries
.
*/
NodeEntry getPrevious(NodeEntry childEntry) {
LinkedEntries.LinkNode ln = entries.getLinkNode(childEntry);
LinkedEntries.LinkNode prevLn = (ln == null) ? null : ln.getPreviousLinkNode();
return (prevLn == null) ? null : prevLn.getNodeEntry();
}
/**
* @see ChildNodeEntries#isComplete()
*/
public boolean isComplete() {
return (parent.getStatus() != Status.INVALIDATED && complete) ||
parent.getStatus() == Status.NEW ||
Status.isTerminal(parent.getStatus());
}
/**
* @see ChildNodeEntries#reload()
*/
public synchronized void reload() throws ItemNotFoundException, RepositoryException {
if (isComplete()) {
// nothing to do
return;
}
NodeId id = parent.getWorkspaceId();
Iterator childNodeInfos = factory.getItemStateFactory().getChildNodeInfos(id);
update(childNodeInfos);
}
/**
* Update the child node entries according to the child-infos obtained
* from the persistence layer.
* NOTE: the status of the entries already present is not respected. Thus
* new or removed entries are not touched in order not to modify the
* transient status of the parent. Operations that affect the set or order
* of child entries (AddNode, Move, Reorder) currently assert the
* completeness of the ChildNodeEntries, therefore avoiding an update
* resulting in inconsistent entries.
*
* @param childNodeInfos
* @see HierarchyEntry#reload(boolean) that ignores items with
* pending changes.
* @see org.apache.jackrabbit.jcr2spi.operation.AddNode
* @see org.apache.jackrabbit.jcr2spi.operation.Move
* @see org.apache.jackrabbit.jcr2spi.operation.ReorderNodes
*/
synchronized void update(Iterator childNodeInfos) {
// insert missing entries and reorder all if necessary.
LinkedEntries.LinkNode prevLN = null;
while (childNodeInfos.hasNext()) {
ChildInfo ci = childNodeInfos.next();
LinkedEntries.LinkNode ln = entriesByName.getLinkNode(ci.getName(), ci.getIndex(), ci.getUniqueID());
if (ln == null) {
// add missing at the correct position.
NodeEntry entry = factory.createNodeEntry(parent, ci.getName(), ci.getUniqueID());
ln = internalAddAfter(entry, ci.getIndex(), prevLN);
} else if (prevLN != null) {
// assert correct order of existing
if (prevLN != ln) {
reorderAfter(ln, prevLN);
} else {
// there was an existing entry but it's the same as the one
// created/retrieved before. getting here indicates that
// the SPI implementation provided invalid childNodeInfos.
log.error("ChildInfo iterator contains multiple entries with the same name|index or uniqueID -> ignore ChildNodeInfo.");
}
}
prevLN = ln;
}
// finally reset the status
complete = true;
}
/**
* @see ChildNodeEntries#iterator()
*/
public Iterator iterator() {
List l = new ArrayList(entries.size());
for (Iterator it = entries.linkNodeIterator(); it.hasNext();) {
l.add(it.next().getNodeEntry());
}
return Collections.unmodifiableList(l).iterator();
}
/**
* @see ChildNodeEntries#get(Name)
*/
public List get(Name nodeName) {
return entriesByName.getList(nodeName);
}
/**
* @see ChildNodeEntries#get(Name, int)
*/
public NodeEntry get(Name nodeName, int index) {
if (index < Path.INDEX_DEFAULT) {
throw new IllegalArgumentException("index is 1-based");
}
return entriesByName.getNodeEntry(nodeName, index);
}
/**
* @see ChildNodeEntries#get(Name, String)
*/
public NodeEntry get(Name nodeName, String uniqueID) {
if (uniqueID == null || nodeName == null) {
throw new IllegalArgumentException();
}
for (NodeEntry cne : get(nodeName)) {
if (uniqueID.equals(cne.getUniqueID())) {
return cne;
}
}
return null;
}
/**
* Adds a NodeEntry
to the end of the list. Same as
* {@link #add(NodeEntry, int)}, where the index is {@link Path#INDEX_UNDEFINED}.
*
* @param cne the NodeEntry
to add.
* @see ChildNodeEntries#add(NodeEntry)
*/
public synchronized void add(NodeEntry cne) {
internalAdd(cne, Path.INDEX_UNDEFINED);
}
/**
* @see ChildNodeEntries#add(NodeEntry, int)
*/
public synchronized void add(NodeEntry cne, int index) {
if (index < Path.INDEX_UNDEFINED) {
throw new IllegalArgumentException("Invalid index" + index);
}
internalAdd(cne, index);
}
/**
* @see ChildNodeEntries#add(NodeEntry, int, NodeEntry)
*/
public synchronized void add(NodeEntry entry, int index, NodeEntry beforeEntry) {
if (beforeEntry != null) {
// the link node where the new entry is ordered before
LinkedEntries.LinkNode beforeLN = entries.getLinkNode(beforeEntry);
if (beforeLN == null) {
throw new NoSuchElementException();
}
LinkedEntries.LinkNode insertLN = internalAdd(entry, index);
reorder(entry.getName(), insertLN, beforeLN);
} else {
// 'before' is null -> simply append new entry at the end
add(entry);
}
}
/**
*
* @param entry
* @param index
* @return the LinkNode
belonging to the added entry.
*/
private LinkedEntries.LinkNode internalAdd(NodeEntry entry, int index) {
Name nodeName = entry.getName();
// retrieve ev. sibling node with same index. if index is 'undefined'
// the existing entry is always null and no reordering occurs.
LinkedEntries.LinkNode existing = null;
if (index >= Path.INDEX_DEFAULT) {
existing = entriesByName.getLinkNode(nodeName, index);
}
// in case index greater than default -> create intermediate entries.
// TODO: TOBEFIXED in case of orderable node the order in the 'linked-entries' must be respected.
for (int i = Path.INDEX_DEFAULT; i < index; i++) {
LinkedEntries.LinkNode previous = entriesByName.getLinkNode(nodeName, i);
if (previous == null) {
NodeEntry sibling = factory.createNodeEntry(parent, nodeName, null);
internalAdd(sibling, i);
}
}
// add new entry
LinkedEntries.LinkNode ln = entries.add(entry, index);
entriesByName.put(nodeName, index, ln);
// reorder the child entries if, the new entry must be inserted rather
// than appended at the end of the list.
if (existing != null) {
reorder(nodeName, ln, existing);
}
return ln;
}
/**
* Add the specified new entry after the specified insertAfter
.
*
* @param newEntry
* @param index
* @param insertAfter
* @return the LinkNode
associated with the newEntry
.
*/
private LinkedEntries.LinkNode internalAddAfter(NodeEntry newEntry, int index,
LinkedEntries.LinkNode insertAfter) {
LinkedEntries.LinkNode ln = entries.addAfter(newEntry, index, insertAfter);
entriesByName.put(newEntry.getName(), index, ln);
return ln;
}
/**
* Removes the child node entry referring to the node state.
*
* @param childEntry the entry to be removed.
* @return the removed entry or null
if there is no such entry.
* @see ChildNodeEntries#remove(NodeEntry)
*/
public synchronized NodeEntry remove(NodeEntry childEntry) {
LinkedEntries.LinkNode ln = entries.removeNodeEntry(childEntry);
if (ln != null) {
entriesByName.remove(childEntry.getName(), ln);
return childEntry;
} else {
return null;
}
}
/**
* Reorders an existing NodeState
before another
* NodeState
. If beforeNode
is
* null
insertNode
is moved to the end of the
* child node entries.
*
* @param insertEntry the NodeEntry to move.
* @param beforeEntry the NodeEntry where insertNode
is
* reordered to.
* @return the NodeEntry that followed the 'insertNode' before the reordering.
* @throws NoSuchElementException if insertNode
or
* beforeNode
does not have a NodeEntry
* in this ChildNodeEntries
.
* @see ChildNodeEntries#reorder(NodeEntry, NodeEntry)
*/
public synchronized NodeEntry reorder(NodeEntry insertEntry, NodeEntry beforeEntry) {
// the link node to move
LinkedEntries.LinkNode insertLN = entries.getLinkNode(insertEntry);
if (insertLN == null) {
throw new NoSuchElementException();
}
// the link node where insertLN is ordered before
LinkedEntries.LinkNode beforeLN = (beforeEntry != null) ? entries.getLinkNode(beforeEntry) : null;
if (beforeEntry != null && beforeLN == null) {
throw new NoSuchElementException();
}
NodeEntry previousBefore = insertLN.getNextLinkNode().getNodeEntry();
if (previousBefore != beforeEntry) {
reorder(insertEntry.getName(), insertLN, beforeLN);
}
return previousBefore;
}
/**
* @see ChildNodeEntries#reorderAfter(NodeEntry, NodeEntry)
*/
public void reorderAfter(NodeEntry insertEntry, NodeEntry afterEntry) {
// the link node to move
LinkedEntries.LinkNode insertLN = entries.getLinkNode(insertEntry);
if (insertLN == null) {
throw new NoSuchElementException();
}
// the link node where insertLN is ordered before
LinkedEntries.LinkNode afterLN = (afterEntry != null) ? entries.getLinkNode(afterEntry) : null;
if (afterEntry != null && afterLN == null) {
throw new NoSuchElementException();
}
LinkedEntries.LinkNode previousLN = insertLN.getPreviousLinkNode();
if (previousLN != afterLN) {
reorderAfter(insertLN, afterLN);
} // else: already in correct position. nothing to do
}
/**
*
* @param insertName
* @param insertLN
* @param beforeLN
*/
private void reorder(Name insertName, LinkedEntries.LinkNode insertLN,
LinkedEntries.LinkNode beforeLN) {
// reorder named map
if (entriesByName.containsSiblings(insertName)) {
int position;
if (beforeLN == null) {
// reorder to the end -> use illegal position as marker
position = - 1;
} else {
// count all SNS-entries that are before 'beforeLN' in order to
// determine the new position of the reordered node regarding
// his siblings.
position = 0;
for (Iterator it = entries.linkNodeIterator(); it.hasNext(); ) {
LinkedEntries.LinkNode ln = it.next();
if (ln == beforeLN) {
break;
} else if (ln != insertLN && insertName.equals(ln.qName)) {
position++;
} // else: ln == insertLN OR no SNS -> not relevant for position count
}
}
entriesByName.reorder(insertName, insertLN, position);
}
// reorder in linked list
entries.reorderNode(insertLN, beforeLN);
}
/**
*
* @param insertLN
* @param afterLN
*/
private void reorderAfter(LinkedEntries.LinkNode insertLN, LinkedEntries.LinkNode afterLN) {
// the link node to move
if (insertLN == null) {
throw new NoSuchElementException();
}
// the link node where insertLN is ordered after
if (afterLN == null) {
// move to first position
afterLN = entries.getHeader();
}
LinkedEntries.LinkNode currentAfter = afterLN.getNextLinkNode();
if (currentAfter == insertLN) {
log.debug("Already ordered behind 'afterEntry'.");
// nothing to do
return;
} else {
// reorder named map
Name insertName = insertLN.qName;
if (entriesByName.containsSiblings(insertName)) {
int position = -1; // default: reorder to the end.
if (afterLN == entries.getHeader()) {
// move to the beginning
position = 0;
} else {
// count all SNS-entries that are before 'afterLN' in order to
// determine the new position of the reordered node regarding
// his siblings.
position = 0;
for (Iterator it = entries.linkNodeIterator(); it.hasNext(); ) {
LinkedEntries.LinkNode ln = it.next();
if (insertName.equals(ln.qName) && (ln != insertLN)) {
position++;
}
if (ln == afterLN) {
break;
}
}
}
entriesByName.reorder(insertName, insertLN, position);
}
// reorder in linked list
entries.reorderNode(insertLN, currentAfter);
}
}
//-------------------------------------------------< AbstractLinkedList >---
/**
* An implementation of a linked list which provides access to the internal
* LinkNode which links the entries of the list.
*/
private final class LinkedEntries extends AbstractLinkedList {
LinkedEntries() {
super();
init();
}
/**
* Returns the matching LinkNode
from a list or a single
* LinkNode
. This method will return null
* if none of the entries matches either due to missing entry for given
* state name or due to missing availability of the NodeEntry
.
*
* @param nodeEntry the NodeEntry
that is compared to the
* resolution of any NodeEntry
that matches by name.
* @return the matching LinkNode
or null
*/
private LinkedEntries.LinkNode getLinkNode(NodeEntry nodeEntry) {
for (Iterator it = linkNodeIterator(); it.hasNext();) {
LinkedEntries.LinkNode ln = it.next();
if (ln.getNodeEntry() == nodeEntry) {
return ln;
}
}
// not found
return null;
}
private LinkedEntries.LinkNode getHeader() {
return (LinkedEntries.LinkNode) header;
}
/**
* Adds a child node entry at the end of this list.
*
* @param cne the child node entry to add.
* @param index
* @return the LinkNode which refers to the added NodeEntry
.
*/
LinkedEntries.LinkNode add(NodeEntry cne, int index) {
LinkedEntries.LinkNode ln = new LinkedEntries.LinkNode(cne, index);
addNode(ln, header);
return ln;
}
/**
* Adds the given child node entry to this list after the specified
* entry
or at the beginning if entry
is
* null
.
*
* @param cne the child node entry to add.
* @param index
* @param insertAfter after which to insert the new entry
* @return the LinkNode which refers to the added NodeEntry
.
*/
LinkedEntries.LinkNode addAfter(NodeEntry cne, int index, LinkedEntries.LinkNode insertAfter) {
LinkedEntries.LinkNode newNode;
if (insertAfter == null) {
// insert at the beginning
newNode = new LinkedEntries.LinkNode(cne, index);
addNode(newNode, header);
} else if (insertAfter.getNextLinkNode() == null) {
newNode = add(cne, index);
} else {
newNode = new LinkedEntries.LinkNode(cne, index);
addNode(newNode, insertAfter.getNextLinkNode());
}
return newNode;
}
/**
* Remove the LinkEntry the contains the given NodeEntry as value.
*
* @param cne NodeEntry to be removed.
* @return LinkedEntries.LinkNode that has been removed.
*/
LinkedEntries.LinkNode removeNodeEntry(NodeEntry cne) {
LinkedEntries.LinkNode ln = getLinkNode(cne);
if (ln != null) {
ln.remove();
}
return ln;
}
/**
* Reorders an existing LinkNode
before another existing
* LinkNode
. If before
is null
* the insert
node is moved to the end of the list.
*
* @param insert the node to reorder.
* @param before the node where to reorder node insert
.
*/
void reorderNode(LinkedEntries.LinkNode insert, LinkedEntries.LinkNode before) {
removeNode(insert);
if (before == null) {
addNode(insert, header);
} else {
addNode(insert, before);
}
}
/**
* Create a new LinkNode
for a given {@link NodeEntry}
* value
.
*
* @param value a child node entry.
* @return a wrapping {@link LinkedEntries.LinkNode}.
* @see AbstractLinkedList#createNode(Object)
*/
@Override
protected Node createNode(Object value) {
return new LinkedEntries.LinkNode(value, Path.INDEX_DEFAULT);
}
/**
* @return a new LinkNode
.
* @see AbstractLinkedList#createHeaderNode()
*/
@Override
protected Node createHeaderNode() {
return new LinkedEntries.LinkNode();
}
/**
* @return iterator over all LinkNode entries in this list.
*/
private Iterator linkNodeIterator() {
return new LinkNodeIterator();
}
//----------------------------------------------------------------------
/**
* Extends the AbstractLinkedList.Node
.
*/
private final class LinkNode extends Node {
private final Name qName;
protected LinkNode() {
super();
qName = null;
}
protected LinkNode(Object value, int index) {
// add soft reference from linkNode to the NodeEntry (value)
// unless the entry is a SNSibling. TODO: review again.
super(index > Path.INDEX_DEFAULT ? value : new SoftReference
© 2015 - 2025 Weber Informatics LLC | Privacy Policy