/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.jcr.cache.document;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.modeshape.schematic.document.Document;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.collection.EmptyIterator;
import org.modeshape.jcr.cache.ChildReference;
import org.modeshape.jcr.cache.ChildReferences;
import org.modeshape.jcr.cache.DocumentNotFoundException;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.document.DocumentTranslator.ChildReferencesInfo;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NamespaceRegistry;
/**
*
*/
public class ImmutableChildReferences {
protected static final ChildReferences EMPTY_CHILD_REFERENCES = EmptyChildReferences.INSTANCE;
protected static final Iterator<ChildReference> EMPTY_ITERATOR = new EmptyIterator<ChildReference>();
protected static final Iterator<NodeKey> EMPTY_KEY_ITERATOR = new EmptyIterator<NodeKey>();
public static ChildReferences create( DocumentTranslator documentTranslator,
Document document,
String childrenFieldName,
boolean allowsSNS) {
return new Medium(documentTranslator, document, childrenFieldName, allowsSNS);
}
public static ChildReferences union( ChildReferences first,
ChildReferences second ) {
long firstSize = first.size();
long secondSize = second.size();
if (firstSize == 0 && secondSize == 0) {
return EMPTY_CHILD_REFERENCES;
} else if (firstSize == 0) {
return second;
} else if (secondSize == 0) {
return first;
}
return new ReferencesUnion(first, second);
}
public static ChildReferences create( ChildReferences first,
ChildReferencesInfo firstSegmentingInfo,
WorkspaceCache cache,
boolean allowsSNS) {
if (firstSegmentingInfo == null || firstSegmentingInfo.nextKey == null) return first;
return new Segmented(cache, first, firstSegmentingInfo, allowsSNS);
}
public static ChildReferences create( ChildReferences first,
ChildReferencesInfo firstSegmentingInfo,
ChildReferences second,
WorkspaceCache cache,
boolean allowsSNS) {
if (firstSegmentingInfo == null || firstSegmentingInfo.nextKey == null) return union(first, second);
Segmented segmentedReferences = new Segmented(cache, first, firstSegmentingInfo, allowsSNS);
return union(segmentedReferences, second);
}
@Immutable
protected final static class EmptyChildReferences implements ChildReferences {
public static final ChildReferences INSTANCE = new EmptyChildReferences();
private EmptyChildReferences() {
}
@Override
public boolean supportsGetChildReferenceByKey() {
return true;
}
@Override
public long size() {
return 0;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public int getChildCount( Name name ) {
return 0;
}
@Override
public ChildReference getChild( Name name ) {
return null;
}
@Override
public ChildReference getChild( Name name,
int snsIndex ) {
return null;
}
@Override
public ChildReference getChild( Name name,
int snsIndex,
Context context ) {
return null;
}
@Override
public ChildReference getChild( org.modeshape.jcr.value.Path.Segment segment ) {
return null;
}
@Override
public ChildReference getChild( NodeKey key ) {
return null;
}
@Override
public ChildReference getChild( NodeKey key,
Context context ) {
return null;
}
@Override
public boolean hasChild( NodeKey key ) {
return false;
}
@Override
public Iterator<ChildReference> iterator() {
return EMPTY_ITERATOR;
}
@Override
public Iterator<ChildReference> iterator( Name name ) {
return EMPTY_ITERATOR;
}
@Override
public Iterator<ChildReference> iterator( Name name,
Context context ) {
return EMPTY_ITERATOR;
}
@Override
public Iterator<ChildReference> iterator( Context context ) {
return EMPTY_ITERATOR;
}
@Override
public Iterator<ChildReference> iterator( Collection<?> namePatterns,
NamespaceRegistry registry ) {
return EMPTY_ITERATOR;
}
@Override
public Iterator<ChildReference> iterator( Context context,
Collection<?> namePatterns,
NamespaceRegistry registry ) {
return EMPTY_ITERATOR;
}
@Override
public Iterator<NodeKey> getAllKeys() {
return EMPTY_KEY_ITERATOR;
}
@Override
public boolean allowsSNS() {
return false;
}
}
@Immutable
protected static final class Medium extends AbstractChildReferences {
private final Map<NodeKey, ChildReference> childReferencesByKey;
private final Map<Name, List<NodeKey>> childKeysByName;
private final boolean allowsSNS;
protected Medium( DocumentTranslator documentTranslator,
Document document,
String childrenFieldName,
boolean allowsSNS) {
this.allowsSNS = allowsSNS;
this.childKeysByName = new HashMap<>();
this.childReferencesByKey = new LinkedHashMap<>();
final List<?> documentArray = document.getArray(childrenFieldName);
if (documentArray == null || documentArray.isEmpty()) {
return;
}
for (Object value : documentArray) {
ChildReference ref = documentTranslator.childReferenceFrom(value);
if (ref == null) {
continue;
}
Name refName = ref.getName();
NodeKey refKey = ref.getKey();
ChildReference old = this.childReferencesByKey.get(refKey);
if (old != null && old.getName().equals(ref.getName())) {
// We already have this key/name pair, so we don't need to add it again ...
continue;
}
// We've not seen this NodeKey/Name combination yet, so it is okay. In fact, we should not see any
// node key more than once, since that is clearly an unexpected condition (as a child may not appear
// more than once in its parent's list of child nodes). See MODE-2120.
// we can precompute the SNS index up front
List<NodeKey> keysWithName = this.childKeysByName.get(refName);
if (allowsSNS) {
int currentSNS = keysWithName != null ? this.childKeysByName.get(refName).size() : 0;
ChildReference refWithSNS = ref.with(currentSNS + 1);
this.childReferencesByKey.put(refKey, refWithSNS);
} else {
this.childReferencesByKey.put(refKey, ref);
}
if (keysWithName == null) {
keysWithName = new ArrayList<>();
this.childKeysByName.put(refName, keysWithName);
}
keysWithName.add(refKey);
}
}
@Override
public long size() {
return childReferencesByKey.size();
}
@Override
public int getChildCount( Name name ) {
List<NodeKey> nodeKeys = childKeysByName.get(name);
return nodeKeys != null ? nodeKeys.size() : 0;
}
@Override
public ChildReference getChild( Name name,
int snsIndex,
Context context ) {
if (!allowsSNS && snsIndex > 1) {
return null;
}
Changes changes = null;
Iterator<ChildInsertions> insertions = null;
boolean includeRenames = false;
if (context == null) {
context = new SingleNameContext();
} else {
changes = context.changes(); // may still be null
if (changes != null) {
insertions = changes.insertions(name);
includeRenames = changes.isRenamed(name);
}
}
List<NodeKey> childrenWithSameName = this.childKeysByName.get(name);
if (changes == null) {
if (childrenWithSameName == null) {
return null;
}
// there are no changes, so we can take advantage of the fact that we precomputed the SNS indexes up front...
if (snsIndex > childrenWithSameName.size()) {
return null;
}
NodeKey childKey = childrenWithSameName.get(snsIndex - 1);
return childReferencesByKey.get(childKey);
}
// there are changes, so there is some extra processing to be done...
if (childrenWithSameName == null && !includeRenames) {
// This segment contains no nodes with the supplied name ...
if (insertions == null) {
// and no nodes with this name were inserted ...
return null;
}
// But there is at least one inserted node with this name ...
while (insertions.hasNext()) {
ChildInsertions inserted = insertions.next();
Iterator<ChildReference> iter = inserted.inserted().iterator();
while (iter.hasNext()) {
ChildReference result = iter.next();
int index = context.consume(result.getName(), result.getKey());
if (index == snsIndex) return result.with(index);
}
}
return null;
}
// This collection contains at least one node with the same name ...
if (insertions != null || includeRenames) {
// The logic for this would involve iteration to find the indexes, so we may as well just iterate ...
Iterator<ChildReference> iter = iterator(context, name);
while (iter.hasNext()) {
ChildReference ref = iter.next();
if (ref.getSnsIndex() == snsIndex) return ref;
}
return null;
}
// We have at least one SNS in this list (and still potentially some removals) ...
for (NodeKey key : childrenWithSameName) {
ChildReference childWithSameName = childReferencesByKey.get(key);
if (changes.isRemoved(childWithSameName)) {
continue;
}
if (changes.isRenamed(childWithSameName)) {
continue;
}
// we've already precomputed the SNS index
if (snsIndex == childWithSameName.getSnsIndex()) {
return childWithSameName;
}
}
return null;
}
@Override
public ChildReference getChild( NodeKey key,
Context context ) {
ChildReference ref = childReferencesByKey.get(key);
if (ref == null) {
// Not in our list, so check the context for changes ...
if (context != null) {
Changes changes = context.changes();
if (changes != null) {
ref = changes.inserted(key);
if (ref != null) {
// The requested node was inserted, so figure out the SNS index.
// Unfortunately, this would require iteration (to figure out the indexes), so the
// easiest/fastest way is (surprisingly) to just iterate ...
Iterator<ChildReference> iter = iterator(context, ref.getName());
while (iter.hasNext()) {
ChildReference child = iter.next();
if (child.getKey().equals(key)) return child;
}
// Shouldn't really happen ...
}
}
// Not in list and no changes, so return ...
}
} else {
// It's in our list, but if there are changes we need to use the context ...
if (context != null) {
Changes changes = context.changes();
if (changes != null) {
boolean renamed = false;
if (changes.isRenamed(ref)) {
// The node was renamed, so get the new name ...
Name newName = changes.renamed(key);
ref = ref.with(newName, 1);
renamed = true;
}
Iterator<ChildInsertions> insertions = changes.insertions(ref.getName());
if (insertions != null || renamed) {
// We're looking for a node that was not inserted but has the same name as those that are
// (perhaps have renaming). As painful as it is, the easiest and most-efficient way to get
// this is to iterate ...
Iterator<ChildReference> iter = iterator(context, ref.getName());
while (iter.hasNext()) {
ChildReference child = iter.next();
if (child.getKey().equals(key)) return child;
}
}
// check if the node was removed only at the end to that insertions before can be processed first
if (changes.isRemoved(ref)) {
// The node was removed ...
return null;
}
}
}
}
return ref;
}
@Override
public ChildReference getChild( NodeKey key ) {
// we should already have precomputed the correct SNS at the beginning
return childReferencesByKey.get(key);
}
@Override
public boolean hasChild( NodeKey key ) {
return childReferencesByKey.containsKey(key);
}
@Override
public Iterator<ChildReference> iterator( Name name ) {
// the child references should already have the correct SNS precomputed
List<NodeKey> childKeys = childKeysByName.get(name);
if (childKeys == null || childKeys.isEmpty()) {
return Collections.emptyIterator();
}
final Iterator<NodeKey> childKeysIterator = childKeys.iterator();
return new Iterator<ChildReference>() {
@Override
public boolean hasNext() {
return childKeysIterator.hasNext();
}
@Override
public ChildReference next() {
return childReferencesByKey.get(childKeysIterator.next());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public Iterator<ChildReference> iterator() {
// the child references should already have the correct SNS precomputed
return childReferencesByKey.values().iterator();
}
@Override
public Iterator<ChildReference> iterator( Name name,
Context context ) {
if (context != null && context.changes() != null) {
// we only want the context-sensitive behavior if there are changes. Otherwise we've already precomputed the SNS
// index
return super.iterator(name, context);
}
return iterator(name);
}
@Override
public Iterator<ChildReference> iterator( Context context ) {
if (context != null && context.changes() != null) {
// we only want the context-sensitive behavior if there are changes. Otherwise we've already precomputed the SNS
// index
return super.iterator(context);
}
return iterator();
}
@Override
public Iterator<NodeKey> getAllKeys() {
return childReferencesByKey.keySet().iterator();
}
@Override
public StringBuilder toString( StringBuilder sb ) {
Iterator<ChildReference> iter = childReferencesByKey.values().iterator();
if (iter.hasNext()) {
sb.append(iter.next());
while (iter.hasNext()) {
sb.append(", ");
sb.append(iter.next());
}
}
return sb;
}
@Override
public boolean allowsSNS() {
return allowsSNS;
}
}
@Immutable
public static class Segmented extends AbstractChildReferences {
protected final WorkspaceCache cache;
protected final long totalSize;
protected final boolean allowsSNS;
private Segment firstSegment;
public Segmented( WorkspaceCache cache,
ChildReferences firstSegment,
ChildReferencesInfo info,
boolean allowsSNS) {
this.cache = cache;
this.totalSize = info.totalSize;
this.firstSegment = new Segment(firstSegment, info.nextKey, allowsSNS);
this.allowsSNS = allowsSNS;
}
@Override
public long size() {
return totalSize;
}
@Override
public boolean supportsGetChildReferenceByKey() {
return size() != ChildReferences.UNKNOWN_SIZE;
}
@Override
public int getChildCount( Name name ) {
int result = 0;
Segment segment = this.firstSegment;
while (segment != null) {
result += segment.getReferences().getChildCount(name);
segment = segment.next(cache);
}
return result;
}
@Override
public ChildReference getChild( Name name,
int snsIndex,
Context context ) {
ChildReference result = null;
Segment segment = this.firstSegment;
while (segment != null) {
result = segment.getReferences().getChild(name, snsIndex, context);
if (result != null) {
return result;
}
segment = segment.next(cache);
}
return result;
}
@Override
public boolean hasChild( NodeKey key ) {
Segment segment = this.firstSegment;
while (segment != null) {
if (segment.getReferences().hasChild(key)) {
return true;
}
segment = segment.next(cache);
}
return false;
}
@Override
public ChildReference getChild( NodeKey key ) {
return getChild(key, defaultContext());
}
@Override
public ChildReference getChild( NodeKey key,
Context context ) {
ChildReference result = null;
Segment segment = this.firstSegment;
while (segment != null) {
result = segment.getReferences().getChild(key, context);
if (result != null) {
return result;
}
segment = segment.next(cache);
}
return result;
}
@Override
public Iterator<ChildReference> iterator( final Name name,
final Context context ) {
final Segment firstSegment = this.firstSegment;
return new Iterator<ChildReference>() {
private Segment segment = firstSegment;
private Iterator<ChildReference> iter = segment != null ? segment.getReferences().iterator(name, context) : ImmutableChildReferences.EMPTY_ITERATOR;
private ChildReference next;
@Override
public boolean hasNext() {
if (next != null) {
// Called 'hasNext()' multiple times in a row ...
return true;
}
if (!iter.hasNext()) {
while (segment != null) {
segment = segment.next(cache);
if (segment != null) {
iter = segment.getReferences().iterator(name, context);
if (iter.hasNext()) {
next = iter.next();
return true;
}
}
}
return false;
}
next = iter.next();
return true;
}
@Override
public ChildReference next() {
try {
if (next == null) {
if (hasNext()) return next;
throw new NoSuchElementException();
}
return next;
} finally {
next = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public Iterator<ChildReference> iterator( final Context context ) {
final Segment firstSegment = this.firstSegment;
return new Iterator<ChildReference>() {
private Segment segment = firstSegment;
private Iterator<ChildReference> iter = segment != null ? segment.getReferences().iterator(context) : ImmutableChildReferences.EMPTY_ITERATOR;
private ChildReference next;
@Override
public boolean hasNext() {
if (next != null) {
// Called 'hasNext()' multiple times in a row ...
return true;
}
if (!iter.hasNext()) {
while (segment != null) {
segment = segment.next(cache);
if (segment != null) {
iter = segment.getReferences().iterator(context);
if (iter.hasNext()) {
next = iter.next();
return true;
}
}
}
return false;
}
next = iter.next();
return true;
}
@Override
public ChildReference next() {
try {
if (next == null) {
if (hasNext()) return next;
throw new NoSuchElementException();
}
return next;
} finally {
next = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public Iterator<NodeKey> getAllKeys() {
final Segment firstSegment = this.firstSegment;
return new Iterator<NodeKey>() {
private Segment segment = firstSegment;
private Iterator<NodeKey> iter = segment != null ? segment.keys() : ImmutableChildReferences.EMPTY_KEY_ITERATOR;
private NodeKey next;
@Override
public boolean hasNext() {
if (!iter.hasNext()) {
while (segment != null) {
segment = segment.next(cache);
if (segment != null) {
iter = segment.keys();
if (iter.hasNext()) {
next = iter.next();
return true;
}
}
}
return false;
}
next = iter.next();
return true;
}
@Override
public NodeKey next() {
try {
if (next == null) {
if (hasNext()) return next;
throw new NoSuchElementException();
}
return next;
} finally {
next = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@SuppressWarnings( "synthetic-access" )
@Override
public StringBuilder toString( StringBuilder sb ) {
Segment segment = firstSegment;
while (segment != null) {
segment.toString(sb);
if (segment.next != null) {
// there is already a loaded segment ...
sb.append(", ");
} else if (segment.nextKey != null) {
// there is a segment, but it hasn't yet been loaded ...
sb.append(", <segment=").append(segment.nextKey).append('>');
}
segment = segment.next;
}
return sb;
}
@Override
public boolean allowsSNS() {
return allowsSNS;
}
}
public static class ReferencesUnion extends AbstractChildReferences {
private final ChildReferences firstReferences;
protected final ChildReferences secondReferences;
ReferencesUnion( ChildReferences firstReferences,
ChildReferences secondReferences ) {
this.firstReferences = firstReferences;
this.secondReferences = secondReferences;
}
@Override
public long size() {
return secondReferences.size() + firstReferences.size();
}
@Override
public int getChildCount( Name name ) {
return secondReferences.getChildCount(name) + firstReferences.getChildCount(name);
}
@Override
public ChildReference getChild( Name name,
int snsIndex,
Context context ) {
ChildReference firstReferencesChild = firstReferences.getChild(name, snsIndex, context);
if (firstReferencesChild != null) {
return firstReferencesChild;
}
return secondReferences.getChild(name, snsIndex, context);
}
@Override
public boolean hasChild( NodeKey key ) {
return secondReferences.hasChild(key) || firstReferences.hasChild(key);
}
@Override
public ChildReference getChild( NodeKey key ) {
return getChild(key, defaultContext());
}
@Override
public ChildReference getChild( NodeKey key,
Context context ) {
ChildReference firstReferencesChild = firstReferences.getChild(key, context);
if (firstReferencesChild != null) {
return firstReferencesChild;
}
return secondReferences.getChild(key, context);
}
@Override
public Iterator<ChildReference> iterator() {
return new UnionIterator<ChildReference>(firstReferences.iterator(), secondReferences);
}
@Override
public Iterator<ChildReference> iterator( final Name name ) {
Iterable<ChildReference> second = new Iterable<ChildReference>() {
@Override
public Iterator<ChildReference> iterator() {
return secondReferences.iterator(name);
}
};
return new UnionIterator<ChildReference>(firstReferences.iterator(name), second);
}
@Override
public Iterator<NodeKey> getAllKeys() {
Set<NodeKey> externalKeys = new HashSet<NodeKey>();
for (Iterator<NodeKey> externalKeysIterator = secondReferences.getAllKeys(); externalKeysIterator.hasNext();) {
externalKeys.add(externalKeysIterator.next());
}
return new UnionIterator<NodeKey>(firstReferences.getAllKeys(), externalKeys);
}
@Override
public StringBuilder toString( StringBuilder sb ) {
sb.append("<second references=").append(secondReferences.toString());
sb.append(">, <first references=").append(firstReferences.toString()).append(">");
return sb;
}
@Override
public boolean allowsSNS() {
return firstReferences.allowsSNS() || secondReferences.allowsSNS();
}
}
protected static class Segment {
private final ChildReferences references;
private final String nextKey;
private final boolean allowsSNS;
private Segment next;
protected Segment( ChildReferences references,
String nextKey,
boolean allowsSNS) {
this.nextKey = nextKey;
this.references = references;
this.allowsSNS = allowsSNS;
}
public ChildReferences getReferences() {
return this.references;
}
public Segment next( WorkspaceCache cache ) {
if (next == null && nextKey != null) {
Document blockDoc = cache.blockFor(nextKey);
if (blockDoc == null) {
throw new DocumentNotFoundException(nextKey);
}
// we only need the direct children of the block to avoid nesting
ChildReferences refs = cache.translator().getChildReferencesFromBlock(blockDoc, allowsSNS);
ChildReferencesInfo nextNextKey = cache.translator().getChildReferencesInfo(blockDoc);
next = new Segment(refs, nextNextKey != null ? nextNextKey.nextKey : null, allowsSNS);
}
return next;
}
public Iterator<NodeKey> keys() {
return references.getAllKeys();
}
@Override
public String toString() {
return toString(new StringBuilder()).toString();
}
public StringBuilder toString( StringBuilder sb ) {
Iterator<ChildReference> iter = references.iterator();
if (iter.hasNext()) {
sb.append(iter.next());
while (iter.hasNext()) {
sb.append(", ");
sb.append(iter.next());
}
}
return sb;
}
}
}