blob: 74b03325cc6087020907d1e83a7954056d55a2fc [file] [log] [blame]
/*
* 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.lucene.monitor;
import java.io.IOException;
import java.util.*;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Matches;
import org.apache.lucene.search.MatchesIterator;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Weight;
/**
* QueryMatch object that contains the hit positions of a matching Query
* <p>
* If the Query does not support interval iteration (eg, if it gets re-written to
* a Filter), then no hits will be reported, but an IntervalsQueryMatch will still
* be returned from an IntervalsMatcher to indicate a match.
*/
public class HighlightsMatch extends QueryMatch {
public static final MatcherFactory<HighlightsMatch> MATCHER = searcher -> new CandidateMatcher<HighlightsMatch>(searcher) {
@Override
protected void matchQuery(String queryId, Query matchQuery, Map<String, String> metadata) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(matchQuery), ScoreMode.COMPLETE_NO_SCORES, 1);
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
for (int i = 0; i < ctx.reader().maxDoc(); i++) {
Matches matches = w.matches(ctx, i);
if (matches != null) {
addMatch(buildMatch(matches, queryId), i);
}
}
}
}
@Override
public HighlightsMatch resolve(HighlightsMatch match1, HighlightsMatch match2) {
return HighlightsMatch.merge(match1.getQueryId(), match1, match2);
}
private HighlightsMatch buildMatch(Matches matches, String queryId) throws IOException {
HighlightsMatch m = new HighlightsMatch(queryId);
for (String field : matches) {
MatchesIterator mi = matches.getMatches(field);
while (mi.next()) {
MatchesIterator sub = mi.getSubMatches();
if (sub != null) {
while (sub.next()) {
m.addHit(field, sub.startPosition(), sub.endPosition(), sub.startOffset(), sub.endOffset());
}
}
else {
m.addHit(field, mi.startPosition(), mi.endPosition(), mi.startOffset(), mi.endOffset());
}
}
}
return m;
}
};
private final Map<String, Set<Hit>> hits;
HighlightsMatch(String queryId) {
super(queryId);
this.hits = new TreeMap<>();
}
/**
* @return a map of hits per field
*/
public Map<String, Set<Hit>> getHits() {
return Collections.unmodifiableMap(this.hits);
}
/**
* @return the fields in which matches have been found
*/
public Set<String> getFields() {
return Collections.unmodifiableSet(hits.keySet());
}
/**
* Get the hits for a specific field
*
* @param field the field
* @return the Hits found in this field
*/
public Collection<Hit> getHits(String field) {
Collection<Hit> found = hits.get(field);
if (found != null)
return Collections.unmodifiableCollection(found);
return Collections.emptyList();
}
/**
* @return the total number of hits for the query
*/
public int getHitCount() {
int c = 0;
for (Set<Hit> fieldhits : hits.values()) {
c += fieldhits.size();
}
return c;
}
static HighlightsMatch merge(String queryId, HighlightsMatch... matches) {
HighlightsMatch newMatch = new HighlightsMatch(queryId);
for (HighlightsMatch match : matches) {
for (String field : match.getFields()) {
Set<Hit> hitSet = newMatch.hits.computeIfAbsent(field, f -> new TreeSet<>());
hitSet.addAll(match.getHits(field));
}
}
return newMatch;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof HighlightsMatch)) return false;
if (!super.equals(o)) return false;
HighlightsMatch that = (HighlightsMatch) o;
if (hits != null ? !hits.equals(that.hits) : that.hits != null) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (hits != null ? hits.hashCode() : 0);
return result;
}
@Override
public String toString() {
return super.toString() + "{hits=" + hits + "}";
}
void addHit(String field, int startPos, int endPos, int startOffset, int endOffset) {
Set<Hit> hitSet = hits.computeIfAbsent(field, f -> new TreeSet<>());
hitSet.add(new Hit(startPos, startOffset, endPos, endOffset));
}
/**
* Represents an individual hit
*/
public static class Hit implements Comparable<Hit> {
/**
* The start position
*/
public final int startPosition;
/**
* The start offset
*/
public final int startOffset;
/**
* The end positions
*/
public final int endPosition;
/**
* The end offset
*/
public final int endOffset;
public Hit(int startPosition, int startOffset, int endPosition, int endOffset) {
this.startPosition = startPosition;
this.startOffset = startOffset;
this.endPosition = endPosition;
this.endOffset = endOffset;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Hit))
return false;
Hit other = (Hit) obj;
return this.startOffset == other.startOffset &&
this.endOffset == other.endOffset &&
this.startPosition == other.startPosition &&
this.endPosition == other.endPosition;
}
@Override
public int hashCode() {
int result = startPosition;
result = 31 * result + startOffset;
result = 31 * result + endPosition;
result = 31 * result + endOffset;
return result;
}
@Override
public String toString() {
return String.format(Locale.ROOT, "%d(%d)->%d(%d)", startPosition, startOffset, endPosition, endOffset);
}
@Override
public int compareTo(Hit other) {
if (this.startPosition != other.startPosition)
return Integer.compare(this.startPosition, other.startPosition);
return Integer.compare(this.endPosition, other.endPosition);
}
}
}