blob: 2006f1dc950087e024a8033fe80f74eaf957d58a [file] [log] [blame]
package org.apache.lucene.facet;
/*
* 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.
*/
import java.io.IOException;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Scorer.ChildScorer;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
/** Collector that scrutinizes each hit to determine if it
* passed all constraints (a true hit) or if it missed
* exactly one dimension (a near-miss, to count for
* drill-sideways counts on that dimension). */
class DrillSidewaysCollector extends Collector {
private final Collector hitCollector;
private final Collector drillDownCollector;
private final Collector[] drillSidewaysCollectors;
private final Scorer[] subScorers;
private final int exactCount;
// Maps Weight to either -1 (mainQuery) or to integer
// index of the dims drillDown. We needs this when
// visiting the child scorers to correlate back to the
// right scorers:
private final Map<Weight,Integer> weightToIndex = new IdentityHashMap<Weight,Integer>();
private Scorer mainScorer;
public DrillSidewaysCollector(Collector hitCollector, Collector drillDownCollector, Collector[] drillSidewaysCollectors,
Map<String,Integer> dims) {
this.hitCollector = hitCollector;
this.drillDownCollector = drillDownCollector;
this.drillSidewaysCollectors = drillSidewaysCollectors;
subScorers = new Scorer[dims.size()];
if (dims.size() == 1) {
// When we have only one dim, we insert the
// MatchAllDocsQuery, bringing the clause count to
// 2:
exactCount = 2;
} else {
exactCount = dims.size();
}
}
@Override
public void collect(int doc) throws IOException {
//System.out.println("collect doc=" + doc + " main.freq=" + mainScorer.freq() + " main.doc=" + mainScorer.docID() + " exactCount=" + exactCount);
if (mainScorer == null) {
// This segment did not have any docs with any
// drill-down field & value:
return;
}
if (mainScorer.freq() == exactCount) {
// All sub-clauses from the drill-down filters
// matched, so this is a "real" hit, so we first
// collect in both the hitCollector and the
// drillDown collector:
//System.out.println(" hit " + drillDownCollector);
hitCollector.collect(doc);
if (drillDownCollector != null) {
drillDownCollector.collect(doc);
}
// Also collect across all drill-sideways counts so
// we "merge in" drill-down counts for this
// dimension.
for(int i=0;i<subScorers.length;i++) {
// This cannot be null, because it was a hit,
// meaning all drill-down dims matched, so all
// dims must have non-null scorers:
assert subScorers[i] != null;
int subDoc = subScorers[i].docID();
assert subDoc == doc;
drillSidewaysCollectors[i].collect(doc);
}
} else {
boolean found = false;
for(int i=0;i<subScorers.length;i++) {
if (subScorers[i] == null) {
// This segment did not have any docs with this
// drill-down field & value:
drillSidewaysCollectors[i].collect(doc);
assert allMatchesFrom(i+1, doc);
found = true;
break;
}
int subDoc = subScorers[i].docID();
//System.out.println(" i=" + i + " sub: " + subDoc);
if (subDoc != doc) {
//System.out.println(" +ds[" + i + "]");
assert subDoc > doc: "subDoc=" + subDoc + " doc=" + doc;
drillSidewaysCollectors[i].collect(doc);
assert allMatchesFrom(i+1, doc);
found = true;
break;
}
}
assert found;
}
}
// Only used by assert:
private boolean allMatchesFrom(int startFrom, int doc) {
for(int i=startFrom;i<subScorers.length;i++) {
assert subScorers[i].docID() == doc;
}
return true;
}
@Override
public boolean acceptsDocsOutOfOrder() {
// We actually could accept docs out of order, but, we
// need to force BooleanScorer2 so that the
// sub-scorers are "on" each docID we are collecting:
return false;
}
@Override
public void setNextReader(AtomicReaderContext leaf) throws IOException {
//System.out.println("DS.setNextReader reader=" + leaf.reader());
hitCollector.setNextReader(leaf);
if (drillDownCollector != null) {
drillDownCollector.setNextReader(leaf);
}
for(Collector dsc : drillSidewaysCollectors) {
dsc.setNextReader(leaf);
}
}
void setWeight(Weight weight, int index) {
assert !weightToIndex.containsKey(weight);
weightToIndex.put(weight, index);
}
private void findScorers(Scorer scorer) {
Integer index = weightToIndex.get(scorer.getWeight());
if (index != null) {
if (index.intValue() == -1) {
mainScorer = scorer;
} else {
subScorers[index] = scorer;
}
}
for(ChildScorer child : scorer.getChildren()) {
findScorers(child.child);
}
}
@Override
public void setScorer(Scorer scorer) throws IOException {
mainScorer = null;
Arrays.fill(subScorers, null);
findScorers(scorer);
hitCollector.setScorer(scorer);
if (drillDownCollector != null) {
drillDownCollector.setScorer(scorer);
}
for(Collector dsc : drillSidewaysCollectors) {
dsc.setScorer(scorer);
}
}
}