Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.search.suggest.completion;
import org.apache.lucene.analysis.CharArraySet;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.util.PriorityQueue;
import org.opensearch.LegacyESVersion;
import org.opensearch.common.ParseField;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.lucene.Lucene;
import org.opensearch.common.text.Text;
import org.opensearch.common.xcontent.ObjectParser;
import org.opensearch.common.xcontent.XContentBuilder;
import org.opensearch.common.xcontent.XContentParser;
import org.opensearch.search.SearchHit;
import org.opensearch.search.suggest.Suggest;
import org.opensearch.search.suggest.Suggest.Suggestion;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.opensearch.search.SearchHit.unknownMetaFieldConsumer;
import static org.opensearch.search.suggest.Suggest.COMPARATOR;
/**
* Suggestion response for {@link CompletionSuggester} results
*
* Response format for each entry:
* {
* "text" : STRING
* "score" : FLOAT
* "contexts" : CONTEXTS
* }
*
* CONTEXTS : {
* "CONTEXT_NAME" : ARRAY,
* ..
* }
*
*/
public final class CompletionSuggestion extends Suggest.Suggestion {
@Deprecated
public static final int TYPE = 4;
private boolean skipDuplicates;
/**
* Creates a completion suggestion given its name, size and whether it should skip duplicates
* @param name The name for the suggestions
* @param size The number of suggestions to return
* @param skipDuplicates Whether duplicate suggestions should be filtered out
*/
public CompletionSuggestion(String name, int size, boolean skipDuplicates) {
super(name, size);
this.skipDuplicates = skipDuplicates;
}
public CompletionSuggestion(StreamInput in) throws IOException {
super(in);
if (in.getVersion().onOrAfter(LegacyESVersion.V_6_1_0)) {
skipDuplicates = in.readBoolean();
}
}
@Override
public String getWriteableName() {
return CompletionSuggestionBuilder.SUGGESTION_NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
if (out.getVersion().onOrAfter(LegacyESVersion.V_6_1_0)) {
out.writeBoolean(skipDuplicates);
}
}
/**
* @return the result options for the suggestion
*/
public List getOptions() {
if (entries.isEmpty() == false) {
assert entries.size() == 1 : "CompletionSuggestion must have only one entry";
return entries.get(0).getOptions();
} else {
return Collections.emptyList();
}
}
/**
* @return whether there is any hits for the suggestion
*/
public boolean hasScoreDocs() {
return getOptions().size() > 0;
}
@Override
public boolean equals(Object other) {
return super.equals(other) && Objects.equals(skipDuplicates, ((CompletionSuggestion) other).skipDuplicates);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), skipDuplicates);
}
public static CompletionSuggestion fromXContent(XContentParser parser, String name) throws IOException {
CompletionSuggestion suggestion = new CompletionSuggestion(name, -1, false);
parseEntries(parser, suggestion, CompletionSuggestion.Entry::fromXContent);
return suggestion;
}
private static final class OptionPriorityQueue extends PriorityQueue {
OptionPriorityQueue(int maxSize) {
super(maxSize);
}
@Override
protected boolean lessThan(ShardOptions a, ShardOptions b) {
int compare = COMPARATOR.compare(a.current, b.current);
if (compare != 0) {
return compare < 0;
}
ScoreDoc aDoc = a.current.getDoc();
ScoreDoc bDoc = b.current.getDoc();
if (aDoc.shardIndex == bDoc.shardIndex) {
return aDoc.doc < bDoc.doc;
}
return aDoc.shardIndex < bDoc.shardIndex;
}
}
private static class ShardOptions {
final Iterator optionsIterator;
Entry.Option current;
private ShardOptions(Iterator optionsIterator) {
assert optionsIterator.hasNext();
this.optionsIterator = optionsIterator;
this.current = optionsIterator.next();
assert this.current.getDoc().shardIndex != -1 : "shardIndex is not set";
}
boolean advanceToNextOption() {
if (optionsIterator.hasNext()) {
current = optionsIterator.next();
return true;
} else {
return false;
}
}
}
@Override
public CompletionSuggestion reduce(List> toReduce) {
if (toReduce.isEmpty()) {
return null;
} else {
final CompletionSuggestion leader = (CompletionSuggestion) toReduce.get(0);
final Entry leaderEntry = leader.getEntries().get(0);
final String name = leader.getName();
int size = leader.getSize();
if (toReduce.size() == 1) {
return leader;
} else {
// combine suggestion entries from participating shards on the coordinating node
// the global top size entries are collected from the shard results
// using a priority queue
OptionPriorityQueue pq = new OptionPriorityQueue(toReduce.size());
for (Suggest.Suggestion suggestion : toReduce) {
assert suggestion.getName().equals(name) : "name should be identical across all suggestions";
Iterator it = ((CompletionSuggestion) suggestion).getOptions().iterator();
if (it.hasNext()) {
pq.add(new ShardOptions(it));
}
}
// Dedup duplicate suggestions (based on the surface form) if skip duplicates is activated
final CharArraySet seenSurfaceForms = leader.skipDuplicates ? new CharArraySet(leader.getSize(), false) : null;
final Entry entry = new Entry(leaderEntry.getText(), leaderEntry.getOffset(), leaderEntry.getLength());
final List options = entry.getOptions();
while (pq.size() > 0) {
ShardOptions top = pq.top();
Entry.Option current = top.current;
if (top.advanceToNextOption()) {
pq.updateTop();
} else {
// options exhausted for this shard
pq.pop();
}
if (leader.skipDuplicates == false || seenSurfaceForms.add(current.getText().toString())) {
options.add(current);
if (options.size() >= size) {
break;
}
}
}
final CompletionSuggestion suggestion = new CompletionSuggestion(leader.getName(), leader.getSize(), leader.skipDuplicates);
suggestion.addTerm(entry);
return suggestion;
}
}
}
public void setShardIndex(int shardIndex) {
if (entries.isEmpty() == false) {
for (Entry.Option option : getOptions()) {
option.setShardIndex(shardIndex);
}
}
}
@Override
public int getWriteableType() {
return TYPE;
}
@Override
protected Entry newEntry(StreamInput in) throws IOException {
return new Entry(in);
}
public static final class Entry extends Suggest.Suggestion.Entry {
public Entry(Text text, int offset, int length) {
super(text, offset, length);
}
private Entry() {}
public Entry(StreamInput in) throws IOException {
super(in);
}
@Override
protected Option newOption(StreamInput in) throws IOException {
return new Option(in);
}
private static final ObjectParser PARSER = new ObjectParser<>("CompletionSuggestionEntryParser", true, Entry::new);
static {
declareCommonFields(PARSER);
/*
* The use of a lambda expression instead of the method reference Entry::addOptions is a workaround for a JDK 14 compiler bug.
* The bug is: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8242214
*/
PARSER.declareObjectArray((e, o) -> e.addOptions(o), (p, c) -> Option.fromXContent(p), new ParseField(OPTIONS));
}
public static Entry fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
public static class Option extends Suggest.Suggestion.Entry.Option {
private final Map> contexts;
private final ScoreDoc doc;
private SearchHit hit;
public static final ParseField CONTEXTS = new ParseField("contexts");
public Option(int docID, Text text, float score, Map> contexts) {
super(text, score);
this.doc = new ScoreDoc(docID, score);
this.contexts = Objects.requireNonNull(contexts, "context map cannot be null");
}
public Option(StreamInput in) throws IOException {
super(in);
this.doc = Lucene.readScoreDoc(in);
if (in.readBoolean()) {
this.hit = new SearchHit(in);
}
int contextSize = in.readInt();
this.contexts = new LinkedHashMap<>(contextSize);
for (int i = 0; i < contextSize; i++) {
String contextName = in.readString();
int nContexts = in.readVInt();
Set contexts = new HashSet<>(nContexts);
for (int j = 0; j < nContexts; j++) {
contexts.add(in.readString());
}
this.contexts.put(contextName, contexts);
}
}
@Override
protected void mergeInto(Suggest.Suggestion.Entry.Option otherOption) {
// Completion suggestions are reduced by
// org.opensearch.search.suggest.completion.CompletionSuggestion.reduce()
throw new UnsupportedOperationException();
}
public Map> getContexts() {
return contexts;
}
public ScoreDoc getDoc() {
return doc;
}
public SearchHit getHit() {
return hit;
}
public void setShardIndex(int shardIndex) {
this.doc.shardIndex = shardIndex;
}
public void setHit(SearchHit hit) {
this.hit = hit;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(TEXT.getPreferredName(), getText());
if (hit != null) {
hit.toInnerXContent(builder, params);
} else {
builder.field(SCORE.getPreferredName(), getScore());
}
if (contexts.size() > 0) {
builder.startObject(CONTEXTS.getPreferredName());
for (Map.Entry> entry : contexts.entrySet()) {
builder.startArray(entry.getKey());
for (CharSequence context : entry.getValue()) {
builder.value(context.toString());
}
builder.endArray();
}
builder.endObject();
}
return builder;
}
private static final ObjectParser