/* * 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; import org.apache.jackrabbit.spi.Name; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.SortField; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * <code>SortedMultiColumnQueryHits</code> implements sorting of query hits * based on {@link Ordering}s. */ public class SortedMultiColumnQueryHits extends FilterMultiColumnQueryHits { /** * Iterator over sorted ScoreNode[]s. */ private final Iterator<ScoreNode[]> it; /** * Creates sorted query hits. * * @param hits the hits to sort. * @param orderings the ordering specifications. * @param reader the current index reader. * @throws IOException if an error occurs while reading from the index. */ public SortedMultiColumnQueryHits(MultiColumnQueryHits hits, Ordering[] orderings, IndexReader reader) throws IOException { super(hits); List<ScoreNode[]> sortedHits = new ArrayList<ScoreNode[]>(); ScoreNode[] next; while ((next = hits.nextScoreNodes()) != null) { sortedHits.add(next); } try { Collections.sort(sortedHits, new ScoreNodeComparator( reader, orderings, hits.getSelectorNames(), sortedHits.size())); } catch (RuntimeException e) { // might be thrown by ScoreNodeComparator#compare throw Util.createIOException(e); } this.it = sortedHits.iterator(); } /** * {@inheritDoc} */ public ScoreNode[] nextScoreNodes() throws IOException { if (it.hasNext()) { return it.next(); } else { return null; } } /** * {@inheritDoc} */ public void skip(int n) throws IOException { while (n-- > 0) { nextScoreNodes(); } } /** * A comparator that compares ScoreNode[]. */ private static final class ScoreNodeComparator implements Comparator<ScoreNode[]> { /** * The current index reader. */ private final IndexReader reader; /** * The ordering specifications. */ private final Ordering[] orderings; /** * The selector name index for each of the {@link #orderings}. */ private final int[] idx; /** * The score doc comparator for each of the {@link #orderings}. */ private final ScoreDocComparator[] comparators; /** * The reverse flag for each of the {@link #orderings}. */ private final boolean[] isReverse; /** * Reusable ScoreDoc for use in {@link #compare(ScoreNode[], ScoreNode[])}. */ private final ScoreDoc doc1 = new ScoreDoc(0, 1.0f); /** * Reusable ScoreDoc for use in {@link #compare(ScoreNode[], ScoreNode[])}. */ private final ScoreDoc doc2 = new ScoreDoc(0, 1.0f); /** * Creates a new comparator. * * @param reader the current index reader. * @param orderings the ordering specifications. * @param selectorNames the selector names associated with the * ScoreNode[] used in * {@link #compare(ScoreNode[], ScoreNode[])}. * @throws IOException if an error occurs while reading from the index. */ private ScoreNodeComparator(IndexReader reader, Ordering[] orderings, Name[] selectorNames, int numHits) throws IOException { this.reader = reader; this.orderings = orderings; List<Name> names = Arrays.asList(selectorNames); this.idx = new int[orderings.length]; this.comparators = new ScoreDocComparator[orderings.length]; this.isReverse = new boolean[orderings.length]; for (int i = 0; i < orderings.length; i++) { idx[i] = names.indexOf(orderings[i].getSelectorName()); SortField sf = orderings[i].getSortField(); if (sf.getComparatorSource() != null) { FieldComparator c = sf.getComparatorSource().newComparator(sf.getField(), numHits, 0, false); assert c instanceof FieldComparatorBase; comparators[i] = new ScoreDocComparator((FieldComparatorBase) c); comparators[i].setNextReader(reader, 0); } isReverse[i] = sf.getReverse(); } } /** * {@inheritDoc} */ public int compare(ScoreNode[] sn1, ScoreNode[] sn2) { for (int i = 0; i < orderings.length; i++) { int c; int scoreNodeIndex = idx[i]; ScoreNode n1 = sn1[scoreNodeIndex]; ScoreNode n2 = sn2[scoreNodeIndex]; if (n1 == n2) { continue; } else if (n1 == null) { c = -1; } else if (n2 == null) { c = 1; } else if (comparators[i] != null) { try { doc1.doc = n1.getDoc(reader); doc1.score = n1.getScore(); doc2.doc = n2.getDoc(reader); doc2.score = n2.getScore(); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } c = comparators[i].compareDocs(doc1.doc, doc2.doc); } else { // compare score c = new Float(n1.getScore()).compareTo(n2.getScore()); } if (c != 0) { if (isReverse[i]) { c = -c; } return c; } } return 0; } } private static final class ScoreDocComparator extends FieldComparatorDecorator { public ScoreDocComparator(FieldComparatorBase base) { super(base); } public int compareDocs(int doc1, int doc2) { return compare(sortValue(doc1), sortValue(doc2)); } } }