You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ck...@apache.org on 2007/08/15 09:16:11 UTC

svn commit: r566042 - in /jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene: ./ hits/

Author: ckiehl
Date: Wed Aug 15 00:16:10 2007
New Revision: 566042

URL: http://svn.apache.org/viewvc?view=rev&rev=566042
Log:
JCR-1041: Avoid using BitSets in ChildAxisQuery to minimize memory usage

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AdaptingHits.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ArrayHits.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/BitSetHits.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/Hits.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/HitsIntersection.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ScorerHits.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java?view=diff&rev=566042&r1=566041&r2=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java Wed Aug 15 00:16:10 2007
@@ -18,6 +18,10 @@
 
 import org.apache.jackrabbit.core.NodeId;
 import org.apache.jackrabbit.core.query.LocationStepQueryNode;
+import org.apache.jackrabbit.core.query.lucene.hits.AdaptingHits;
+import org.apache.jackrabbit.core.query.lucene.hits.Hits;
+import org.apache.jackrabbit.core.query.lucene.hits.HitsIntersection;
+import org.apache.jackrabbit.core.query.lucene.hits.ScorerHits;
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.ItemStateManager;
 import org.apache.jackrabbit.core.state.NodeState;
@@ -37,7 +41,6 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -238,19 +241,14 @@
         private final IndexReader reader;
 
         /**
-         * BitSet storing the id's of selected documents
-         */
-        private final BitSet hits;
-
-        /**
-         * List of UUIDs of selected nodes
+         * The next document id to return
          */
-        private List uuids = null;
+        private int nextDoc = -1;
 
         /**
-         * The next document id to return
+         * A <code>Hits</code> instance containing all hits
          */
-        private int nextDoc = -1;
+        private Hits hits;
 
         /**
          * Creates a new <code>ChildAxisScorer</code>.
@@ -261,7 +259,6 @@
         protected ChildAxisScorer(Similarity similarity, IndexReader reader) {
             super(similarity);
             this.reader = reader;
-            this.hits = new BitSet(reader.maxDoc());
         }
 
         /**
@@ -269,7 +266,10 @@
          */
         public boolean next() throws IOException {
             calculateChildren();
-            nextDoc = hits.nextSetBit(nextDoc + 1);
+            do {
+                nextDoc = hits.next();
+            } while (nextDoc >= 0 && !indexIsValid(nextDoc));
+            
             return nextDoc > -1;
         }
 
