/* * ModeShape (http://www.modeshape.org) * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * See the AUTHORS.txt file in the distribution for a full listing of * individual contributors. * * ModeShape is free software. Unless otherwise indicated, all code in ModeShape * is licensed to you under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * ModeShape is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.modeshape.jcr.cache.document; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.infinispan.schematic.document.Document; import org.modeshape.common.annotation.Immutable; import org.modeshape.common.collection.EmptyIterator; import org.modeshape.common.collection.LinkedListMultimap; import org.modeshape.common.collection.ListMultimap; 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 EMPTY_ITERATOR = new EmptyIterator(); protected static final Iterator EMPTY_KEY_ITERATOR = new EmptyIterator(); public static ChildReferences create( List references ) { int size = references.size(); if (size == 0) { return EMPTY_CHILD_REFERENCES; } return new Medium(references); } public static ChildReferences create( ChildReferences first, ChildReferencesInfo segmentingInfo, WorkspaceCache cache ) { return new Segmented(cache, first, segmentingInfo); } public static ChildReferences create( ChildReferences first, ChildReferencesInfo segmentingInfo, ChildReferences externalReferences, WorkspaceCache cache ) { Segmented segmentedReferences = new Segmented(cache, first, segmentingInfo); return !externalReferences.isEmpty() ? new FederatedReferences(segmentedReferences, externalReferences) : segmentedReferences; } @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 iterator() { return EMPTY_ITERATOR; } @Override public Iterator iterator( Name name ) { return EMPTY_ITERATOR; } @Override public Iterator iterator( Name name, Context context ) { return EMPTY_ITERATOR; } @Override public Iterator iterator( Context context ) { return EMPTY_ITERATOR; } @Override public Iterator iterator( Collection namePatterns, NamespaceRegistry registry ) { return EMPTY_ITERATOR; } @Override public Iterator iterator( Context context, Collection namePatterns, NamespaceRegistry registry ) { return EMPTY_ITERATOR; } @Override public Iterator getAllKeys() { return EMPTY_KEY_ITERATOR; } } @Immutable protected static final class Medium extends AbstractChildReferences { private final ListMultimap childReferences; private final Map childReferencesByKey; protected Medium( Iterable children ) { this.childReferences = LinkedListMultimap.create(); this.childReferencesByKey = new HashMap(); for (ChildReference ref : children) { this.childReferences.put(ref.getName(), ref); this.childReferencesByKey.put(ref.getKey(), ref); } } @Override public long size() { return childReferencesByKey.size(); } @Override public int getChildCount( Name name ) { return childReferences.get(name).size(); } @Override public ChildReference getChild( Name name, int snsIndex, Context context ) { Changes changes = null; Iterator 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 childrenWithSameName = this.childReferences.get(name); if (childrenWithSameName.isEmpty() && !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 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 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 (ChildReference childWithSameName : childrenWithSameName) { if (changes != null) { if (changes.isRemoved(childWithSameName)) continue; if (changes.isRenamed(childWithSameName)) continue; } int index = context.consume(childWithSameName.getName(), childWithSameName.getKey()); if (index == snsIndex) return childWithSameName.with(index); } 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. // Unforunately, this would require iteration (to figure out the indexes), so the // easiest/fastest way is (surprisingly) to just iterate ... Iterator 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) { // Hacked by epic begin, reorder the remove section to bottom 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 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 iter = iterator(context, ref.getName()); while (iter.hasNext()) { ChildReference child = iter.next(); if (child.getKey().equals(key)) return child; } // Shouldn't really happen ... } if (changes.isRemoved(ref)) { // The node was removed ... return null; } // Hacked by epic end, reorder the remove section to bottom } else { // It's in our list but there are no changes ... List childrenWithSameName = this.childReferences.get(ref.getName()); assert childrenWithSameName != null; assert childrenWithSameName.size() != 0; // Consume the child references until we find the reference ... for (ChildReference child : childrenWithSameName) { int index = context.consume(child.getName(), child.getKey()); if (key.equals(child.getKey())) return child.with(index); } } } } return ref; } @Override public ChildReference getChild( NodeKey key ) { return getChild(key, new BasicContext()); } @Override public boolean hasChild( NodeKey key ) { return childReferencesByKey.containsKey(key); } @Override public Iterator iterator( Name name ) { return childReferences.get(name).iterator(); } @Override public Iterator iterator() { return childReferences.values().iterator(); } @Override public Iterator getAllKeys() { return childReferencesByKey.keySet().iterator(); } @Override public StringBuilder toString( StringBuilder sb ) { Iterator iter = childReferences.values().iterator(); if (iter.hasNext()) { sb.append(iter.next()); while (iter.hasNext()) { sb.append(", "); sb.append(iter.next()); } } return sb; } } @Immutable public static class Segmented extends AbstractChildReferences { protected final WorkspaceCache cache; protected final long totalSize; private Segment firstSegment; public Segmented( WorkspaceCache cache, ChildReferences firstSegment, ChildReferencesInfo info ) { this.cache = cache; this.totalSize = info.totalSize; this.firstSegment = new Segment(firstSegment, info.nextKey); } @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, new BasicContext()); } @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 iterator( final Name name ) { final Segment firstSegment = this.firstSegment; return new Iterator() { private Segment segment = firstSegment; private Iterator iter = segment != null ? segment.getReferences().iterator(name) : 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); 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 iterator() { final Segment firstSegment = this.firstSegment; return new Iterator() { private Segment segment = firstSegment; private Iterator iter = segment != null ? segment.getReferences().iterator() : 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(); 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 getAllKeys() { final Segment firstSegment = this.firstSegment; return new Iterator() { private Segment segment = firstSegment; private Iterator 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 = segment.next; } return sb; } } public static class FederatedReferences extends AbstractChildReferences { private final ChildReferences internalReferences; private final ChildReferences externalReferences; FederatedReferences( ChildReferences externalReferences, ChildReferences internalReferences ) { this.externalReferences = externalReferences; this.internalReferences = internalReferences; } @Override public long size() { return externalReferences.size() + internalReferences.size(); } @Override public int getChildCount( Name name ) { return externalReferences.getChildCount(name) + internalReferences.getChildCount(name); } @Override public ChildReference getChild( Name name, int snsIndex, Context context ) { ChildReference nonFederatedRef = internalReferences.getChild(name, snsIndex, context); if (nonFederatedRef != null) { return nonFederatedRef; } return externalReferences.getChild(name, snsIndex, context); } @Override public boolean hasChild( NodeKey key ) { return externalReferences.hasChild(key) || internalReferences.hasChild(key); } @Override public ChildReference getChild( NodeKey key ) { return getChild(key, new BasicContext()); } @Override public ChildReference getChild( NodeKey key, Context context ) { ChildReference nonFederatedRef = internalReferences.getChild(key, context); if (nonFederatedRef != null) { return nonFederatedRef; } return externalReferences.getChild(key, context); } @Override public Iterator iterator() { return new UnionIterator(internalReferences.iterator(), externalReferences); } @Override public Iterator getAllKeys() { Set externalKeys = new HashSet(); for (Iterator externalKeysIterator = externalReferences.getAllKeys(); externalKeysIterator.hasNext();) { externalKeys.add(externalKeysIterator.next()); } return new UnionIterator(internalReferences.getAllKeys(), externalKeys); } @Override public StringBuilder toString( StringBuilder sb ) { sb.append(", "); return sb; } } protected static class Segment { private final ChildReferences references; private final String nextKey; private Segment next; protected Segment( ChildReferences references, String nextKey ) { this.nextKey = nextKey; this.references = references; } 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); ChildReferencesInfo nextNextKey = cache.translator().getChildReferencesInfo(blockDoc); next = new Segment(refs, nextNextKey != null ? nextNextKey.nextKey : null); } return next; } public Iterator keys() { return references.getAllKeys(); } @Override public String toString() { return toString(new StringBuilder()).toString(); } public StringBuilder toString( StringBuilder sb ) { Iterator iter = references.iterator(); if (iter.hasNext()) { sb.append(iter.next()); while (iter.hasNext()) { sb.append(", "); sb.append(iter.next()); } } return sb; } } }