org.pageseeder.flint.lucene.facet.FlexibleRangeFacet.java Source code

Java tutorial

Introduction

Here is the source code for org.pageseeder.flint.lucene.facet.FlexibleRangeFacet.java

Source

/*
 * Copyright 2015 Allette Systems (Australia)
 * http://www.allette.com.au
 *
 * 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.pageseeder.flint.lucene.facet;

import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.DateTools.Resolution;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.pageseeder.flint.lucene.search.DocumentCounter;
import org.pageseeder.flint.lucene.search.Filter;
import org.pageseeder.flint.lucene.search.Terms;
import org.pageseeder.flint.lucene.util.Beta;
import org.pageseeder.flint.lucene.util.Bucket;
import org.pageseeder.flint.lucene.util.Bucket.Entry;
import org.pageseeder.flint.lucene.util.Dates;
import org.pageseeder.xmlwriter.XMLWriter;

/**
 * A facet implementation using a simple index field.
 *
 * @author Jean-Baptiste Reure
 *
 * @version 5.1.3
 */
@Beta
public abstract class FlexibleRangeFacet extends FlexibleFacet<FlexibleRangeFacet.Range> {

    /**
     * The default number of facet values if not specified.
     */
    public static final int DEFAULT_MAX_NUMBER_OF_VALUES = 10;

    /**
     * The queries used to calculate each facet.
     */
    protected transient Bucket<Range> bucket;

    /**
     * If the facet was computed in a "flexible" way
     */
    protected transient boolean flexible = false;

    /**
     * The total number of ranges containing the field used in this facet
     */
    protected transient int totalRanges = 0;

    /**
     * Creates a new facet with the specified name;
     *
     * @param name     The name of the facet.
     */
    protected FlexibleRangeFacet(String name) {
        super(name);
    }

    /**
     * Computes each facet option as a flexible facet.
     * All filters but the ones using the same field as this facet are applied to the base query before computing the numbers.
     *
     * @param searcher the index search to use.
     * @param base     the base query.
     * @param filters  the filters applied to the base query
     * @param size     the maximum number of field values to compute.
     *
     * @throws IOException if thrown by the searcher.
     */
    public void compute(IndexSearcher searcher, Query base, List<Filter> filters, int size) throws IOException {
        // If the base is null, simply calculate for each query
        if (base == null) {
            compute(searcher, size);
        } else {
            if (size < 0)
                throw new IllegalArgumentException("size < 0");
            this.totalRanges = 0;
            // find all terms
            List<Term> terms = Terms.terms(searcher.getIndexReader(), this._name);
            // Otherwise, re-compute the query without the corresponding filter 
            Query filtered = base;
            if (filters != null) {
                this.flexible = true;
                for (Filter filter : filters) {
                    if (!this._name.equals(filter.name()))
                        filtered = filter.filterQuery(filtered);
                }
            }
            DocumentCounter counter = new DocumentCounter();
            Map<Range, Integer> ranges = new HashMap<>();
            for (Term t : terms) {
                // find range
                Range r = findRange(t);
                if (r == null)
                    r = OTHER;
                // find count
                BooleanQuery query = new BooleanQuery();
                query.add(filtered, Occur.MUST);
                query.add(termToQuery(t), Occur.MUST);
                searcher.search(query, counter);
                int count = counter.getCount();
                if (count > 0) {
                    // add to map
                    Integer ec = ranges.get(r);
                    ranges.put(r, Integer.valueOf(count + (ec == null ? 0 : ec.intValue())));
                }
                counter.reset();
            }
            this.totalRanges = ranges.size();
            // add to bucket
            Bucket<Range> b = new Bucket<>(size);
            for (Range r : ranges.keySet()) {
                b.add(r, ranges.get(r));
            }
            this.bucket = b;
        }
    }

    /**
     * Computes each facet option.
     *
     * <p>Same as <code>compute(searcher, base, 10);</code>.
     *
     * <p>Defaults to 10.
     *
     * @see #compute(IndexSearcher, Query, int)
     *
     * @param searcher the index search to use.
     * @param base     the base query.
     *
     * @throws IOException if thrown by the searcher.
     */
    public void compute(IndexSearcher searcher, Query base, int size) throws IOException {
        compute(searcher, base, null, size);
    }

    /**
     * Computes each facet option.
     *
     * <p>Same as <code>compute(searcher, base, 10);</code>.
     *
     * <p>Defaults to 10.
     *
     * @see #compute(IndexSearcher, Query, int)
     *
     * @param searcher the index search to use.
     * @param base     the base query.
     *
     * @throws IOException if thrown by the searcher.
     */
    public void compute(IndexSearcher searcher, Query base) throws IOException {
        compute(searcher, base, null, DEFAULT_MAX_NUMBER_OF_VALUES);
    }

    /**
     * Computes each facet option as a flexible facet.
     *
     * <p>Same as <code>computeFlexible(searcher, base, filters, 10);</code>.
     *
     * <p>Defaults to 10.
     *
     * @see #compute(IndexSearcher, Query, List, int)
     *
     * @param searcher the index search to use.
     * @param base     the base query.
     * @param filters  the filters applied to the base query
     *
     * @throws IOException if thrown by the searcher.
     */
    public void compute(IndexSearcher searcher, Query base, List<Filter> filters) throws IOException {
        compute(searcher, base, filters, DEFAULT_MAX_NUMBER_OF_VALUES);
    }

