Package org.apache.jackrabbit.core.query.lucene

Source Code of org.apache.jackrabbit.core.query.lucene.RangeQuery$RangeQueryScorer

/*
* Copyright 2004-2005 The Apache Software Foundation or its licensors,
*                     as applicable.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.core.query.lucene;

import org.apache.lucene.search.Query;
import org.apache.lucene.search.Weight;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.index.TermDocs;
import org.apache.log4j.Logger;
import org.apache.commons.collections.map.LRUMap;

import java.io.IOException;
import java.util.BitSet;
import java.util.Map;
import java.util.WeakHashMap;

/**
* Implements a variant of the lucene class {@link org.apache.lucene.search.RangeQuery}.
* This class does not rewrite to basic {@link org.apache.lucene.search.TermQuery}
* but will calculate the matching documents itself. That way a
* <code>TooManyClauses</code> can be avoided.
*/
public class RangeQuery extends Query {

    /**
     * Logger instance for this class.
     */
    private static final Logger log = Logger.getLogger(RangeQuery.class);

    /**
     * Simple result cache for previously calculated hits.
     * key=IndexReader value=Map{key=String:range,value=BitSet:hits}
     */
    private static final Map cache = new WeakHashMap();

    /**
     * The lower term. May be <code>null</code> if <code>upperTerm</code> is not
     * <code>null</code>.
     */
    private Term lowerTerm;

    /**
     * The upper term. May be <code>null</code> if <code>lowerTerm</code> is not
     * <code>null</code>.
     */
    private Term upperTerm;

    /**
     * If <code>true</code> the range interval is inclusive.
     */
    private boolean inclusive;

