/* * 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. */ package org.elasticsearch.search.suggest.term; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.text.Text; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.search.suggest.SortBy; import org.elasticsearch.search.suggest.Suggest.Suggestion; import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option; import java.io.IOException; import java.util.Comparator; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; /** * The suggestion responses corresponding with the suggestions in the request. */ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> { public static final String NAME = "term"; public static final Comparator<Suggestion.Entry.Option> SCORE = new Score(); public static final Comparator<Suggestion.Entry.Option> FREQUENCY = new Frequency(); public static final int TYPE = 1; private SortBy sort; public TermSuggestion() { } public TermSuggestion(String name, int size, SortBy sort) { super(name, size); this.sort = sort; } // Same behaviour as comparators in suggest module, but for SuggestedWord // Highest score first, then highest freq first, then lowest term first public static class Score implements Comparator<Suggestion.Entry.Option> { @Override public int compare(Suggestion.Entry.Option first, Suggestion.Entry.Option second) { // first criteria: the distance int cmp = Float.compare(second.getScore(), first.getScore()); if (cmp != 0) { return cmp; } return FREQUENCY.compare(first, second); } } // Same behaviour as comparators in suggest module, but for SuggestedWord // Highest freq first, then highest score first, then lowest term first public static class Frequency implements Comparator<Suggestion.Entry.Option> { @Override public int compare(Suggestion.Entry.Option first, Suggestion.Entry.Option second) { // first criteria: the popularity int cmp = ((TermSuggestion.Entry.Option) second).getFreq() - ((TermSuggestion.Entry.Option) first).getFreq(); if (cmp != 0) { return cmp; } // second criteria (if first criteria is equal): the distance cmp = Float.compare(second.getScore(), first.getScore()); if (cmp != 0) { return cmp; } // third criteria: term text return first.getText().compareTo(second.getText()); } } @Override public int getWriteableType() { return TYPE; } @Override protected String getType() { return NAME; } @Override protected Comparator<Option> sortComparator() { switch (sort) { case SCORE: return SCORE; case FREQUENCY: return FREQUENCY; default: throw new ElasticsearchException("Could not resolve comparator for sort key: [" + sort + "]"); } } @Override protected void innerReadFrom(StreamInput in) throws IOException { super.innerReadFrom(in); sort = SortBy.readFromStream(in); } @Override public void innerWriteTo(StreamOutput out) throws IOException { super.innerWriteTo(out); sort.writeTo(out); } public static TermSuggestion fromXContent(XContentParser parser, String name) throws IOException { // the "size" parameter and the SortBy for TermSuggestion cannot be parsed from the response, use default values TermSuggestion suggestion = new TermSuggestion(name, -1, SortBy.SCORE); parseEntries(parser, suggestion, TermSuggestion.Entry::fromXContent); return suggestion; } @Override protected Entry newEntry() { return new Entry(); } /** * Represents a part from the suggest text with suggested options. */ public static class Entry extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<TermSuggestion.Entry.Option> { public Entry(Text text, int offset, int length) { super(text, offset, length); } Entry() { } @Override protected Option newOption() { return new Option(); } private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("TermSuggestionEntryParser", true, Entry::new); static { declareCommonFields(PARSER); PARSER.declareObjectArray(Entry::addOptions, (p,c) -> Option.fromXContent(p), new ParseField(OPTIONS)); } public static Entry fromXContent(XContentParser parser) { return PARSER.apply(parser, null); } /** * Contains the suggested text with its document frequency and score. */ public static class Option extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option { public static final ParseField FREQ = new ParseField("freq"); private int freq; public Option(Text text, int freq, float score) { super(text, score); this.freq = freq; } @Override protected void mergeInto(Suggestion.Entry.Option otherOption) { super.mergeInto(otherOption); freq += ((Option) otherOption).freq; } protected Option() { super(); } public void setFreq(int freq) { this.freq = freq; } /** * @return How often this suggested text appears in the index. */ public int getFreq() { return freq; } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); freq = in.readVInt(); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeVInt(freq); } @Override protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { builder = super.innerToXContent(builder, params); builder.field(FREQ.getPreferredName(), freq); return builder; } private static final ConstructingObjectParser<Option, Void> PARSER = new ConstructingObjectParser<>( "TermSuggestionOptionParser", true, args -> { Text text = new Text((String) args[0]); int freq = (Integer) args[1]; float score = (Float) args[2]; return new Option(text, freq, score); }); static { PARSER.declareString(constructorArg(), Suggestion.Entry.Option.TEXT); PARSER.declareInt(constructorArg(), FREQ); PARSER.declareFloat(constructorArg(), Suggestion.Entry.Option.SCORE); } public static Option fromXContent(XContentParser parser) { return PARSER.apply(parser, null); } } } }