blob: 5dc966c5bc61abd2bd12bd0075f7f2cf3ed922fc [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.search;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.util.BytesRefIterator;
import org.apache.lucene.util.IOSupplier;
/**
* Contains static functions that aid the implementation of {@link Matches} and
* {@link MatchesIterator} interfaces.
*/
public final class MatchesUtils {
private MatchesUtils() {} // static functions only
/**
* Indicates a match with no term positions, for example on a Point or DocValues field,
* or a field indexed as docs and freqs only
*/
public static final Matches MATCH_WITH_NO_TERMS = new Matches() {
@Override
public MatchesIterator getMatches(String field) {
return null;
}
@Override
public Collection<Matches> getSubMatches() {
return Collections.emptyList();
}
@Override
public Iterator<String> iterator() {
return Collections.emptyIterator();
}
};
/**
* Amalgamate a collection of {@link Matches} into a single object
*/
public static Matches fromSubMatches(List<Matches> subMatches) {
if (subMatches == null || subMatches.size() == 0) {
return null;
}
List<Matches> sm = subMatches.stream().filter(m -> m != MATCH_WITH_NO_TERMS).collect(Collectors.toList());
if (sm.size() == 0) {
return MATCH_WITH_NO_TERMS;
}
if (sm.size() == 1) {
return sm.get(0);
}
return new Matches() {
@Override
public MatchesIterator getMatches(String field) throws IOException {
List<MatchesIterator> subIterators = new ArrayList<>(sm.size());
for (Matches m : sm) {
MatchesIterator it = m.getMatches(field);
if (it != null) {
subIterators.add(it);
}
}
return DisjunctionMatchesIterator.fromSubIterators(subIterators);
}
@Override
public Iterator<String> iterator() {
// for each sub-match, iterate its fields (it's an Iterable of the fields), and return the distinct set
return sm.stream().flatMap(m -> StreamSupport.stream(m.spliterator(), false)).distinct().iterator();
}
@Override
public Collection<Matches> getSubMatches() {
return subMatches;
}
};
}
/**
* Create a Matches for a single field
*/
public static Matches forField(String field, IOSupplier<MatchesIterator> mis) throws IOException {
// The indirection here, using a Supplier object rather than a MatchesIterator
// directly, is to allow for multiple calls to Matches.getMatches() to return
// new iterators. We still need to call MatchesIteratorSupplier.get() eagerly
// to work out if we have a hit or not.
MatchesIterator mi = mis.get();
if (mi == null) {
return null;
}
return new Matches() {
boolean cached = true;
@Override
public MatchesIterator getMatches(String f) throws IOException {
if (Objects.equals(field, f) == false) {
return null;
}
if (cached == false) {
return mis.get();
}
cached = false;
return mi;
}
@Override
public Iterator<String> iterator() {
return Collections.singleton(field).iterator();
}
@Override
public Collection<Matches> getSubMatches() {
return Collections.emptyList();
}
};
}
/**
* Create a MatchesIterator that iterates in order over all matches in a set of subiterators
*/
public static MatchesIterator disjunction(List<MatchesIterator> subMatches) throws IOException {
return DisjunctionMatchesIterator.fromSubIterators(subMatches);
}
/**
* Create a MatchesIterator that is a disjunction over a list of terms extracted from a {@link BytesRefIterator}.
*
* Only terms that have at least one match in the given document will be included
*/
public static MatchesIterator disjunction(LeafReaderContext context, int doc, Query query, String field, BytesRefIterator terms) throws IOException {
return DisjunctionMatchesIterator.fromTermsEnum(context, doc, query, field, terms);
}
}