    /**
     * Creates a new RangeQuery. The lower or the upper term may be
     * <code>null</code>, but not both!
     *
     * @param lowerTerm the lower term of the interval, or <code>null</code>
     * @param upperTerm the upper term of the interval, or <code>null</code>.
     * @param inclusive if <code>true</code> the interval is inclusive.
     */
    public RangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive) {
        if (lowerTerm == null && upperTerm == null) {
            throw new IllegalArgumentException("At least one term must be non-null");
        }
        if (lowerTerm != null && upperTerm != null && lowerTerm.field() != upperTerm.field()) {
            throw new IllegalArgumentException("Both terms must be for the same field");
        }

        // if we have a lowerTerm, start there. otherwise, start at beginning
        if (lowerTerm != null) {
            this.lowerTerm = lowerTerm;
        } else {
            this.lowerTerm = new Term(upperTerm.field(), "");
        }

        this.upperTerm = upperTerm;
        this.inclusive = inclusive;
    }

    /**
     * Tries to rewrite this query into a standard lucene RangeQuery.
     * This rewrite might fail with a TooManyClauses exception. If that
     * happens, we use our own implementation.
     *
     * @param reader the index reader.
     * @return the rewritten query or this query if rewriting is not possible.
     * @throws IOException if an error occurs.
     */
    public Query rewrite(IndexReader reader) throws IOException {
        Query stdRangeQueryImpl
                = new org.apache.lucene.search.RangeQuery(lowerTerm, upperTerm, inclusive);
        try {
            return stdRangeQueryImpl.rewrite(reader);
        } catch (BooleanQuery.TooManyClauses e) {
            log.debug("Too many terms to enumerate, using custom RangeQuery");
            // failed, use own implementation
            return this;
        }
    }

    /**
     * Creates the <code>Weight</code> for this query.
     *
     * @param searcher the searcher to use for the <code>Weight</code>.
     * @return the <code>Weigth</code> for this query.
     */
    protected Weight createWeight(Searcher searcher) {
        return new RangeQueryWeight(searcher);
    }

    /**
     * Returns a string representation of this query.
     * @param field the field name for which to create a string representation.
     * @return a string representation of this query.
     */
    public String toString(String field) {
        StringBuffer buffer = new StringBuffer();
        if (!getField().equals(field)) {
            buffer.append(getField());
            buffer.append(":");
        }
        buffer.append(inclusive ? "[" : "{");
        buffer.append(lowerTerm != null ? lowerTerm.text() : "null");
        buffer.append(" TO ");
        buffer.append(upperTerm != null ? upperTerm.text() : "null");
        buffer.append(inclusive ? "]" : "}");
        if (getBoost() != 1.0f) {
            buffer.append("^");
            buffer.append(Float.toString(getBoost()));
        }
        return buffer.toString();
    }

    /**
     * Returns the field name for this query.
     */
    private String getField() {
        return (lowerTerm != null ? lowerTerm.field() : upperTerm.field());
    }

    //--------------------------< RangeQueryWeight >----------------------------

    /**
     * The <code>Weight</code> implementation for this <code>RangeQuery</code>.
     */
    private class RangeQueryWeight implements Weight {

        /**
         * The searcher in use
         */
        private final Searcher searcher;

        /**
         * Creates a new <code>RangeQueryWeight</code> instance using
         * <code>searcher</code>.
         *
         * @param searcher a <code>Searcher</code> instance.
         */
        RangeQueryWeight(Searcher searcher) {
            this.searcher = searcher;
        }

        /**
         * Returns this <code>RangeQuery</code>.
         *
         * @return this <code>RangeQuery</code>.
         */
        public Query getQuery() {
            return RangeQuery.this;
        }

        /**
         * {@inheritDoc}
         */
        public float getValue() {
            return 1.0f;
        }

        /**
         * {@inheritDoc}
         */
        public float sumOfSquaredWeights() throws IOException {
            return 1.0f;
        }

        /**
         * {@inheritDoc}
         */
        public void normalize(float norm) {
        }

        /**
         * Creates a scorer for this <code>Rangequery</code>.
         *
         * @param reader a reader for accessing the index.
         * @return a <code>RangeQueryScorer</code>.
         * @throws IOException if an error occurs while reading from the index.
         */
        public Scorer scorer(IndexReader reader) throws IOException {
            return new RangeQueryScorer(searcher.getSimilarity(), reader);
        }

        /**
         * {@inheritDoc}
         */
        public Explanation explain(IndexReader reader, int doc) throws IOException {
            return new Explanation();
        }
    }

    //------------------------< RangeQueryScorer >------------------------------

    /**
     * Implements a <code>Scorer</code> for this <code>RangeQuery</code>.
     */
    private final class RangeQueryScorer extends Scorer {

        /**
         * The index reader to use for calculating the matching documents.
         */
        private final IndexReader reader;

        /**
         * The documents ids that match this range query.
         */
        private final BitSet hits;

        /**
         * Set to <code>true</code> when the hits have been calculated.
         */
        private boolean hitsCalculated = false;

        /**
         * The next document id to return
         */
        private int nextDoc = -1;

        /**
         * The cache key to use to store the results.
         */
        private final String cacheKey;

        /**
         * The map to store the results.
         */
        private final Map resultMap;

        /**
         * Creates a new RangeQueryScorer.
         * @param similarity the similarity implementation.
         * @param reader the index reader to use.
         */
        RangeQueryScorer(Similarity similarity, IndexReader reader) {
            super(similarity);
            this.reader = reader;
            StringBuffer key = new StringBuffer();
            key.append(lowerTerm != null ? lowerTerm.field() : upperTerm.field());
            key.append('\uFFFF');
            key.append(lowerTerm != null ? lowerTerm.text() : "");
            key.append('\uFFFF');
            key.append(upperTerm != null ? upperTerm.text() : "");
            key.append('\uFFFF');
            key.append(inclusive);
            this.cacheKey = key.toString();
            // check cache
            synchronized (cache) {
                Map m = (Map) cache.get(reader);
                if (m == null) {
                    m = new LRUMap(10);
                    cache.put(reader, m);
                }
                resultMap = m;
            }
            synchronized (resultMap) {
                BitSet result = (BitSet) resultMap.get(cacheKey);
                if (result == null) {
                    result = new BitSet(reader.maxDoc());
                } else {
                    hitsCalculated = true;
                }
                hits = result;
            }
        }

        /**
         * {@inheritDoc}
         */
        public boolean next() throws IOException {
            calculateHits();
            nextDoc = hits.nextSetBit(nextDoc + 1);
            return nextDoc > -1;
        }

        /**
         * {@inheritDoc}
         */
        public int doc() {
            return nextDoc;
        }

        /**
         * {@inheritDoc}
         */
        public float score() {
            return 1.0f;
        }

        /**
         * {@inheritDoc}
         */
        public boolean skipTo(int target) {
            nextDoc = hits.nextSetBit(target);
            return nextDoc > -1;
        }

        /**
         * Returns an empty Explanation object.
         * @return an empty Explanation object.
         */
        public Explanation explain(int doc) {
            return new Explanation();
        }

        /**
         * Calculates the ids of the documents matching this range query.
         * @throws IOException if an error occurs while reading from the index.
         */
        private void calculateHits() throws IOException {
            if (hitsCalculated) {
                return;
            }

            TermEnum enumerator = reader.terms(lowerTerm);

            try {
                boolean checkLower = false;
                if (!inclusive) {
                    // make adjustments to set to exclusive
                    checkLower = true;
                }

                String testField = getField();

                TermDocs docs = reader.termDocs();
                try {
                    do {
                        Term term = enumerator.term();
                        if (term != null && term.field() == testField) {
                            if (!checkLower || term.text().compareTo(lowerTerm.text()) > 0) {
                                checkLower = false;
                                if (upperTerm != null) {
                                    int compare = upperTerm.text().compareTo(term.text());
                                    // if beyond the upper term, or is exclusive and
                                    // this is equal to the upper term, break out
                                    if ((compare < 0) || (!inclusive && compare == 0)) {
                                        break;
                                    }
                                }

                                docs.seek(enumerator);
                                while (docs.next()) {
                                    hits.set(docs.doc());
                                }
                            }
                        } else {
                            break;
                        }
                    } while (enumerator.next());
                } finally {
                    docs.close();
                }
            } finally {
                enumerator.close();
            }
            hitsCalculated = true;
            // put to cache
            synchronized (resultMap) {
                resultMap.put(cacheKey, hits);
            }
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.query.lucene.RangeQuery$RangeQueryScorer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.