    /**
     * Computes each facet option without a base query.
     *
     * @param searcher the index search to use.
     * @param size     the number of facet values to calculate.
     *
     * @throws IOException if thrown by the searcher.
     */
    protected void compute(IndexSearcher searcher, int size) throws IOException {
        // find all terms
        List<Term> terms = Terms.terms(searcher.getIndexReader(), this._name);
        DocumentCounter counter = new DocumentCounter();
        Map<Range, Integer> ranges = new HashMap<>();
        for (Term t : terms) {
            // find the range
            Range r = findRange(t);
            if (r == null)
                r = OTHER;
            // find number
            searcher.search(termToQuery(t), counter);
            int count = counter.getCount();
            if (count > 0) {
                // add to map
                Integer ec = ranges.get(r);
                ranges.put(r, Integer.valueOf(count + (ec == null ? 0 : ec.intValue())));
            }
            counter.reset();
        }
        // set totals
        this.totalRanges = ranges.size();
        // add to bucket
        Bucket<Range> b = new Bucket<>(size);
        for (Range r : ranges.keySet()) {
            b.add(r, ranges.get(r));
        }
        this.bucket = b;
    }

    /**
     * Create a query for the term given.
     * 
     * @param t the term
     * 
     * @return the query
     */
    protected Query termToQuery(Term t) {
        return new TermQuery(t);
    }

    public abstract String getType();

    protected abstract void rangeToXML(Range range, int cardinality, XMLWriter xml) throws IOException;

    protected abstract Range findRange(Term t);

    @Override
    public void toXML(XMLWriter xml) throws IOException {
        xml.openElement("facet", true);
        xml.attribute("name", this._name);
        xml.attribute("type", getType());
        xml.attribute("flexible", String.valueOf(this.flexible));
        if (!this.flexible) {
            xml.attribute("total-ranges", this.totalRanges);
        }
        if (this.bucket != null) {
            for (Entry<Range> e : this.bucket.entrySet()) {
                if (e.item() == OTHER) {
                    xml.openElement("remaining-range");
                    xml.attribute("cardinality", e.count());
                    xml.closeElement();
                } else {
                    rangeToXML(e.item(), e.count(), xml);
                }
            }
        }
        xml.closeElement();
    }

    public Bucket<Range> getValues() {
        return this.bucket;
    }

    public int getTotalRanges() {
        return this.totalRanges;
    }

    public static class Range implements Comparable<Range> {
        private final String _min;
        private final String _max;
        private boolean _includeMin;
        private boolean _includeMax;
        private Resolution _resolution;

        private Range(String min, boolean withMin, String max, boolean withMax) {
            this(min, withMin, max, withMax, null);
        }

        private Range(String min, boolean withMin, String max, boolean withMax, Resolution resolution) {
            this._max = max;
            this._min = min;
            this._includeMin = withMin;
            this._includeMax = withMax;
        }

        public String getMin() {
            return this._min;
        }

        public String getMax() {
            return this._max;
        }

        public String getFormattedMin() {
            return this._resolution != null ? toDateString(this._min, this._resolution) : this._min;
        }

        public String getFormattedMax() {
            return this._resolution != null ? toDateString(this._max, this._resolution) : this._max;
        }

        public boolean includeMax() {
            return this._includeMax;
        }

        public boolean includeMin() {
            return this._includeMin;
        }

        @Override
        public String toString() {
            return (this._includeMin ? '[' : '{') + this._min + '-' + this._max + (this._includeMax ? ']' : '}');
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Range) {
                Range r = (Range) obj;
                return ((r._min == null && this._min == null) || (r._min != null && r._min.equals(this._min)))
                        && ((r._max == null && this._max == null) || (r._max != null && r._max.equals(this._max)))
                        && this._includeMin == r._includeMin && this._includeMax == r._includeMax;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return (this._min != null ? this._min.hashCode() * 13 : 13)
                    + (this._max != null ? this._max.hashCode() * 11 : 11) + (this._includeMin ? 17 : 7)
                    + (this._includeMax ? 5 : 3);
        }

        @Override
        public int compareTo(Range o) {
            if (this._min == null) {
                if (o._min != null)
                    return -1;
                if (this._max == null)
                    return -1;
                if (o._max == null)
                    return 1;
                return this._max.compareTo(o._max);
            } else {
                if (o._min == null)
                    return 1;
                return this._min.compareTo(o._min);
            }
        }

        public static Range stringRange(String min, String max) {
            return stringRange(min, true, max, true);
        }

        public static Range stringRange(String min, boolean withMin, String max, boolean withMax) {
            return new Range(min, withMin, max, withMax);
        }

        public static Range numericRange(Number min, Number ma) {
            return numericRange(min, true, ma, true);
        }

        public static Range numericRange(Number min, boolean withMin, Number ma, boolean withMax) {
            return new Range(min == null ? null : min.toString(), withMin, ma == null ? null : ma.toString(),
                    withMax);
        }

        public static Range dateRange(Date min, Date max, Resolution res) {
            return dateRange(min, true, max, true, res);
        }

        public static Range dateRange(Date min, boolean withMin, Date max, boolean withMax, Resolution res) {
            return new Range(Dates.toString(min, res), withMin, Dates.toString(max, res), withMax, res);
        }
    }

    public static boolean isOther(Range range) {
        return range == OTHER;
    }

    public static final Range OTHER = new Range((String) null, false, (String) null, false);

}