### Eclipse Workspace Patch 1.0 #P jackrabbit-jcr-commons Index: src/main/java/org/apache/jackrabbit/flat/PropertySequence.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/PropertySequence.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/PropertySequence.java (revision 0) @@ -0,0 +1,47 @@ +/* + * 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.flat; + +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +/** + * Extension of {@link Sequence Sequence<Property>} which provides methods + * for adding and removing properties by key. + */ +public interface PropertySequence extends Sequence { + + /** + * Add a property with the given key and value. + * + * @param key key of the property to add + * @param value value of the property to add + * @return the newly added property + * @throws RepositoryException + */ + Property addProperty(String key, Value value) throws RepositoryException; + + /** + * Remove the property with the given key. + * + * @param key The key of the property to remove + * @throws RepositoryException If there is no property with such a key or + * another error occurs. + */ + void removeProperty(String key) throws RepositoryException; +} \ No newline at end of file Index: src/test/java/btree/RankTest.java =================================================================== --- src/test/java/btree/RankTest.java (revision 0) +++ src/test/java/btree/RankTest.java (revision 0) @@ -0,0 +1,185 @@ +/* + * 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 btree; + +import org.apache.jackrabbit.flat.Rank; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; + +import junit.framework.TestCase; + +public class RankTest extends TestCase { + private static final Random rnd = new Random(); + + public void testEmpty() { + Rank r = Rank.rank(new Integer[0]); + assertFalse(r.take(0).hasNext()); + assertEquals(0, r.size()); + try { + r.take(1); + fail("Excpeted " + NoSuchElementException.class.getName()); + } + catch (NoSuchElementException ignore) { } + } + + public void testSingleton() { + Rank r = Rank.rank(new Integer[] {42}); + assertFalse(r.take(0).hasNext()); + assertEquals(1, r.size()); + + Iterator it = r.take(1); + assertTrue(it.hasNext()); + assertEquals(0, r.size()); + assertEquals(42, it.next().intValue()); + + assertFalse(r.take(0).hasNext()); + + try { + r.take(1); + fail("Excpeted " + NoSuchElementException.class.getName()); + } + catch (NoSuchElementException ignore) { } + } + + public void testRank() { + for (int n = 1; n <= 2000; n++) { + testRank(n); + } + } + + public void testGetAll() { + int n = 100; + List values = createValues(n); + Rank r = Rank.rank(values, Integer.class); + + try { + r.take(n + 1); + fail("Excpeted " + NoSuchElementException.class.getName()); + } + catch (NoSuchElementException ignore) { } + + assertEquals(n, r.size()); + + Iterator it = r.take(n); + while (it.hasNext()) { + assertTrue(values.remove(it.next())); + } + + assertTrue(values.isEmpty()); + assertEquals(0, r.size()); + } + + public void testGetSingles() { + int n = 100; + List values = createValues(n); + Rank r = Rank.rank(values, Integer.class); + + List sorted = new ArrayList(); + Iterator it; + while (r.size() > 0) { + it = r.take(1); + sorted.add(it.next()); + assertFalse(it.hasNext()); + } + + assertTrue(sorted.containsAll(values)); + assertTrue(values.containsAll(sorted)); + + Comparator order = r.getOrder(); + checkOrdered(sorted.iterator(), order); + } + + public void testOrdered() { + int n = 1000000; + Integer[] values = new Integer[n]; + for (int k = 0; k < n; k++) { + values[k] = k; + } + + Rank r = Rank.rank(values); + while (r.size() > 0) { + int k = Math.min(rnd.nextInt(n/10), r.size()); + checkOrdered(r.take(k), r.getOrder()); + } + } + + // -----------------------------------------------------< internal >--- + + private static List createValues(int count) { + Set ints = new HashSet(); + for (int k = 0; k < count; k++) { + ints.add(rnd.nextInt()); + } + + List intList = new LinkedList(ints); + Collections.shuffle(intList); + return intList; + } + + private void checkOrdered(Iterator it, Comparator order) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + assertTrue(order.compare(prev, next) < 0); + prev = next; + } + } + + private static void testRank(int count) { + List ints = createValues(count); + Rank r = Rank.rank(ints, Integer.class); + Set all = new HashSet(ints); + Set previous = new HashSet(); + while (r.size() > 0) { + int n = Math.min(rnd.nextInt(count + 1), r.size()); + Iterator it = r.take(n); + Set actual = new HashSet(); + while (it.hasNext()) { + Integer i = it.next(); + assertTrue(actual.add(i)); + assertTrue(all.remove(i)); + } + + checkOrdering(previous, actual, r.getOrder()); + previous = actual; + } + + assertTrue(all.isEmpty()); + } + + private static void checkOrdering(Set previous, Set actual, + Comparator order) { + + for (Iterator pIt = previous.iterator(); pIt.hasNext(); ) { + Integer p = pIt.next(); + for(Iterator aIt = actual.iterator(); aIt.hasNext(); ) { + Integer a = aIt.next(); + assertTrue(order.compare(p, a) < 0); + } + } + } + +} Index: src/main/java/org/apache/jackrabbit/flat/TreeManager.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/TreeManager.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/TreeManager.java (revision 0) @@ -0,0 +1,150 @@ +/* + * 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.flat; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import java.util.Comparator; + +/** + * TreeManager instances are responsible for the mapping between sequence views + * of {@link Node}s and {@link Property}s and an hierarchical JCR + * representation. They are passed to the various factory methods in + * {@link ItemSequence} to parameterize the behavior of {@link NodeSequence}s + * and {@link PropertySequence}s. + * + * @see NodeSequence + * @see PropertySequence + */ +public interface TreeManager { + + /** + * @return the root node of the JCR sub-tree where the items of the sequence + * are be mapped to. + */ + Node getRoot(); + + /** + * Determined whether the given node is the root node of the + * JCR sub-tree. + * + * @param node Node to test for root + * @return getRoot().isSame(node). + * @throws RepositoryException + */ + boolean isRoot(Node node) throws RepositoryException; + + /** + * Determines whether the given node is a leaf. Leaf nodes are + * the nodes which are actually part of a {@link NodeSequence} or the + * parents of the properties of a {@link PropertySequence}. + * + * @param node Node to test for leaf + * @return true if node is a leaf node, + * false otherwise. + * @throws RepositoryException + */ + boolean isLeaf(Node node) throws RepositoryException; + + /** + * {@link Comparator} used for establishing the order of the keys in the + * sequence. + * + * @return a Comparator<String> instance + */ + Comparator getOrder(); + + /** + * After the node cause has been inserted into the sequence + * itemSequence, the implementation of this method may decide + * to split the parent node of cause into two or + * more new nodes. Splitting must be done such that the overall order of the + * keys in this sequence obeys the order given by {@link #getOrder()} as + * much as possible. + * + * @param itemSequence the {@link ItemSequence} where the new node + * cause has been inserted. + * @param node the parent node of the newly inserted node + * @param cause the newly inserted node or null if not known. + * @throws RepositoryException + */ + void split(ItemSequence itemSequence, Node node, Node cause) throws RepositoryException; + + /** + * After the property cause has been inserted into the sequence + * itemSequence, the implementation of this method may decide + * to split the parent node of cause into two or + * more new nodes. Splitting must be done such that the overall order of the + * keys in this sequence obeys the order given by {@link #getOrder()} as + * much as possible. + * + * @param itemSequence the {@link ItemSequence} where the new property + * cause has been inserted. + * @param node the parent node of the newly inserted property + * @param cause the newly inserted property or null if not + * known. + * @throws RepositoryException + */ + void split(ItemSequence itemSequence, Node node, Property cause) throws RepositoryException; + + /** + * After the node cause has been deleted from the sequence + * itemSequence, the implementation of this method may decide + * to join the parent node of cause with some + * other nodes. Joining must be done such that the overall order of the keys + * in this sequence obeys the order given by {@link #getOrder()} as much as + * possible. + * + * @param itemSequence the {@link ItemSequence} where the node + * cause has been deleted from. + * @param node the parent node from which cause has been + * deleted. + * @param cause the deleted node or null if not known. + * Note: cause might be stale. + * @throws RepositoryException + */ + void join(ItemSequence itemSequence, Node node, Node cause) throws RepositoryException; + + /** + * After the property cause has been deleted from the sequence + * itemSequence, the implementation of this method may decide + * to join the parent node of cause with some + * other nodes. Joining must be done such that the overall order of the keys + * in this sequence obeys the order given by {@link #getOrder()} as much as + * possible. + * + * @param itemSequence the {@link ItemSequence} where the property + * cause has been deleted from. + * @param node the parent node from which cause has been + * deleted. + * @param cause the deleted property or null if not known. + * Note: cause might be stale. + * @throws RepositoryException + */ + void join(ItemSequence itemSequence, Node node, Property cause) throws RepositoryException; + + /** + * Whether to automatically save changes of the current session occurring + * from adding/removing nodes and properties. + * + * @return true if changes should be automatically saved, + * false otherwiese. + */ + boolean getAutoSave(); +} Index: src/main/java/org/apache/jackrabbit/flat/NodeSequence.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/NodeSequence.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/NodeSequence.java (revision 0) @@ -0,0 +1,46 @@ +/* + * 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.flat; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +/** + * Extension of {@link Sequence Sequence<Node>} which provides methods for + * adding and removing nodes by key. + */ +public interface NodeSequence extends Sequence { + + /** + * Add a with the given key and primary node type name. + * + * @param key key of the node to add + * @param primaryNodeTypeName primary node type of the node to add + * @return the newly added node + * @throws RepositoryException + */ + Node addNode(String key, String primaryNodeTypeName) throws RepositoryException; + + /** + * Remove the node with the given key. + * + * @param key The key of the node to remove + * @throws RepositoryException If there is no node with such a key or + * another error occurs. + */ + void removeNode(String key) throws RepositoryException; +} \ No newline at end of file Index: src/main/java/org/apache/jackrabbit/flat/Sequence.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/Sequence.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/Sequence.java (revision 0) @@ -0,0 +1,66 @@ +/* + * 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.flat; + +import javax.jcr.AccessDeniedException; +import javax.jcr.Item; +import javax.jcr.ItemNotFoundException; +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; + +import java.util.Iterator; + +/** + * Interface for accessing JCR {@link Item}s sequentially through an + * {@link Iterator} or looking them up through a key. + * + * @param extends <Item> + */ +public interface Sequence extends Iterable { + + /** + * Iterator for the {@link Item}s in this sequence. The order of the items + * is implementation specific. + * + * @see java.lang.Iterable#iterator() + */ + Iterator iterator(); + + /** + * Retrieve an {@link Item} from this sequence by its key. If + * the sequence does not contain the key this method throws an + * {@link ItemNotFoundException}. + * + * @param key The key of the item to retrieve. Must not be + * null. + * @return The item belonging to key. + * @throws ItemNotFoundException + * @throws RepositoryException + */ + T getItem(String key) throws AccessDeniedException, PathNotFoundException, ItemNotFoundException, + RepositoryException; + + /** + * Determine whether this sequence contains a specific key. + * + * @param key The key to look up. + * @return true if this sequence contains key. + * False otherwise. + * @throws RepositoryException + */ + boolean hasItem(String key) throws RepositoryException; +} \ No newline at end of file Index: src/main/java/org/apache/jackrabbit/flat/ItemSequence.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/ItemSequence.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/ItemSequence.java (revision 0) @@ -0,0 +1,508 @@ +/* + * 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.flat; + +import org.apache.jackrabbit.flat.TreeTraverser.ErrorHandler; +import org.apache.jackrabbit.flat.TreeTraverser.InclusionPolicy; + +import javax.jcr.ItemExistsException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; + +import java.util.Comparator; +import java.util.Iterator; + +/** + *