@@ -292,8 +292,10 @@
          */
         public boolean skipTo(int target) throws IOException {
             calculateChildren();
-            nextDoc = hits.nextSetBit(target);
-            return nextDoc > -1;
+            nextDoc = hits.skipTo(target);
+            while (!indexIsValid(nextDoc))
+                next();
+            return nextDoc > -1;            
         }
 
         /**
@@ -307,117 +309,113 @@
         }
 
         private void calculateChildren() throws IOException {
-            if (uuids == null) {
-                uuids = new ArrayList();
+            if (hits == null) {
+                
+                // collect all context nodes
+                List uuids = new ArrayList();
+                final Hits contextHits = new AdaptingHits();
                 contextScorer.score(new HitCollector() {
                     public void collect(int doc, float score) {
-                        hits.set(doc);
+                        contextHits.set(doc);
                     }
                 });
 
-                // collect nameTest hits
-                final BitSet nameTestHits = new BitSet();
-                if (nameTestScorer != null) {
-                    nameTestScorer.score(new HitCollector() {
-                        public void collect(int doc, float score) {
-                            nameTestHits.set(doc);
-                        }
-                    });
-                }
-
                 // read the uuids of the context nodes
-                for (int i = hits.nextSetBit(0); i >= 0; i = hits.nextSetBit(i + 1)) {
+                int i = contextHits.next();
+                while (i > -1) {
                     String uuid = reader.document(i).get(FieldNames.UUID);
                     uuids.add(uuid);
+                    i = contextHits.next();
                 }
+                
+                // collect all children of the context nodes
+                Hits childrenHits = new AdaptingHits();
 
-                // collect the doc ids of all child nodes. we reuse the existing
-                // bitset.
-                hits.clear();
                 TermDocs docs = reader.termDocs();
                 try {
                     for (Iterator it = uuids.iterator(); it.hasNext();) {
                         docs.seek(new Term(FieldNames.PARENT, (String) it.next()));
                         while (docs.next()) {
-                            hits.set(docs.doc());
+                            childrenHits.set(docs.doc());
                         }
                     }
                 } finally {
                     docs.close();
                 }
-                // filter out the child nodes that do not match the name test
-                // if there is any name test at all.
+                
                 if (nameTestScorer != null) {
-                    hits.and(nameTestHits);
+                    hits = new HitsIntersection(childrenHits, new ScorerHits(nameTestScorer));
+                } else {
+                    hits = childrenHits;
                 }
+            }
+        }
 
-                // filter by index
-                if (position != LocationStepQueryNode.NONE) {
-                    for (int i = hits.nextSetBit(0); i >= 0; i = hits.nextSetBit(i + 1)) {
-                        Document node = reader.document(i);
-                        NodeId parentId = NodeId.valueOf(node.get(FieldNames.PARENT));
-                        NodeId id = NodeId.valueOf(node.get(FieldNames.UUID));
-                        try {
-                            NodeState state = (NodeState) itemMgr.getItemState(parentId);
-                            if (nameTest == null) {
-                                // only select this node if it is the child at
-                                // specified position
-                                if (position == LocationStepQueryNode.LAST) {
-                                    // only select last
-                                    List childNodes = state.getChildNodeEntries();
-                                    if (childNodes.size() == 0
-                                            || !((NodeState.ChildNodeEntry) childNodes.get(childNodes.size() - 1))
-                                                .getId().equals(id)) {
-                                        hits.flip(i);
-                                    }
-                                } else {
-                                    List childNodes = state.getChildNodeEntries();
-                                    if (position < 1
-                                            || childNodes.size() < position
-                                            || !((NodeState.ChildNodeEntry) childNodes.get(position - 1)).getId().equals(id)) {
-                                        hits.flip(i);
-                                    }
+        private boolean indexIsValid(int i) throws IOException {
+            if (position != LocationStepQueryNode.NONE) {
+                Document node = reader.document(i);
+                NodeId parentId = NodeId.valueOf(node.get(FieldNames.PARENT));
+                NodeId id = NodeId.valueOf(node.get(FieldNames.UUID));
+                try {
+                    NodeState state = (NodeState) itemMgr.getItemState(parentId);
+                    if (nameTest == null) {
+                        // only select this node if it is the child at
+                        // specified position
+                        if (position == LocationStepQueryNode.LAST) {
+                            // only select last
+                            List childNodes = state.getChildNodeEntries();
+                            if (childNodes.size() == 0
+                                    || !((NodeState.ChildNodeEntry) childNodes.get(childNodes.size() - 1))
+                                        .getId().equals(id)) {
+                                return false;
+                            }
+                        } else {
+                            List childNodes = state.getChildNodeEntries();
+                            if (position < 1
+                                    || childNodes.size() < position
+                                    || !((NodeState.ChildNodeEntry) childNodes.get(position - 1)).getId().equals(id)) {
+                                return false;
+                            }
+                        }
+                    } else {
+                        // select the node when its index is equal to
+                        // specified position
+                        if (position == LocationStepQueryNode.LAST) {
+                            // only select last
+                            NodeState.ChildNodeEntry entry =
+                                    state.getChildNodeEntry(id);
+                            if (entry == null) {
+                                // no such child node, probably deleted meanwhile
+                                return false;
+                            } else {
+                                // only use the last one
+                                QName name = entry.getName();
+                                List childNodes = state.getChildNodeEntries(name);
+                                if (childNodes.size() == 0
+                                        || !((NodeState.ChildNodeEntry) childNodes.get(childNodes.size() - 1))
+                                            .getId().equals(id)) {
+                                    return false;
                                 }
+                            }
+                        } else {
+                            NodeState.ChildNodeEntry entry =
+                                    state.getChildNodeEntry(id);
+                            if (entry == null) {
+                                // no such child node, probably has been deleted meanwhile
+                                return false;
                             } else {
-                                // select the node when its index is equal to
-                                // specified position
-                                if (position == LocationStepQueryNode.LAST) {
-                                    // only select last
-                                    NodeState.ChildNodeEntry entry =
-                                            state.getChildNodeEntry(id);
-                                    if (entry == null) {
-                                        // no such child node, probably deleted meanwhile
-                                        hits.flip(i);
-                                    } else {
-                                        // only use the last one
-                                        QName name = entry.getName();
-                                        List childNodes = state.getChildNodeEntries(name);
-                                        if (childNodes.size() == 0
-                                                || !((NodeState.ChildNodeEntry) childNodes.get(childNodes.size() - 1))
-                                                    .getId().equals(id)) {
-                                            hits.flip(i);
-                                        }
-                                    }
-                                } else {
-                                    NodeState.ChildNodeEntry entry =
-                                            state.getChildNodeEntry(id);
-                                    if (entry == null) {
-                                        // no such child node, probably has been deleted meanwhile
-                                        hits.flip(i);
-                                    } else {
-                                        if (entry.getIndex() != position) {
-                                            hits.flip(i);
-                                        }
-                                    }
+                                if (entry.getIndex() != position) {
+                                    return false;
                                 }
                             }
-                        } catch (ItemStateException e) {
-                            // ignore this node, probably has been deleted meanwhile
-                            hits.flip(i);
                         }
                     }
+                } catch (ItemStateException e) {
+                    // ignore this node, probably has been deleted meanwhile
+                    return false;
                 }
             }
+            return true;
         }
     }
 }

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AdaptingHits.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AdaptingHits.java?view=auto&rev=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AdaptingHits.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AdaptingHits.java Wed Aug 15 00:16:10 2007
@@ -0,0 +1,123 @@
+/*
+ * 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.core.query.lucene.hits;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is an implementation of Hits which starts with marking hits in an 
+ * ArrayHits instance and switches to a BitSetHits instance if at least the 
+ * threshold of 8kb for the ArrayHits is reached and a BitSetHits instance 
+ * would consume less memory. 
+ */
+public class AdaptingHits implements Hits {
+
+    /**
+     * Logger instance for this class.
+     */
+    private static final Logger log = LoggerFactory.getLogger(AdaptingHits.class);
+    
+    /**
+     * The lower threshold before a conversion is tried
+     */
+    private static final int DEFAULT_THRESHOLD = 2048;
+    
+    /**
+     * Internal hits instance
+     */
+    private Hits hits;
+    
+    /**
+     * The maximum doc number in hits. Used to calculate the expected
+     * BitSetHits memory footprint.
+     */
+    private int maxDoc;
+    
+    /**
+     * The total number of hits. Used to calculate the memory footprint of the
+     * initial ArrayHits instance.
+     */
+    private int docCount;
+
+    private int threshold;
+    
+    public AdaptingHits() {
+        this(DEFAULT_THRESHOLD);
+    }
+
+    public AdaptingHits(int threshold) {
+        this.threshold = threshold;
+        hits = new ArrayHits();
+        maxDoc = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int next() throws IOException {
+        // delegate to the internal Hits instance
+        return hits.next();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(int doc) {
+        hits.set(doc);
+        docCount++;
+        if (doc > maxDoc) {
+            maxDoc = doc;
+        }
+        
+        if (docCount > threshold && (hits instanceof ArrayHits)) {
+            int intArraySize = docCount * 4;
+            int bitSetSize = maxDoc / 8;
+            if (bitSetSize < intArraySize) {
+                log.debug("BitSet is smaller than int[]: "
+                        + bitSetSize + " vs " + intArraySize);
+                BitSetHits bitSetHits = new BitSetHits();
+                int i = 0;
+                while (i > -1) {
+                    try {
+                        i = hits.next();
+                        if (i > -1)
+                            bitSetHits.set(i);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                hits = bitSetHits;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int skipTo(int target) throws IOException {
+        // delegate to the internal Hits instance
+        return hits.skipTo(target);
+    }
+    
+    Hits getInternalHits() {
+        return hits;
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ArrayHits.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ArrayHits.java?view=auto&rev=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ArrayHits.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ArrayHits.java Wed Aug 15 00:16:10 2007
@@ -0,0 +1,95 @@
+/*
+ * 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.core.query.lucene.hits;
+
+import java.util.Arrays;
+
+/**
+ * Uses an integer array to store the hit set. This implementation uses less
+ * memory than {@link BitSetHits} if the total number of documents is high and
+ * and the number of hits is low or if your hits doc numbers are mostly in the
+ * upper part of your doc number range.
+ * If you don't know about your hit distribution in advance use 
+ * {@link AdaptingHits} instead.
+ */
+public class ArrayHits implements Hits {
+    
+    private static final int INITIAL_SIZE = 100;
+    private int[] hits;
+    private int index;
+    private boolean initialized;
+
+    public ArrayHits() {
+        this(INITIAL_SIZE);
+    }
+
+    public ArrayHits(int initialSize) {
+        hits = new int[initialSize];
+        index = 0;
+        initialized = false;
+    }
+
+    private void initialize() {
+        if (!initialized) {
+            Arrays.sort(hits);
+            index = hits.length - index;
+            initialized = true;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(int doc) {
+        if (initialized) {
+            throw new IllegalStateException(
+                    "You must not call set() after next() or skipTo()");
+        }
+        if (index >= hits.length) {
+            int[] resizedHits = new int[hits.length * 2];
+            System.arraycopy(hits, 0, resizedHits, 0, hits.length);
+            hits = resizedHits;
+        }
+        hits[index++] = doc;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int next() {
+        initialize();
+        if (index >= hits.length)
+            return -1;
+
+        return hits[index++];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int skipTo(int target) {
+        initialize();
+        for (int i = index; i < hits.length; i++) {
+            int nextDocValue = hits[i];
+            if (nextDocValue >= target) {
+                index = i;
+                break;
+            }
+        }
+        return next();
+    }
+}

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/BitSetHits.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/BitSetHits.java?view=auto&rev=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/BitSetHits.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/BitSetHits.java Wed Aug 15 00:16:10 2007
@@ -0,0 +1,58 @@
+/*
+ * 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.core.query.lucene.hits;
+
+import java.util.BitSet;
+
+/**
+ * Uses a BitSet instance to store the hit set. Keep in mind that this BitSet
+ * is at least as large as the highest doc number in the hit set. This means it
+ * might need of lot of memory for large indexes.
+ */
+public class BitSetHits implements Hits {
+    private BitSet hits;
+    private int index;
+
+    public BitSetHits() {
+        hits = new BitSet();
+        index = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(int doc) {
+        hits.set(doc);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int next() {
+        int result = hits.nextSetBit(index);
+        index = result + 1;
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int skipTo(int target) {
+        index = target;
+        return next();
+    }
+}

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/Hits.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/Hits.java?view=auto&rev=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/Hits.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/Hits.java Wed Aug 15 00:16:10 2007
@@ -0,0 +1,45 @@
+/*
+ * 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.core.query.lucene.hits;
+
+import java.io.IOException;
+
+/**
+ * Representation of a set of hits 
+ */
+public interface Hits {
+    
+    /**
+     * Marks the document with doc number <code>doc</code> as a hit. 
+     * Implementations may throw an exception if you call set() after next() or 
+     * skipTo() has been called.
+     */
+    void set(int doc);
+    
+    /**
+     * Return the doc number of the next hit in the set. Subsequent calls never
+     * return the same doc number. 
+     */
+    int next() throws IOException;
+    
+    /** 
+     * Skips to the first match beyond the current whose document number is
+     * greater than or equal to the given target.
+     */
+    int skipTo(int target) throws IOException;
+    
+}
\ No newline at end of file

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/HitsIntersection.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/HitsIntersection.java?view=auto&rev=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/HitsIntersection.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/HitsIntersection.java Wed Aug 15 00:16:10 2007
@@ -0,0 +1,80 @@
+/*
+ * 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.core.query.lucene.hits;
+
+import java.io.IOException;
+
+/**
+ * Creates the intersection of two hit sets.
+ */
+public class HitsIntersection implements Hits {
+
+    private final Hits hits1;
+    private final Hits hits2;
+
+    private int nextChildrenHit = -1;
+    private int nextNameTestHit = -1;
+
+    public HitsIntersection(Hits hits1, Hits hits2) {
+        this.hits1 = hits1;
+        this.hits2 = hits2;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(int doc) {
+        throw new UnsupportedOperationException();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public int next() throws IOException {
+        do {
+            if (nextChildrenHit == nextNameTestHit) {
+                nextNameTestHit = hits2.next();
+                nextChildrenHit = hits1.next();
+            } else if (nextNameTestHit < nextChildrenHit) {
+                nextNameTestHit = hits2.skipTo(nextChildrenHit);
+            } else {
+                nextChildrenHit = hits1.skipTo(nextNameTestHit);
+            }
+        } while (nextChildrenHit > -1 && nextNameTestHit > -1
+                && nextNameTestHit != nextChildrenHit);
+
+        int nextDoc = -1;
+        if (nextChildrenHit == nextNameTestHit) {
+            nextDoc = nextChildrenHit;
+        }
+        return nextDoc;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int skipTo(int target) throws IOException {
+        nextChildrenHit = hits1.skipTo(target);
+        nextNameTestHit = hits2.skipTo(target);
+        if (nextChildrenHit == nextNameTestHit) {
+            return nextChildrenHit;
+        } else {
+            return next();
+        }
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ScorerHits.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ScorerHits.java?view=auto&rev=566042
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ScorerHits.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/ScorerHits.java Wed Aug 15 00:16:10 2007
@@ -0,0 +1,62 @@
+/*
+ * 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.core.query.lucene.hits;
+
+import java.io.IOException;
+
+import org.apache.lucene.search.Scorer;
+
+/**
+ * Wraps a {@link org.apache.lucene.search.Scorer} in a {@link Hits} instance.
+ */
+public class ScorerHits implements Hits {
+    
+    private final Scorer scorer;
+
+    public ScorerHits(Scorer scorer) {
+        this.scorer = scorer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void set(int doc) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int next() throws IOException {
+        if (scorer.next()) {
+            return scorer.doc();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int skipTo(int target) throws IOException {
+        if (scorer.skipTo(target)) {
+            return scorer.doc();
+        } else {
+            return -1;
+        }
+    }
+}