+ * This class serves as main entry point for obtaining sequences of {@link Node} + * s and {@link Property}s. It provides factory methods for creating + * {@link NodeSequence}s and {@link PropertySequence}s. + *

+ * + *

+ * NodeSequence and PropertySequence instances provide a flat representation of + * a JCR hierarchy rooted at a certain node. They allow iterating over all + * items, retrieving items by key, checking whether a given key is mapped, + * adding new items and removing existing items. + *

+ * + *

+ * The specifics of the mapping from the flat representation to the JCR + * hierarchy are delegated to a {@link TreeManager}. Particularly the + * TreeManager specifies the order of the items when retrieved as sequence and + * when and how to add and remove intermediate nodes when new items are inserted + * or removed. + *

+ * + *

+ * An {@link ErrorHandler} is used to handle exceptions which occur while + * traversing the hierarchy. + *

+ * + * @see TreeTraverser + * @see NodeSequence + * @see PropertySequence + * @see TreeManager + */ +public abstract class ItemSequence { + + /** + * The {@link TreeManager} instance managing the mapping between the + * sequence view and the JCR hierarchy. + */ + protected final TreeManager treeManager; + + /** + * The {@link ErrorHandler} instance used for handling exceptions occurring + * while traversing the hierarchy. + */ + protected final ErrorHandler errorHandler; + + /** + * @see TreeManager#getRoot() + */ + protected final Node root; + + /** + * @see TreeManager#getOrder() + */ + protected final Comparator order; + + /** + * @see TreeManager#getAutoSave() + */ + protected final boolean autoSave; + + /** + * Create a new {@link ItemSequence} instance. + * + * @param treeManager The {@link TreeManager} for managing the mapping + * between the sequence view and the JCR hierarchy. + * @param errorHandler The {@link ErrorHandler} for handling exceptions + * occurring while traversing the hierarchy. + * @throws IllegalArgumentException If either treeManager is + * null or {@link TreeManager#getRoot()} return + * null or {@link TreeManager#getOrder()} return + * null. + */ + protected ItemSequence(TreeManager treeManager, ErrorHandler errorHandler) { + super(); + + if (treeManager == null) { + throw new IllegalArgumentException("tree manager must not be null"); + } + if (treeManager.getRoot() == null) { + throw new IllegalArgumentException("root must not be null"); + } + if (treeManager.getOrder() == null) { + throw new IllegalArgumentException("order must not be null"); + } + + this.treeManager = treeManager; + this.errorHandler = errorHandler; + this.root = treeManager.getRoot(); + this.order = treeManager.getOrder(); + this.autoSave = treeManager.getAutoSave(); + } + + /** + * Create a new {@link NodeSequence} instance. + * + * @param treeManager The {@link TreeManager} for managing the mapping + * between the sequence view and the JCR hierarchy. + * @param errorHandler The {@link ErrorHandler} for handling exceptions + * occurring while + * @return + */ + public static NodeSequence createNodeSequence(TreeManager treeManager, ErrorHandler errorHandler) { + return new NodeSequenceImpl(treeManager, errorHandler); + } + + /** + * Create a new {@link NodeSequence} instance. + * + * @param treeManager The {@link TreeManager} for managing the mapping + * between the sequence view and the JCR hierarchy. + * @return + */ + public static NodeSequence createNodeSequence(TreeManager treeManager) { + return new NodeSequenceImpl(treeManager, ErrorHandler.IGNORE); + } + + /** + * Create a new {@link PropertySequence} instance. + * + * @param treeManager The {@link TreeManager} for managing the mapping + * between the sequence view and the JCR hierarchy. + * @param errorHandler The {@link ErrorHandler} for handling exceptions + * occurring while + * @return + */ + public static PropertySequence createPropertySequence(TreeManager treeManager, ErrorHandler errorHandler) { + return new PropertySequenceImpl(treeManager, errorHandler); + } + + /** + * Create a new {@link PropertySequence} instance. + * + * @param treeManager The {@link TreeManager} for managing the mapping + * between the sequence view and the JCR hierarchy. + * @return + */ + public static PropertySequence createPropertySequence(TreeManager treeManager) { + return new PropertySequenceImpl(treeManager, ErrorHandler.IGNORE); + } + + /** + * Create a new {@link NodeSequence} instance with the same parametrization + * as this instance. + * + * @return + */ + public NodeSequence getNodeSequence() { + return new NodeSequenceImpl(treeManager, errorHandler); + } + + /** + * Create a new {@link PropertySequence} instance with the same + * parametrization as this instance. + * + * @return + */ + public PropertySequence getPropertySequence() { + return new PropertySequenceImpl(treeManager, errorHandler); + } + + // -----------------------------------------------------< internal >--- + + /** + * Returns the parent node for the given key. When the key is not present in + * this sequence already, the returned node is the node that would contain + * that key if it where present. + */ + protected abstract Node getParent(String key) throws RepositoryException; + + /** + * Returns the predecessor node for the given + * key. That is the node + * whose key directly precedes the passed key in the order + * determined by {@link TreeManager#getOrder()}. There are two cases: + *
    + *
  • A node with the given key is mapped: then that node is + * returned.
  • + *
  • A node with the given key is not mapped: the the node + * where that would contain that key if present is returned.
  • + *
+ */ + protected final Node getPredecessor(String key) throws RepositoryException { + Node p = root; + Node n; + while ((n = getPredecessor(p, key)) != null) { + p = n; + } + + return p; + } + + /** + * Returns the direct predecessor of key amongst + * node's child nodes wrt. to {@link TreeManager#getOrder()}. + * Returns null if either node has no child nodes + * or node is a leaf (see {@link TreeManager#isLeaf(Node)}) or + * key is smaller than all the keys of all child nodes of + * node. + */ + protected final Node getPredecessor(Node node, String key) throws RepositoryException { + if (!node.hasNodes() || treeManager.isLeaf(node)) { + return null; + } + + // Shortcut for exact match + try { + return node.getNode(key); + } + catch (PathNotFoundException ignore) { } + + // Search for direct predecessor of key in the nodes children + // todo performance: for ordered nodes use binary search + NodeIterator childNodes = node.getNodes(); + Node p = null; + while (childNodes.hasNext()) { + Node n = childNodes.nextNode(); + String childKey = n.getName(); + if (order.compare(key, childKey) > 0 && (p == null || order.compare(childKey, p.getName()) > 0)) { + p = n; + } + } + + return p; + } + + /** + * Returns the successor node for the given + * key. That is the node + * whose key directly succeeds the passed key in the order + * determined by {@link TreeManager#getOrder()}. There are two cases: + *
    + *
  • A node with the given key is mapped: then that node is + * returned.
  • + *
  • A node with the given key is not mapped: the the node + * where that would contain that key if present is returned.
  • + *
+ */ + protected final Node getSuccessor(Node node, String key) throws RepositoryException { + if (!node.hasNodes() || treeManager.isLeaf(node)) { + return null; + } + + // Shortcut for exact match + try { + return node.getNode(key); + } + catch (PathNotFoundException ignore) { } + + // Search for direct succecessor of key in the nodes children + // todo performance: for ordered nodes use binary search + NodeIterator childNodes = node.getNodes(); + Node s = null; + while (childNodes.hasNext()) { + Node n = childNodes.nextNode(); + String childKey = n.getName(); + if (order.compare(key, childKey) < 0 && (s == null || order.compare(childKey, s.getName()) < 0)) { + s = n; + } + } + + return s; + } + + /** + * Returns the node with the minimal key wrt. {@link TreeManager#getOrder()}. + * For the empty sequence this is {@link TreeManager#getRoot()}. + */ + protected final Node getMinimal() throws RepositoryException { + Node p = null; + Node n = root; + while ((n = getMinimal(n)) != null) { + p = n; + } + + return p; + } + + /** + * Returns the node amongst the child nodes of node whose key + * is minimal wrt. {@link TreeManager#getOrder()}. Returns null + * id either node has no child nodes or node is a + * leaf (see {@link TreeManager#isLeaf(Node)}). + */ + protected final Node getMinimal(Node node) throws RepositoryException { + if (!node.hasNodes() || treeManager.isLeaf(node)) { + return null; + } + + // Search for minimal key in the nodes children + // todo performance: for ordered nodes use binary search + NodeIterator childNodes = node.getNodes(); + Node p = childNodes.nextNode(); + String minKey = p.getName(); + while (childNodes.hasNext()) { + Node n = childNodes.nextNode(); + if (order.compare(n.getName(), minKey) < 0) { + p = n; + minKey = p.getName(); + } + } + + return p; + } + + /** + * Rename the path of the node with the minimal key. That is, assuming + * node is the node with the minimal key (see + * {@link #getMinimal()}), this method renames every segment of the path of + * node up to {@link TreeManager#getRoot()} to key + * . Note: If node is not the node with the minimal + * key, the behavior of this method is not specified. + */ + protected final void renamePath(Node node, String key) throws RepositoryException { + if (!treeManager.isRoot(node)) { + Node p = node.getParent(); + renamePath(p, key); + Session s = node.getSession(); + s.move(node.getPath(), p.getPath() + "/" + key); + + // If orderable, move to first child node + if (p.getPrimaryNodeType().hasOrderableChildNodes()) { + p.orderBefore(key, p.getNodes().nextNode().getName()); + } + } + } + + protected static class NodeSequenceImpl extends ItemSequence implements NodeSequence { + + public NodeSequenceImpl(TreeManager treeManager, ErrorHandler errorHandler) { + super(treeManager, errorHandler); + } + + public Iterator iterator() { + return TreeTraverser.nodeIterator(root, errorHandler, new InclusionPolicy() { + public boolean include(Node node) throws RepositoryException { + return treeManager.isLeaf(node); + } + }); + } + + public Node getItem(String key) throws RepositoryException { + return getParent(key).getNode(key); + } + + public boolean hasItem(String key) throws RepositoryException { + return getParent(key).hasNode(key); + } + + public Node addNode(String key, String primaryNodeTypeName) throws RepositoryException { + Node parent = getOrCreateParent(key); + if (parent.hasNode(key)) { + throw new ItemExistsException(key); + } + + Node n; + if (parent.getPrimaryNodeType().hasOrderableChildNodes()) { + Node dest = getSuccessor(parent, key); + n = parent.addNode(key, primaryNodeTypeName); + parent.orderBefore(key, dest == null ? null : dest.getName()); + } + else { + n = parent.addNode(key, primaryNodeTypeName); + } + + treeManager.split(this, parent, n); + + if (autoSave) { + parent.getSession().save(); + } + + return n; + } + + public void removeNode(String key) throws RepositoryException { + Node parent = getParent(key); + Node n = parent.getNode(key); + n.remove(); + treeManager.join(this, parent, n); + + if (autoSave) { + parent.getSession().save(); + } + } + + @Override + public Node getParent(String key) throws RepositoryException { + Node p = getPredecessor(key); + if (treeManager.isLeaf(p) && !treeManager.isRoot(p)) { + return p.getParent(); + } + else { + return p; + } + } + + private Node getOrCreateParent(String key) throws RepositoryException { + Node p = getParent(key); + if (treeManager.isRoot(p)) { + Node min = getMinimal(); + if (min != null) { + p = min.getParent(); + renamePath(p, key); + } + } + return p; + } + + } + + protected static class PropertySequenceImpl extends ItemSequence implements PropertySequence { + + public PropertySequenceImpl(TreeManager treeManager, ErrorHandler errorHandler) { + super(treeManager, errorHandler); + } + + public Iterator iterator() { + return TreeTraverser.propertyIterator(getNodeSequence().iterator(), errorHandler); + } + + public Property getItem(String key) throws RepositoryException { + return getParent(key).getProperty(key); + } + + public boolean hasItem(String key) throws RepositoryException { + return getParent(key).hasProperty(key); + } + + public Property addProperty(String key, Value value) throws RepositoryException { + Node parent = getOrCreateParent(key); + if (parent.hasProperty(key)) { + throw new ItemExistsException(key); + } + + Property p = parent.setProperty(key, value); + treeManager.split(this, parent, p); + + if (autoSave) { + p.getSession().save(); + } + + return p; + } + + public void removeProperty(String key) throws RepositoryException { + Node parent = getParent(key); + Property p = parent.getProperty(key); + p.remove(); + treeManager.join(this, parent, p); + + if (autoSave) { + parent.getSession().save(); + } + } + + @Override + public Node getParent(String key) throws RepositoryException { + return getPredecessor(key); + } + + private Node getOrCreateParent(String key) throws RepositoryException { + Node p = getParent(key); + if (treeManager.isRoot(p)) { + Node min = getMinimal(); + if (min != null) { + p = min; + renamePath(p, key); + } + } + return p; + } + + } + +} Index: pom.xml =================================================================== --- pom.xml (revision 959241) +++ pom.xml (working copy) @@ -71,6 +71,10 @@ json test + + commons-collections + commons-collections + Index: src/main/java/org/apache/jackrabbit/flat/TreeTraverser.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/TreeTraverser.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/TreeTraverser.java (revision 0) @@ -0,0 +1,391 @@ +/* + * 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.flat; + +import org.apache.commons.collections.iterators.EmptyIterator; +import org.apache.commons.collections.iterators.IteratorChain; +import org.apache.commons.collections.iterators.SingletonIterator; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + *

+ * Utility class for traversing the {@link Item}s of a JCR hierarchy rooted at a + * specific {@link Node}. + *

+ * + *

+ * This class provides an {@link Iterator} of JCR items either through its + * implementation of {@link Iterable} or through various static factory methods. + * The iterators return its elements in pre-order. That is, each node occurs + * before its child nodes are traversed. The order in which child nodes are + * traversed is determined by the underlying JCR implementation. Generally the + * order is not specified unless a {@link Node} has orderable child nodes. + *

+ * + *

+ * Whether a specific node is included is determined by an + * {@link #InclusionPolicy}. Error occurring while traversing are delegated to + * an {@link #ErrorHandler}. + *

+ */ +public final class TreeTraverser implements Iterable { + private final Node root; + private final ErrorHandler errorHandler; + private final InclusionPolicy inclusionPolicy; + + /** + * Create a new instance of a TreeTraverser rooted at node. + * + * @param root The root node of the sub-tree to traverse + * @param errorHandler Handler for errors while traversing + * @param inclusionPolicy Inclusion policy to determine which nodes to + * include + */ + public TreeTraverser(Node root, ErrorHandler errorHandler, InclusionPolicy inclusionPolicy) { + super(); + this.root = root; + this.errorHandler = errorHandler == null? ErrorHandler.IGNORE : errorHandler; + this.inclusionPolicy = inclusionPolicy; + } + + /** + * Create a new instance of a TreeTraverser rooted at node. + * + * @param root The root node of the sub-tree to traverse + */ + public TreeTraverser(Node root) { + this(root, ErrorHandler.IGNORE, InclusionPolicy.ALL); + } + + /** + * Error handler for handling {@link RepositoryException}s occurring on + * traversal. The predefined {@link #IGNORE} error handler can be used to + * ignore all exceptions. + */ + public interface ErrorHandler { + + /** + * Predefined error handler which ignores all exceptions. + */ + public static ErrorHandler IGNORE = new ErrorHandler() { + public void call(Item item, RepositoryException exception) { /* ignore */ } + }; + + /** + * This call back method is called whenever an error occurs while + * traversing. + * + * @param item The item which was the target of an operation which + * failed and caused the exception. + * @param exception The exception which occurred. + */ + void call(Item item, RepositoryException exception); + } + + /** + * Inclusion policy to determine which nodes to include when traversing. + * There a two predefined inclusion policies: + *
    + *
  • {@link #ALL} includes all nodes.
  • + *
  • {@link #LEAVES} includes only leave nodes. A leaf node is a node + * which does not have childe nodes.
  • + *
+ */ + public interface InclusionPolicy { + + /** + * This inclusions policy includes all nodes. + */ + public static InclusionPolicy ALL = new InclusionPolicy() { + public boolean include(Node node) { + return true; + } + }; + + /** + * This inclusion policy only includes leave nodes. A leaf node is a + * node which does not have child nodes. + */ + public static InclusionPolicy LEAVES = new InclusionPolicy() { + public boolean include(Node node) throws RepositoryException { + return !node.hasNodes(); + } + }; + + /** + * Call back method to determine whether to include a given node. + * + * @param node The node under consideration + * @return true when node should be included. + * false otherwise. + * + * @throws RepositoryException + */ + boolean include(Node node) throws RepositoryException; + } + + /** + * Create an iterator for the nodes of the sub-tree rooted at + * root. + * + * @param root root node of the sub-tree to traverse + * @param errorHandler handler for exceptions occurring on traversal + * @param inclusionPolicy inclusion policy to determine which nodes to + * include + * @return iterator of {@link Node} + */ + public static Iterator nodeIterator(Node root, ErrorHandler errorHandler, + InclusionPolicy inclusionPolicy) { + + return new TreeTraverser(root, errorHandler, inclusionPolicy).iterator(); + } + + /** + * Create an iterator for the nodes of the sub-tree rooted at + * root. Exceptions occurring on traversal are ignored. + * + * @param root root node of the sub-tree to traverse + * @return iterator of {@link Node} + */ + public static Iterator nodeIterator(Node root) { + return nodeIterator(root, ErrorHandler.IGNORE, InclusionPolicy.ALL); + } + + /** + * Create an iterator of the properties for a given iterator of nodes. The + * order of the returned properties is only specified so far that if node + * n1 occurs before node n2 in the iterator of + * nodes, then any property of n1 will occur before any + * property of n2. + * + * @param nodes nodes whose properties to chain + * @param errorHandler handler for exceptions occurring on traversal + * @return iterator of {@link Property} + */ + public static Iterator propertyIterator(Iterator nodes, ErrorHandler errorHandler) { + return chain(propertyIterators(nodes, errorHandler)); + } + + /** + * Create an iterator of the properties for a given iterator of nodes. The + * order of the returned properties is only specified so far that if node + * n1 occurs before node n2 in the iterator of + * nodes, then any property of n1 will occur before any + * property of n2. Exceptions occurring on traversal are + * ignored. + * + * @param nodes nodes whose properties to chain + * @return iterator of {@link Property} + */ + public static Iterator propertyIterator(Iterator nodes) { + return propertyIterator(nodes, ErrorHandler.IGNORE); + } + + /** + * Create an iterator of the properties of all nodes of the sub-tree rooted + * at root. + * + * @param root root node of the sub-tree to traverse + * @param errorHandler handler for exceptions occurring on traversal + * @param inclusionPolicy inclusion policy to determine which nodes to + * include + * @return iterator of {@link Property} + */ + public static Iterator propertyIterator(Node root, ErrorHandler errorHandler, + InclusionPolicy inclusionPolicy) { + + return propertyIterator(nodeIterator(root, errorHandler, inclusionPolicy), errorHandler); + } + + /** + * Create an iterator of the properties of all nodes of the sub-tree rooted + * at root. Exceptions occurring on traversal are ignored. + * + * @param root root node of the sub-tree to traverse + * @return iterator of {@link Property} + */ + public static Iterator propertyIterator(Node root) { + return propertyIterator(root, ErrorHandler.IGNORE, InclusionPolicy.ALL); + } + + /** + * Returns an iterator of {@link Node} for this instance. + * + * @see TreeTraverser#TreeTraverser(Node, ErrorHandler, InclusionPolicy) + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return iterator(root); + } + + // -----------------------------------------------------< internal >--- + + /** + * Returns an iterator of the nodes of the sub-tree rooted at + * node. + */ + private Iterator iterator(Node node) { + try { + if (inclusionPolicy.include(node)) { + return chain(singleton(node), childIterators(node)); + } + else { + return chain(childIterators(node)); + } + } + catch (RepositoryException e) { + errorHandler.call(node, e); + return empty(); + } + } + + /** + * Returns an iterator containing the single element element. + */ + @SuppressWarnings("unchecked") + private static Iterator singleton(T element) { + return new SingletonIterator(element); + } + + /** + * Returns an iterator of iterators of the child nodes of node. + */ + private Iterator> childIterators(final Node node) { + try { + return new Iterator>() { + private final NodeIterator childNodes = node.getNodes(); + + public boolean hasNext() { + return childNodes.hasNext(); + } + + public Iterator next() { + return iterator(childNodes.nextNode()); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + catch (RepositoryException e) { + errorHandler.call(node, e); + return empty(); + } + } + + /** + * Returns an iterator of all properties of all nodes. For node + * n1 occurring before node n2 in + * nodes, any property of n1 will occur before any + * property of n2 in the iterator. + */ + private static Iterator> propertyIterators(final Iterator nodes, + final ErrorHandler errorHandler) { + + return new Iterator>() { + public boolean hasNext() { + return nodes.hasNext(); + } + + @SuppressWarnings("unchecked") + public Iterator next() { + Node n = nodes.next(); + try { + return n.getProperties(); + } + catch (RepositoryException e) { + errorHandler.call(n, e); + return empty(); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Returns the concatenation of iterator with the concatenation + * of all iterators in iterators. + */ + @SuppressWarnings("unchecked") + private static Iterator chain(Iterator iterator, Iterator> iterators) { + return new IteratorChain(iterator, new LazyIteratorChain(iterators)); + } + + /** + * Returns the concatenation of all iterators in iterators. + */ + private static Iterator chain(Iterator> iterators) { + return new LazyIteratorChain(iterators); + } + + /** + * Returns an empty iterator. + */ + @SuppressWarnings("unchecked") + private static Iterator empty() { + return EmptyIterator.INSTANCE; + } + + /** + * The class implements the concatenation of iterators. The implementation + * is lazy in the sense that advancing off all iterators is deferred as much + * as possible. Specifically no iterator is fully unwrapped at one single + * point of time. + */ + private static final class LazyIteratorChain implements Iterator { + private final Iterator> iterators; + private Iterator currentIterator; + + private LazyIteratorChain(Iterator> iterators) { + super(); + this.iterators = iterators; + } + + public boolean hasNext() { + while ((currentIterator == null || !currentIterator.hasNext()) && iterators.hasNext()) { + currentIterator = iterators.next(); + } + return currentIterator != null && currentIterator.hasNext(); + } + + public T next() { + if (hasNext()) { + return currentIterator.next(); + } + else { + throw new NoSuchElementException(); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + +} Index: src/main/java/org/apache/jackrabbit/flat/Rank.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/Rank.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/Rank.java (revision 0) @@ -0,0 +1,280 @@ +/* + * 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.flat; + +import org.apache.commons.collections.iterators.ArrayIterator; +import org.apache.commons.collections.iterators.EmptyIterator; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; + +/** + *

+ * This class does efficient ranking of values of type T wrt. to a + * {@link Comparator} for T. After creating an instance of + * Rank, the {@link #take(int)} method returns the next + * k smallest values. That is, each of these values is smaller than + * every value not yet retrieved. The order of the values returned by + * take is not specified in general. However if the values are in + * increasing order, the values returned by take will also be in + * increasing order. + *

+ *

+ * Note: The values may not contain duplicates or the behavior + * of take is not defined. + *

+ * + * @param Type of values in this Rank. + */ +public class Rank { + private final T[] values; + private final Comparator order; + private int first; + + /** + * Create a new instance of Rank for a given array of + * values and a given order. The + * values are manipulated in place, no copying is performed. + * + * @param values values for ranking. Duplicates are not allowed. + * @param order Ordering for ranking + */ + public Rank(T[] values, Comparator order) { + super(); + this.values = values; + this.order = order; + } + + /** + * Create a new instance of Rank for a given collection of + * values and a given order. The + * values are copied into an internal array before they are + * manipulated. + * + * @param values values for ranking. Duplicates are not allowed. + * @param componentType type evidence for the values + * @param order Ordering for ranking + */ + @SuppressWarnings("unchecked") + public Rank(Collection values, Class componentType, Comparator order) { + super(); + Object array = Array.newInstance(componentType, values.size()); + this.values = values.toArray((T[]) array); + this.order = order; + } + + /** + * Create a new instance of Rank for the first + * count values in a a given iterator of values + * and a given order. The values are copied into + * an internal array before they are manipulated. + * + * @param values values for ranking. Duplicates are not allowed. + * @param componentType type evidence for the values + * @param count Number of items to include. -1 for all. + * @param order Ordering for ranking + */ + @SuppressWarnings("unchecked") + public Rank(Iterator values, Class componentType, int count, Comparator order) { + super(); + this.order = order; + + if (count >= 0) { + this.values = (T[]) Array.newInstance(componentType, count); + for (int k = 0; k < count; k++) { + this.values[k] = values.next(); + } + } + else { + List l = new LinkedList(); + while (values.hasNext()) { + l.add(values.next()); + } + Object array = Array.newInstance(componentType, l.size()); + this.values = l.toArray((T[]) array); + } + } + + /** + * Create a new instance of Rank for a given array of + * values. The order is determined by the natural ordering of + * the values (i.e. through {@link Comparable}). The values are + * manipulated in place, no copying is performed. + * + * @param extends Comparable<S> + * @param values values for ranking. Duplicates are not allowed. + * @return A new instance of Rank. + */ + public static > Rank rank(S[] values) { + return new Rank(values, Rank.comparableComparator()); + } + + /** + * Create a new instance of Rank for a given collection of + * values. The order is determined by the natural ordering of + * the values (i.e. through {@link Comparable}). The values are + * copied into an internal array before they are manipulated. + * + * @param extends Comparable<S> + * @param values values for ranking. Duplicates are not allowed. + * @param componentType type evidence for the values + * @return A new instance of Rank. + */ + public static > Rank rank(Collection values, Class componentType) { + return new Rank(values, componentType, Rank.comparableComparator()); + } + + /** + * Create a new instance of Rank for the first + * count values in a a given iterator of values. + * The order is determined by the natural ordering of the values (i.e. + * through {@link Comparable}). The values are copied into an + * internal array before they are manipulated. + * + * @param extends Comparable<S> + * @param values values for ranking. Duplicates are not allowed. + * @param componentType type evidence for the values + * @param count Number of items to include. -1 for all. + * @return A new instance of Rank. + */ + public static > Rank rank(Iterator values, Class componentType, int count) { + return new Rank(values, componentType, count, Rank.comparableComparator()); + } + + /** + * Utility method for creating a {@link Comparator} of T from a + * {@link Comparable} of type T. + * + * @param extends Comparable<T> + * @return Comparator whose order is defined by T. + */ + public static > Comparator comparableComparator() { + return new Comparator() { + public int compare(T c1, T c2) { + return c1.compareTo(c2); + } + }; + } + + public Comparator getOrder() { + return order; + } + + /** + * Returns the n-th smallest values remaining in this + * Rank. + * + * @param n Number of values to return + * @return An iterator containing the next n smallest values. + * @throws NoSuchElementException if this Rank has not enough + * remaining elements or when n is negative. + */ + public Iterator take(int n) { + if (n < 0 || n + first > values.length) { + throw new NoSuchElementException(); + } + + if (n > 0) { + take(n, first, values.length - 1); + first += n; + return arrayIterator(values, first - n, first); + } + else { + return empty(); + } + } + + /** + * Returns the number of remaining items in the Rank. + * + * @return number of remaining items. + */ + public int size() { + return values.length - first; + } + + // -----------------------------------------------------< internal >--- + + /** + * Rearrange {@link #values} such that each of the n first + * values starting at from is smaller that all the remaining + * items up to to. + */ + private void take(int n, int from, int to) { + // Shortcut for all values + if (n >= to - from + 1) { + return; + } + + // Choosing the n-th value as pivot results in correct partitioning after one pass + // for already ordered values. + int pivot = from + n - 1; + int lo = from; + int hi = to; + + // Partition values around pivot + while (lo < hi) { + // Find values to swap around the pivot + while (order.compare(values[lo], values[pivot]) < 0) lo++; + while (order.compare(values[hi], values[pivot]) > 0) hi--; + if (lo < hi) { + // Swap values and keep track of pivot position in case the pivot itself is swapped + if (lo == pivot) pivot = hi; + else if (hi == pivot) pivot = lo; + swap(lo, hi); + lo++; + hi--; + } + } + + // Actual number of values taken + int nn = pivot + 1 - from; + if (nn > n) { // Recurse: take first n elements from first partition + take(n, from, pivot); + } + else if (nn < n) { // Recurse: take first n - nn elements from second partition + take(n - nn, pivot + 1, to); + } + // else done + } + + private void swap(int lo, int hi) { + T t1 = values[lo]; + T t2 = values[hi]; + if (order.compare(t1, t2) == 0) { + throw new IllegalStateException("Detected duplicates " + t1); + } + values[lo] = t2; + values[hi] = t1; + } + + @SuppressWarnings("unchecked") + private static Iterator arrayIterator(T[] values, int from, int to) { + return new ArrayIterator(values, from, to); + } + + @SuppressWarnings("unchecked") + private Iterator empty() { + return EmptyIterator.INSTANCE; + } + +} Index: src/main/java/org/apache/jackrabbit/flat/BTreeManager.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/BTreeManager.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/BTreeManager.java (revision 0) @@ -0,0 +1,424 @@ +/* + * 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.flat; + + +import org.apache.commons.collections.Predicate; +import org.apache.commons.collections.iterators.FilterIterator; +import org.apache.jackrabbit.JcrConstants; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; + +import java.util.Comparator; +import java.util.Iterator; + +/** + *

+ * This {@link TreeManager} implementation provides B+-tree like behavior. That + * is items of a sequence (i.e. {@link NodeSequence} or {@link PropertySequence}) + * are mapped to a sub-tree in JCR in a way such that only leave nodes carry + * actual values, the sub-tree is always balanced and ordered. This + * implementation does in contrast to a full B+-tree implementation not + * join nodes after deletions. This does not affect the order of items and also + * leaves the tree balanced wrt. its depths. It might however result in a sparse + * tree. That is, the tree might get unbalanced wrt. its weights. + *

+ * + *

+ * The nodes in the JCR sub tree are arranged such that any node named x + * only contains child nodes with names greater or equal to x. + * The implementation keeps the child nodes in the sub tree ordered if the + * respective node type supports ordering of child nodes. + * Ordering is always wrt. to a {@link Comparator} on the respective keys. + * For lexical order this arrangement corresponds to how words are arranged in a multi + * volume encyclopedia. + *

+ * + *

+ * Example usage: + * + *

+ * // Create a new TreeManager instance rooted at node. Splitting of nodes takes place
+ * // when the number of children of a node exceeds 40 and is done such that each new
+ * // node has at least 40 child nodes. The keys are ordered according to the natural
+ * // order of java.lang.String.
+ * TreeManager treeManager = new BTreeManager(node, 20, 40, Rank.<String>comparableComparator(), true);
+ *
+ * // Create a new NodeSequence with that tree manager
+ * NodeSequence nodes = ItemSequence.createNodeSequence(treeManager);
+ *
+ * // Add nodes with key "jcr" and "day"
+ * nodes.addNode("jcr", NodeType.NT_UNSTRUCTURED);
+ * nodes.addNode("day", NodeType.NT_UNSTRUCTURED);
+ *
+ * // Iterate over the node in the sequence.
+ * // Prints "day jcr "
+ * for (Node n : nodes) {
+ *     System.out.print(n.getName() + " ");
+ * }
+ *
+ * // Retrieve node with key "jcr"
+ * Node n = nodes.getItem("jcr");
+ *
+ * // Remove node with key "day"
+ * nodes.removeNode("day");
+ * 
+ * + *

+ */ +public class BTreeManager implements TreeManager { + private final Node root; + private final int minChildren; + private final int maxChildren; + private final Comparator order; + private final boolean autoSave; + private final Comparator itemOrder; + + /** + * Create a new {@link BTreeManager} rooted at Node root. + * + * @param root the root of the JCR sub-tree where the items of the sequence + * are stored. + * @param minChildren minimal number of children for a node after splitting. + * @param maxChildren maximal number of children for a node after which + * splitting occurs. + * @param order order according to which the keys are stored + * @param autoSave determines whether the current session is saved after + * add/delete operations. + * @throws RepositoryException + */ + public BTreeManager(Node root, int minChildren, int maxChildren, Comparator order, boolean autoSave) + throws RepositoryException { + super(); + + if (root == null) { + throw new IllegalArgumentException("root must not be null"); + } + if (minChildren <= 0) { + throw new IllegalArgumentException("minChildren must be positive"); + } + if (maxChildren > Integer.MAX_VALUE) { + throw new IllegalArgumentException("maxChildren must not exceed " + Integer.MAX_VALUE); + } + if (2 * minChildren < maxChildren) { + throw new IllegalArgumentException("maxChildren must be at least twice minChildren"); + } + if (order == null) { + throw new IllegalArgumentException("order must not be null"); + } + + this.root = root; + this.minChildren = minChildren; + this.maxChildren = maxChildren; + this.order = order; + this.autoSave = autoSave; + this.itemOrder = new Comparator() { + public int compare(Item i1, Item i2) { + try { + return BTreeManager.this.order.compare(i1.getName(), i2.getName()); + } + catch (RepositoryException e) { + throw new WrappedRepositoryException(e); + } + } + }; + } + + /** + * This implementations splits node when its number of child + * nodes exceeds the maximum number specified in the constructor. Splitting + * is done such that after the split each of the new child nodes contains at + * least as many nodes as specified in the constructor. + * + * @see org.apache.jackrabbit.flat.TreeManager#split(org.apache.jackrabbit.flat.ItemSequence, + * javax.jcr.Node, javax.jcr.Node) + */ + public void split(ItemSequence itemSequence, Node node, Node cause) throws RepositoryException { + SizedIterator childNodes = getNodes(node); + int count = (int) childNodes.getSize(); + if (count >= 0 && count <= maxChildren) { + return; + } + + split(node, new Rank(childNodes, Node.class, count, itemOrder), itemSequence); + } + + /** + * This implementations splits node when its number of + * properties exceeds the maximum number specified in the constructor. + * Splitting is done such that after the split each of the new child nodes + * contains at least as many nodes as specified in the constructor. + * + * @see org.apache.jackrabbit.flat.TreeManager#split(org.apache.jackrabbit.flat.ItemSequence, + * javax.jcr.Node, javax.jcr.Property) + */ + public void split(ItemSequence itemSequence, Node node, Property cause) throws RepositoryException { + SizedIterator properties = getProperties(node); + int count = (int) properties.getSize(); + if (count >= 0 && count <= maxChildren) { + return; + } + + split(node, new Rank(properties, Property.class, count, itemOrder), itemSequence); + } + + /** + * This implementation does not actually join any nodes. It does however + * delete node if {@link #getNodes(Node)} returns an empty + * iterator. It does further recursively delete any parent of + * node which does not have any child node. + * + * @see org.apache.jackrabbit.flat.TreeManager#join(org.apache.jackrabbit.flat.ItemSequence, + * javax.jcr.Node, javax.jcr.Node) + */ + public void join(ItemSequence itemSequence, Node node, Node cause) throws RepositoryException { + SizedIterator nodes = getNodes(node); + long count = nodes.getSize(); + if (count < 0) { + for (count = 0; nodes.hasNext(); count++, nodes.next()); + } + + if (count == 0) { + Node parent = node.getParent(); + node.remove(); + removeRec(parent); + } + } + + /** + * This implementation does not actually join any nodes. It does however + * delete node if {@link #getProperties(Node)} returns an empty + * iterator. It does further recursively delete any parent of + * node which does not have any child node. + * + * @see org.apache.jackrabbit.flat.TreeManager#join(org.apache.jackrabbit.flat.ItemSequence, + * javax.jcr.Node, javax.jcr.Property) + */ + public void join(ItemSequence itemSequence, Node node, Property cause) throws RepositoryException { + SizedIterator properties = getProperties(node); + long count = properties.getSize(); + if (count < 0) { + for (count = 0; properties.hasNext(); count++, properties.next()); + } + + if (count == 0) { + removeRec(node); + } + } + + public Node getRoot() { + return root; + } + + public boolean isRoot(Node node) throws RepositoryException { + return node.isSame(root); + } + + /** + * Returns !node.hasNodes() + * @see org.apache.jackrabbit.flat.TreeManager#isLeaf(javax.jcr.Node) + */ + public boolean isLeaf(Node node) throws RepositoryException { + return !node.hasNodes(); + } + + public Comparator getOrder() { + return order; + } + + public boolean getAutoSave() { + return autoSave; + } + + // -----------------------------------------------------< internal >--- + + /** + * Returns a {@link SizedIterator} of the child nodes of node. + */ + @SuppressWarnings("unchecked") + protected SizedIterator getNodes(Node node) throws RepositoryException { + NodeIterator nodes = node.getNodes(); + return getSizedIterator(nodes, nodes.getSize()); + } + + /** + * Returns a {@link SizedIterator} of the properties of node which + * excludes the jcr.primaryType property. + */ + protected SizedIterator getProperties(final Node node) throws RepositoryException { + final PropertyIterator properties = node.getProperties(); + + @SuppressWarnings("unchecked") + final Iterator filtered = new FilterIterator(properties, new Predicate() { + public boolean evaluate(Object object) { + Property p = (Property) object; + try { + return !JcrConstants.JCR_PRIMARYTYPE.equals(p.getName()); + } + catch (RepositoryException ignore) { + return true; + } + + } + }); + + long size = properties.getSize(); + return getSizedIterator(filtered, size > 0 ? size - 1 : size); + } + + /** + * Creates and return an intermediate node for the given name + * as child node of parent. + */ + protected Node createIntermediateNode(Node parent, String name) throws RepositoryException { + return parent.addNode(name); + } + + /** + * Move node to the new parent. + */ + protected void move(Node node, Node parent) throws RepositoryException { + String oldPath = node.getPath(); + String newPath = parent.getPath() + "/" + node.getName(); + node.getSession().move(oldPath, newPath); + } + + /** + * Move property to the new parent. + */ + protected void move(Property property, Node parent) throws RepositoryException { + parent.setProperty(property.getName(), property.getValue()); + property.remove(); + } + + /** + * Wraps iterator into a {@link SizedIterator} given a + * size. The value of the size parameter must + * correctly reflect the number of items in iterator.Ê + */ + protected final SizedIterator getSizedIterator(final Iterator iterator, final long size) { + return new SizedIterator() { + public boolean hasNext() { + return iterator.hasNext(); + } + + public T next() { + return iterator.next(); + } + + public void remove() { + iterator.remove(); + } + + public long getSize() { + return size; + } + }; + } + + // -----------------------------------------------------< internal >--- + + private void split(Node node, Rank ranking, ItemSequence itemSequence) throws RepositoryException { + if (ranking.size() <= maxChildren) { + return; + } + + try { + Node grandParent; + if (isRoot(node)) { + grandParent = node; + } + else { + grandParent = node.getParent(); + + // leave first minChildren items where they are + ranking.take(minChildren); + } + + // move remaining items to new parents + for (int k = ranking.size() / minChildren; k > 0; k--) { + T item = ranking.take(1).next(); + String key = item.getName(); + + Node newParent; + if (grandParent.getPrimaryNodeType().hasOrderableChildNodes()) { + Node dest = itemSequence.getSuccessor(grandParent, key); + newParent = createIntermediateNode(grandParent, key); + grandParent.orderBefore(key, dest == null ? null : dest.getName()); + } + else { + newParent = createIntermediateNode(grandParent, key); + } + + move(item, newParent); + + int c = k > 1 ? minChildren - 1 : ranking.size(); + Iterator remaining = ranking.take(c); + + // If ordered, ranking returns an ordered iterator. So order will be correct here + while (remaining.hasNext()) { + move(remaining.next(), newParent); + } + } + + // If we did not reach root yet, recursively split the parent + if (!node.isSame(root)) { + split(itemSequence, grandParent, (Node) null); + } + } + catch (WrappedRepositoryException e) { + throw e.wrapped(); + } + } + + private void move(T item, Node parent) throws RepositoryException { + if (item.isNode()) { + move((Node) item, parent); + } + else { + move((Property) item, parent); + } + } + + private void removeRec(Node node) throws RepositoryException { + Node n = node; + while (!n.hasNodes() && !isRoot(n)) { + Node d = n; + n = n.getParent(); + d.remove(); + } + } + + private static class WrappedRepositoryException extends RuntimeException { + private final RepositoryException wrapped; + + public WrappedRepositoryException(RepositoryException e) { + super(); + this.wrapped = e; + } + + public RepositoryException wrapped() { + return wrapped; + } + } + +} \ No newline at end of file Index: src/main/java/org/apache/jackrabbit/flat/SizedIterator.java =================================================================== --- src/main/java/org/apache/jackrabbit/flat/SizedIterator.java (revision 0) +++ src/main/java/org/apache/jackrabbit/flat/SizedIterator.java (revision 0) @@ -0,0 +1,35 @@ +/* + * 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.flat; + +import java.util.Iterator; + +/** + * SizedIterator extends {@link Iterator} with a + * getSize method. + * + * @param the type of elements of this iterator + */ +public interface SizedIterator extends Iterator { + + /** + * The number of elements of this iterator or -1 if not known. + * + * @return number of elements. + */ + long getSize(); +}