From c00c0fa020a2ab3391a38312aba898db517f9f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Tue, 26 Jan 2016 09:59:11 +0100 Subject: [PATCH] Initial refactoring for phrase suggester Adding initial serialization methods (readFrom, writeTo) to the PhraseSuggestionBuilder, also adding the base test framework for serialiazation testing, equals and hashCode. Moving SuggestionBuilder out of the global SuggestBuilder for better readability. --- .../action/suggest/SuggestRequest.java | 5 +- .../action/suggest/SuggestRequestBuilder.java | 4 +- .../common/io/stream/StreamInput.java | 16 + .../common/io/stream/StreamOutput.java | 17 + .../search/suggest/SuggestBuilder.java | 131 +------- .../search/suggest/SuggestionBuilder.java | 299 ++++++++++++++++++ .../CompletionSuggestionBuilder.java | 47 ++- .../phrase/PhraseSuggestionBuilder.java | 176 ++++++++++- .../suggest/term/TermSuggestionBuilder.java | 39 ++- .../AbstractSuggestionBuilderTestCase.java | 186 +++++++++++ .../suggest/CompletionSuggestSearchIT.java | 3 +- .../ContextCompletionSuggestSearchIT.java | 2 +- .../suggest/CustomSuggesterSearchIT.java | 61 +++- .../phrase/PhraseSuggestionBuilderTests.java | 114 +++++++ .../messy/tests/SuggestSearchTests.java | 74 ++--- 15 files changed, 982 insertions(+), 192 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java create mode 100644 core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java create mode 100644 core/src/test/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilderTests.java diff --git a/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequest.java b/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequest.java index 0d1c4932d48..5dcb39fa14b 100644 --- a/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequest.java +++ b/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequest.java @@ -30,6 +30,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import java.io.IOException; import java.util.Arrays; @@ -99,10 +100,10 @@ public final class SuggestRequest extends BroadcastRequest { } /** - * set a new source using a {@link org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder} + * set a new source using a {@link org.elasticsearch.search.suggest.SuggestionBuilder} * for completion suggestion lookup */ - public SuggestRequest suggest(SuggestBuilder.SuggestionBuilder suggestionBuilder) { + public SuggestRequest suggest(SuggestionBuilder suggestionBuilder) { return suggest(suggestionBuilder.buildAsBytes(Requests.CONTENT_TYPE)); } diff --git a/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequestBuilder.java b/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequestBuilder.java index 06a2b00c648..d9f957aa2b1 100644 --- a/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequestBuilder.java +++ b/core/src/main/java/org/elasticsearch/action/suggest/SuggestRequestBuilder.java @@ -27,7 +27,7 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.search.suggest.SuggestBuilder; -import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import java.io.IOException; @@ -45,7 +45,7 @@ public class SuggestRequestBuilder extends BroadcastOperationRequestBuilder SuggestRequestBuilder addSuggestion(SuggestionBuilder suggestion) { + public SuggestRequestBuilder addSuggestion(SuggestionBuilder suggestion) { suggest.addSuggestion(suggestion); return this; } diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java index 02e937dbd83..3f44b61f212 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java @@ -38,6 +38,7 @@ import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder; import org.elasticsearch.search.rescore.RescoreBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -281,6 +282,14 @@ public abstract class StreamInput extends InputStream { return null; } + @Nullable + public Float readOptionalFloat() throws IOException { + if (readBoolean()) { + return readFloat(); + } + return null; + } + @Nullable public Integer readOptionalVInt() throws IOException { if (readBoolean()) { @@ -683,6 +692,13 @@ public abstract class StreamInput extends InputStream { return readNamedWriteable(RescoreBuilder.class); } + /** + * Reads a {@link SuggestionBuilder} from the current stream + */ + public SuggestionBuilder readSuggestion() throws IOException { + return readNamedWriteable(SuggestionBuilder.class); + } + /** * Reads a {@link org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder} from the current stream */ diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java index 0863717a5ab..5e0af597b09 100644 --- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java +++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java @@ -37,6 +37,7 @@ import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder; import org.elasticsearch.search.rescore.RescoreBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import org.joda.time.ReadableInstant; import java.io.EOFException; @@ -230,6 +231,15 @@ public abstract class StreamOutput extends OutputStream { } } + public void writeOptionalFloat(@Nullable Float floatValue) throws IOException { + if (floatValue == null) { + writeBoolean(false); + } else { + writeBoolean(true); + writeFloat(floatValue); + } + } + public void writeOptionalText(@Nullable Text text) throws IOException { if (text == null) { writeInt(-1); @@ -684,4 +694,11 @@ public abstract class StreamOutput extends OutputStream { public void writeRescorer(RescoreBuilder rescorer) throws IOException { writeNamedWriteable(rescorer); } + + /** + * Writes a {@link SuggestionBuilder} to the current stream + */ + public void writeSuggestion(SuggestionBuilder suggestion) throws IOException { + writeNamedWriteable(suggestion); + } } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java index 5621e03e7de..92661b21f18 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java @@ -42,14 +42,14 @@ public class SuggestBuilder extends ToXContentToBytes { public SuggestBuilder() { this.name = null; } - + public SuggestBuilder(String name) { this.name = name; } - + /** * Sets the text to provide suggestions for. The suggest text is a required option that needs - * to be set either via this setter or via the {@link org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder#setText(String)} method. + * to be set either via this setter or via the {@link org.elasticsearch.search.suggest.SuggestionBuilder#text(String)} method. *

* The suggest text gets analyzed by the suggest analyzer or the suggest field search analyzer. * For each analyzed token, suggested terms are suggested if possible. @@ -67,7 +67,7 @@ public class SuggestBuilder extends ToXContentToBytes { suggestions.add(suggestion); return this; } - + /** * Returns all suggestions with the defined names. */ @@ -82,7 +82,7 @@ public class SuggestBuilder extends ToXContentToBytes { } else { builder.startObject(name); } - + if (globalText != null) { builder.field("text", globalText); } @@ -92,125 +92,4 @@ public class SuggestBuilder extends ToXContentToBytes { builder.endObject(); return builder; } - - public static abstract class SuggestionBuilder extends ToXContentToBytes { - - private String name; - private String suggester; - private String text; - private String prefix; - private String regex; - private String field; - private String analyzer; - private Integer size; - private Integer shardSize; - - public SuggestionBuilder(String name, String suggester) { - this.name = name; - this.suggester = suggester; - } - - /** - * Same as in {@link SuggestBuilder#setText(String)}, but in the suggestion scope. - */ - @SuppressWarnings("unchecked") - public T text(String text) { - this.text = text; - return (T) this; - } - - protected void setPrefix(String prefix) { - this.prefix = prefix; - } - - protected void setRegex(String regex) { - this.regex = regex; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(name); - if (text != null) { - builder.field("text", text); - } - if (prefix != null) { - builder.field("prefix", prefix); - } - if (regex != null) { - builder.field("regex", regex); - } - builder.startObject(suggester); - if (analyzer != null) { - builder.field("analyzer", analyzer); - } - if (field != null) { - builder.field("field", field); - } - if (size != null) { - builder.field("size", size); - } - if (shardSize != null) { - builder.field("shard_size", shardSize); - } - - builder = innerToXContent(builder, params); - builder.endObject(); - builder.endObject(); - return builder; - } - - protected abstract XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException; - - /** - * Sets from what field to fetch the candidate suggestions from. This is an - * required option and needs to be set via this setter or - * {@link org.elasticsearch.search.suggest.term.TermSuggestionBuilder#field(String)} - * method - */ - @SuppressWarnings("unchecked") - public T field(String field) { - this.field = field; - return (T)this; - } - - /** - * Sets the analyzer to analyse to suggest text with. Defaults to the search - * analyzer of the suggest field. - */ - @SuppressWarnings("unchecked") - public T analyzer(String analyzer) { - this.analyzer = analyzer; - return (T)this; - } - - /** - * Sets the maximum suggestions to be returned per suggest text term. - */ - @SuppressWarnings("unchecked") - public T size(int size) { - if (size <= 0) { - throw new IllegalArgumentException("Size must be positive"); - } - this.size = size; - return (T)this; - } - - /** - * Sets the maximum number of suggested term to be retrieved from each - * individual shard. During the reduce phase the only the top N suggestions - * are returned based on the size option. Defaults to the - * size option. - *

- * Setting this to a value higher than the `size` can be useful in order to - * get a more accurate document frequency for suggested terms. Due to the - * fact that terms are partitioned amongst shards, the shard level document - * frequencies of suggestions may not be precise. Increasing this will make - * these document frequencies more precise. - */ - @SuppressWarnings("unchecked") - public T shardSize(Integer shardSize) { - this.shardSize = shardSize; - return (T)this; - } - } } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java new file mode 100644 index 00000000000..7705f2201d1 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java @@ -0,0 +1,299 @@ +/* + * 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; + +import org.elasticsearch.action.support.ToXContentToBytes; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.NamedWriteable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * Base class for the different suggestion implementations. + */ +public abstract class SuggestionBuilder> extends ToXContentToBytes implements NamedWriteable { + + protected final String name; + // TODO this seems mandatory and should be constructor arg + protected String fieldname; + protected String text; + protected String prefix; + protected String regex; + protected String analyzer; + protected Integer size; + protected Integer shardSize; + + protected static final ParseField TEXT_FIELD = new ParseField("text"); + protected static final ParseField PREFIX_FIELD = new ParseField("prefix"); + protected static final ParseField REGEX_FIELD = new ParseField("regex"); + protected static final ParseField FIELDNAME_FIELD = new ParseField("field"); + protected static final ParseField ANALYZER_FIELD = new ParseField("analyzer"); + protected static final ParseField SIZE_FIELD = new ParseField("size"); + protected static final ParseField SHARDSIZE_FIELD = new ParseField("shard_size"); + + public SuggestionBuilder(String name) { + this.name = name; + } + + /** + * get the name for this suggestion + */ + public String name() { + return this.name; + } + + /** + * Same as in {@link SuggestBuilder#setText(String)}, but in the suggestion scope. + */ + @SuppressWarnings("unchecked") + public T text(String text) { + this.text = text; + return (T) this; + } + + /** + * get the text for this suggestion + */ + public String text() { + return this.text; + } + + @SuppressWarnings("unchecked") + protected T prefix(String prefix) { + this.prefix = prefix; + return (T) this; + } + + /** + * get the prefix for this suggestion + */ + public String prefix() { + return this.prefix; + } + + @SuppressWarnings("unchecked") + protected T regex(String regex) { + this.regex = regex; + return (T) this; + } + + /** + * get the regex for this suggestion + */ + public String regex() { + return this.regex; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(name); + if (text != null) { + builder.field(TEXT_FIELD.getPreferredName(), text); + } + if (prefix != null) { + builder.field(PREFIX_FIELD.getPreferredName(), prefix); + } + if (regex != null) { + builder.field(REGEX_FIELD.getPreferredName(), regex); + } + builder.startObject(getSuggesterName()); + if (analyzer != null) { + builder.field(ANALYZER_FIELD.getPreferredName(), analyzer); + } + if (fieldname != null) { + builder.field(FIELDNAME_FIELD.getPreferredName(), fieldname); + } + if (size != null) { + builder.field(SIZE_FIELD.getPreferredName(), size); + } + if (shardSize != null) { + builder.field(SHARDSIZE_FIELD.getPreferredName(), shardSize); + } + + builder = innerToXContent(builder, params); + builder.endObject(); + builder.endObject(); + return builder; + } + + private String getSuggesterName() { + //default impl returns the same as writeable name, but we keep the distinction between the two just to make sure + return getWriteableName(); + } + + protected abstract XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException; + + /** + * Sets from what field to fetch the candidate suggestions from. This is an + * required option and needs to be set via this setter or + * {@link org.elasticsearch.search.suggest.term.TermSuggestionBuilder#field(String)} + * method + */ + @SuppressWarnings("unchecked") + public T field(String field) { + this.fieldname = field; + return (T)this; + } + + /** + * get the {@link #field()} parameter + */ + public String field() { + return this.fieldname; + } + + /** + * Sets the analyzer to analyse to suggest text with. Defaults to the search + * analyzer of the suggest field. + */ + @SuppressWarnings("unchecked") + public T analyzer(String analyzer) { + this.analyzer = analyzer; + return (T)this; + } + + /** + * get the {@link #analyzer()} parameter + */ + public String analyzer() { + return this.analyzer; + } + + /** + * Sets the maximum suggestions to be returned per suggest text term. + */ + @SuppressWarnings("unchecked") + public T size(int size) { + if (size <= 0) { + throw new IllegalArgumentException("Size must be positive"); + } + this.size = size; + return (T)this; + } + + /** + * get the {@link #size()} parameter + */ + public Integer size() { + return this.size; + } + + /** + * Sets the maximum number of suggested term to be retrieved from each + * individual shard. During the reduce phase the only the top N suggestions + * are returned based on the size option. Defaults to the + * size option. + *

+ * Setting this to a value higher than the `size` can be useful in order to + * get a more accurate document frequency for suggested terms. Due to the + * fact that terms are partitioned amongst shards, the shard level document + * frequencies of suggestions may not be precise. Increasing this will make + * these document frequencies more precise. + */ + @SuppressWarnings("unchecked") + public T shardSize(Integer shardSize) { + this.shardSize = shardSize; + return (T)this; + } + + /** + * get the {@link #shardSize()} parameter + */ + public Integer shardSize() { + return this.shardSize; + } + + + @Override + public final T readFrom(StreamInput in) throws IOException { + String name = in.readString(); + T suggestionBuilder = doReadFrom(in, name); + suggestionBuilder.fieldname = in.readOptionalString(); + suggestionBuilder.text = in.readOptionalString(); + suggestionBuilder.prefix = in.readOptionalString(); + suggestionBuilder.regex = in.readOptionalString(); + suggestionBuilder.analyzer = in.readOptionalString(); + suggestionBuilder.size = in.readOptionalVInt(); + suggestionBuilder.shardSize = in.readOptionalVInt(); + return suggestionBuilder; + } + + /** + * Subclass should return a new instance, reading itself from the input string + * @param in the input string to read from + * @param name the name of the suggestion (read from stream by {@link SuggestionBuilder} + */ + protected abstract T doReadFrom(StreamInput in, String name) throws IOException; + + @Override + public final void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + doWriteTo(out); + out.writeOptionalString(fieldname); + out.writeOptionalString(text); + out.writeOptionalString(prefix); + out.writeOptionalString(regex); + out.writeOptionalString(analyzer); + out.writeOptionalVInt(size); + out.writeOptionalVInt(shardSize); + } + + protected abstract void doWriteTo(StreamOutput out) throws IOException; + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + @SuppressWarnings("unchecked") + T other = (T) obj; + return Objects.equals(name, other.name()) && + Objects.equals(text, other.text()) && + Objects.equals(prefix, other.prefix()) && + Objects.equals(regex, other.regex()) && + Objects.equals(fieldname, other.field()) && + Objects.equals(analyzer, other.analyzer()) && + Objects.equals(size, other.size()) && + Objects.equals(shardSize, other.shardSize()) && + doEquals(other); + } + + /** + * Indicates whether some other {@link SuggestionBuilder} of the same type is "equal to" this one. + */ + protected abstract boolean doEquals(T other); + + @Override + public final int hashCode() { + return Objects.hash(name, text, prefix, regex, fieldname, analyzer, size, shardSize, doHashCode()); + } + + /** + * HashCode for the subclass of {@link SuggestionBuilder} to implement. + */ + protected abstract int doHashCode(); +} \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java index 9cf78ea6677..1b515e75409 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java @@ -22,11 +22,13 @@ import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery; import org.apache.lucene.util.automaton.Operations; import org.apache.lucene.util.automaton.RegExp; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.query.RegexpFlag; -import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext; import org.elasticsearch.search.suggest.completion.context.GeoQueryContext; @@ -45,7 +47,7 @@ import java.util.Set; * are created at index-time and so must be defined in the mapping with the type "completion" before * indexing. */ -public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilder { +public class CompletionSuggestionBuilder extends SuggestionBuilder { final static String SUGGESTION_NAME = "completion"; static final ParseField PAYLOAD_FIELD = new ParseField("payload"); @@ -56,7 +58,7 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde private final Set payloadFields = new HashSet<>(); public CompletionSuggestionBuilder(String name) { - super(name, SUGGESTION_NAME); + super(name); } /** @@ -255,8 +257,9 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde * Sets the prefix to provide completions for. * The prefix gets analyzed by the suggest analyzer. */ + @Override public CompletionSuggestionBuilder prefix(String prefix) { - super.setPrefix(prefix); + super.prefix(prefix); return this; } @@ -264,7 +267,7 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde * Same as {@link #prefix(String)} with fuzziness of fuzziness */ public CompletionSuggestionBuilder prefix(String prefix, Fuzziness fuzziness) { - super.setPrefix(prefix); + super.prefix(prefix); this.fuzzyOptionsBuilder = new FuzzyOptionsBuilder().setFuzziness(fuzziness); return this; } @@ -274,7 +277,7 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde * see {@link FuzzyOptionsBuilder} */ public CompletionSuggestionBuilder prefix(String prefix, FuzzyOptionsBuilder fuzzyOptionsBuilder) { - super.setPrefix(prefix); + super.prefix(prefix); this.fuzzyOptionsBuilder = fuzzyOptionsBuilder; return this; } @@ -282,8 +285,9 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde /** * Sets a regular expression pattern for prefixes to provide completions for. */ + @Override public CompletionSuggestionBuilder regex(String regex) { - super.setRegex(regex); + super.regex(regex); return this; } @@ -362,4 +366,33 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde } return builder; } + + @Override + public String getWriteableName() { + return SUGGESTION_NAME; + } + + @Override + public void doWriteTo(StreamOutput out) throws IOException { + // NORELEASE + throw new UnsupportedOperationException(); + } + + @Override + public CompletionSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException { + // NORELEASE + throw new UnsupportedOperationException(); + } + + @Override + protected boolean doEquals(CompletionSuggestionBuilder other) { + // NORELEASE + return false; + } + + @Override + protected int doHashCode() { + // NORELEASE + return 0; + } } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilder.java index 1055fbe83fc..46c9b0f99f6 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilder.java @@ -18,10 +18,12 @@ */ package org.elasticsearch.search.suggest.phrase; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.script.Template; -import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import java.io.IOException; import java.util.ArrayList; @@ -29,12 +31,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; /** * Defines the actual suggest command for phrase suggestions ( phrase). */ public final class PhraseSuggestionBuilder extends SuggestionBuilder { + + static final String SUGGESTION_NAME = "phrase"; + + public static final PhraseSuggestionBuilder PROTOTYPE = new PhraseSuggestionBuilder("_na_"); + private Float maxErrors; private String separator; private Float realWordErrorLikelihood; @@ -51,7 +59,7 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder0.95 corresponding to 5% or @@ -100,6 +129,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder collateParams() { + return this.collateParams; + } + /** * Sets whether to prune suggestions after collation */ @@ -205,6 +290,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder> generators = new HashMap<>(); + } + + @Override + public PhraseSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException { + PhraseSuggestionBuilder builder = new PhraseSuggestionBuilder(name); + builder.maxErrors = in.readOptionalFloat(); + builder.realWordErrorLikelihood = in.readOptionalFloat(); + builder.confidence = in.readOptionalFloat(); + builder.gramSize = in.readOptionalVInt(); + // NORELEASE read model + builder.forceUnigrams = in.readOptionalBoolean(); + builder.tokenLimit = in.readOptionalVInt(); + builder.preTag = in.readOptionalString(); + builder.postTag = in.readOptionalString(); + builder.separator = in.readOptionalString(); + if (in.readBoolean()) { + builder.collateQuery = Template.readTemplate(in); + } + builder.collateParams = in.readMap(); + builder.collatePrune = in.readOptionalBoolean(); + // NORELEASE read Map> generators; + return builder; + } + + @Override + protected boolean doEquals(PhraseSuggestionBuilder other) { + return Objects.equals(maxErrors, other.maxErrors) && + Objects.equals(separator, other.separator) && + Objects.equals(realWordErrorLikelihood, other.realWordErrorLikelihood) && + Objects.equals(confidence, other.confidence) && + // NORELEASE Objects.equals(generator, other.generator) && + Objects.equals(gramSize, other.gramSize) && + // NORELEASE Objects.equals(model, other.model) && + Objects.equals(forceUnigrams, other.forceUnigrams) && + Objects.equals(tokenLimit, other.tokenLimit) && + Objects.equals(preTag, other.preTag) && + Objects.equals(postTag, other.postTag) && + Objects.equals(collateQuery, other.collateQuery) && + Objects.equals(collateParams, other.collateParams) && + Objects.equals(collatePrune, other.collatePrune); + } + + @Override + protected int doHashCode() { + return Objects.hash(maxErrors, separator, realWordErrorLikelihood, confidence, + /** NORELEASE generators, */ + gramSize, + /** NORELEASE model, */ + forceUnigrams, tokenLimit, preTag, postTag, + collateQuery, collateParams, collatePrune); + } + } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java index 03eb388f003..e2a14c1a2b2 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java @@ -17,8 +17,10 @@ * under the License. */ package org.elasticsearch.search.suggest.term; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; import java.io.IOException; @@ -29,6 +31,8 @@ import java.io.IOException; */ public class TermSuggestionBuilder extends SuggestionBuilder { + static final String SUGGESTION_NAME = "term"; + private String suggestMode; private Float accuracy; private String sort; @@ -39,13 +43,13 @@ public class TermSuggestionBuilder extends SuggestionBuilder> extends ESTestCase { + + private static final int NUMBER_OF_TESTBUILDERS = 20; + private static NamedWriteableRegistry namedWriteableRegistry; + + /** + * setup for the whole base test class + */ + @BeforeClass + public static void init() { + namedWriteableRegistry = new NamedWriteableRegistry(); + namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, PhraseSuggestionBuilder.PROTOTYPE); + } + + @AfterClass + public static void afterClass() throws Exception { + namedWriteableRegistry = null; + } + + /** + * Test serialization and deserialization of the suggestion builder + */ + public void testSerialization() throws IOException { + for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) { + SB original = randomTestBuilder(); + SB deserialized = serializedCopy(original); + assertEquals(deserialized, original); + assertEquals(deserialized.hashCode(), original.hashCode()); + assertNotSame(deserialized, original); + } + } + + /** + * returns a random suggestion builder, setting the common options randomly + */ + protected SB randomTestBuilder() { + SB randomSuggestion = randomSuggestionBuilder(); + maybeSet(randomSuggestion::text, randomAsciiOfLengthBetween(2, 20)); + maybeSet(randomSuggestion::prefix, randomAsciiOfLengthBetween(2, 20)); + maybeSet(randomSuggestion::regex, randomAsciiOfLengthBetween(2, 20)); + maybeSet(randomSuggestion::field, randomAsciiOfLengthBetween(2, 20)); + maybeSet(randomSuggestion::analyzer, randomAsciiOfLengthBetween(2, 20)); + maybeSet(randomSuggestion::size, randomIntBetween(1, 20)); + maybeSet(randomSuggestion::shardSize, randomInt(20)); + return randomSuggestion; + } + + /** + * create a randomized {@link SuggestBuilder} that is used in further tests + */ + protected abstract SB randomSuggestionBuilder(); + + /** + * Test equality and hashCode properties + */ + public void testEqualsAndHashcode() throws IOException { + for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) { + SB firstBuilder = randomTestBuilder(); + assertFalse("suggestion builder is equal to null", firstBuilder.equals(null)); + assertFalse("suggestion builder is equal to incompatible type", firstBuilder.equals("")); + assertTrue("suggestion builder is not equal to self", firstBuilder.equals(firstBuilder)); + assertThat("same suggestion builder's hashcode returns different values if called multiple times", firstBuilder.hashCode(), + equalTo(firstBuilder.hashCode())); + assertThat("different suggestion builders should not be equal", mutate(firstBuilder), not(equalTo(firstBuilder))); + + SB secondBuilder = serializedCopy(firstBuilder); + assertTrue("suggestion builder is not equal to self", secondBuilder.equals(secondBuilder)); + assertTrue("suggestion builder is not equal to its copy", firstBuilder.equals(secondBuilder)); + assertTrue("equals is not symmetric", secondBuilder.equals(firstBuilder)); + assertThat("suggestion builder copy's hashcode is different from original hashcode", secondBuilder.hashCode(), equalTo(firstBuilder.hashCode())); + + SB thirdBuilder = serializedCopy(secondBuilder); + assertTrue("suggestion builder is not equal to self", thirdBuilder.equals(thirdBuilder)); + assertTrue("suggestion builder is not equal to its copy", secondBuilder.equals(thirdBuilder)); + assertThat("suggestion builder copy's hashcode is different from original hashcode", secondBuilder.hashCode(), equalTo(thirdBuilder.hashCode())); + assertTrue("equals is not transitive", firstBuilder.equals(thirdBuilder)); + assertThat("suggestion builder copy's hashcode is different from original hashcode", firstBuilder.hashCode(), equalTo(thirdBuilder.hashCode())); + assertTrue("equals is not symmetric", thirdBuilder.equals(secondBuilder)); + assertTrue("equals is not symmetric", thirdBuilder.equals(firstBuilder)); + } + } + + private SB mutate(SB firstBuilder) throws IOException { + SB mutation = serializedCopy(firstBuilder); + assertNotSame(mutation, firstBuilder); + if (randomBoolean()) { + // change one of the common SuggestionBuilder parameters + switch (randomIntBetween(0, 6)) { + case 0: + mutation.text(randomValueOtherThan(mutation.text(), () -> randomAsciiOfLengthBetween(2, 20))); + break; + case 1: + mutation.prefix(randomValueOtherThan(mutation.prefix(), () -> randomAsciiOfLengthBetween(2, 20))); + break; + case 2: + mutation.regex(randomValueOtherThan(mutation.regex(), () -> randomAsciiOfLengthBetween(2, 20))); + break; + case 3: + mutation.field(randomValueOtherThan(mutation.field(), () -> randomAsciiOfLengthBetween(2, 20))); + break; + case 4: + mutation.analyzer(randomValueOtherThan(mutation.analyzer(), () -> randomAsciiOfLengthBetween(2, 20))); + break; + case 5: + mutation.size(randomValueOtherThan(mutation.size(), () -> randomIntBetween(1, 20))); + break; + case 6: + mutation.shardSize(randomValueOtherThan(mutation.shardSize(), () -> randomIntBetween(1, 20))); + break; + } + } else { + mutateSpecificParameters(firstBuilder); + } + return mutation; + } + + /** + * take and input {@link SuggestBuilder} and return another one that is different in one aspect (to test non-equality) + */ + protected abstract void mutateSpecificParameters(SB firstBuilder) throws IOException; + + @SuppressWarnings("unchecked") + protected SB serializedCopy(SB original) throws IOException { + try (BytesStreamOutput output = new BytesStreamOutput()) { + output.writeSuggestion(original);; + try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) { + return (SB) in.readSuggestion(); + } + } + } + + protected static void maybeSet(Consumer consumer, T value) { + if (randomBoolean()) { + consumer.accept(value); + } + } + + /** + * helper to get a random value in a certain range that's different from the input + */ + protected static T randomValueOtherThan(T input, Supplier randomSupplier) { + T randomValue = null; + do { + randomValue = randomSupplier.get(); + } while (randomValue.equals(input)); + return randomValue; + } + +} diff --git a/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java b/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java index 1543433be32..271fa08487b 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.search.suggest; import com.carrotsearch.hppc.ObjectLongHashMap; import com.carrotsearch.randomizedtesting.generators.RandomStrings; + import org.apache.lucene.analysis.TokenStreamToAutomaton; import org.apache.lucene.search.suggest.document.ContextSuggestField; import org.apache.lucene.util.LuceneTestCase.SuppressCodecs; @@ -907,7 +908,7 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase { } - public void assertSuggestions(String suggestionName, SuggestBuilder.SuggestionBuilder suggestBuilder, String... suggestions) { + public void assertSuggestions(String suggestionName, SuggestionBuilder suggestBuilder, String... suggestions) { SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(suggestBuilder ).execute().actionGet(); assertSuggestions(suggestResponse, suggestionName, suggestions); diff --git a/core/src/test/java/org/elasticsearch/search/suggest/ContextCompletionSuggestSearchIT.java b/core/src/test/java/org/elasticsearch/search/suggest/ContextCompletionSuggestSearchIT.java index 18d6d9b99f9..d92bd865f59 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/ContextCompletionSuggestSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/ContextCompletionSuggestSearchIT.java @@ -632,7 +632,7 @@ public class ContextCompletionSuggestSearchIT extends ESIntegTestCase { assertEquals("Hotel Amsterdam in Berlin", suggestResponse.getSuggest().getSuggestion(suggestionName).iterator().next().getOptions().iterator().next().getText().string()); } - public void assertSuggestions(String suggestionName, SuggestBuilder.SuggestionBuilder suggestBuilder, String... suggestions) { + public void assertSuggestions(String suggestionName, SuggestionBuilder suggestBuilder, String... suggestions) { SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(suggestBuilder ).execute().actionGet(); CompletionSuggestSearchIT.assertSuggestions(suggestResponse, suggestionName, suggestions); diff --git a/core/src/test/java/org/elasticsearch/search/suggest/CustomSuggesterSearchIT.java b/core/src/test/java/org/elasticsearch/search/suggest/CustomSuggesterSearchIT.java index 18b4fa50e7b..80eb4d7b7d4 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/CustomSuggesterSearchIT.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/CustomSuggesterSearchIT.java @@ -20,6 +20,8 @@ package org.elasticsearch.search.suggest; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.plugins.Plugin; @@ -31,6 +33,7 @@ import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Objects; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.hasSize; @@ -59,16 +62,7 @@ public class CustomSuggesterSearchIT extends ESIntegTestCase { String randomField = randomAsciiOfLength(10); String randomSuffix = randomAsciiOfLength(10); SuggestBuilder suggestBuilder = new SuggestBuilder(); - suggestBuilder.addSuggestion( - new SuggestBuilder.SuggestionBuilder("someName", "custom") { - @Override - protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { - builder.field("field", randomField); - builder.field("suffix", randomSuffix); - return builder; - } - }.text(randomText) - ); + suggestBuilder.addSuggestion(new CustomSuggestionBuilder("someName", randomField, randomSuffix).text(randomText)); SearchRequestBuilder searchRequestBuilder = client().prepareSearch("test").setTypes("test").setFrom(0).setSize(1) .suggest(suggestBuilder); @@ -83,4 +77,51 @@ public class CustomSuggesterSearchIT extends ESIntegTestCase { assertThat(suggestions.get(1).getText().string(), is(String.format(Locale.ROOT, "%s-%s-%s-123", randomText, randomField, randomSuffix))); } + class CustomSuggestionBuilder extends SuggestionBuilder { + + private String randomField; + private String randomSuffix; + + public CustomSuggestionBuilder(String name, String randomField, String randomSuffix) { + super(name); + this.randomField = randomField; + this.randomSuffix = randomSuffix; + } + + @Override + protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { + builder.field("field", randomField); + builder.field("suffix", randomSuffix); + return builder; + } + + @Override + public String getWriteableName() { + return "custom"; + } + + @Override + public void doWriteTo(StreamOutput out) throws IOException { + out.writeString(randomField); + out.writeString(randomSuffix); + } + + @Override + public CustomSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException { + return new CustomSuggestionBuilder(in.readString(), in.readString(), in.readString()); + } + + @Override + protected boolean doEquals(CustomSuggestionBuilder other) { + return Objects.equals(randomField, other.randomField) && + Objects.equals(randomSuffix, other.randomSuffix); + } + + @Override + protected int doHashCode() { + return Objects.hash(randomField, randomSuffix); + } + + } + } diff --git a/core/src/test/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilderTests.java b/core/src/test/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilderTests.java new file mode 100644 index 00000000000..74f655b6aa1 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilderTests.java @@ -0,0 +1,114 @@ +/* + * 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.phrase; + +import org.elasticsearch.script.Template; +import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class PhraseSuggestionBuilderTests extends AbstractSuggestionBuilderTestCase { + + @Override + protected PhraseSuggestionBuilder randomSuggestionBuilder() { + PhraseSuggestionBuilder testBuilder = new PhraseSuggestionBuilder(randomAsciiOfLength(10)); + maybeSet(testBuilder::maxErrors, randomFloat()); + maybeSet(testBuilder::separator, randomAsciiOfLengthBetween(1, 10)); + maybeSet(testBuilder::realWordErrorLikelihood, randomFloat()); + maybeSet(testBuilder::confidence, randomFloat()); + maybeSet(testBuilder::collatePrune, randomBoolean()); + maybeSet(testBuilder::collateQuery, randomAsciiOfLengthBetween(3, 20)); + if (randomBoolean()) { + // preTag, postTag + testBuilder.highlight(randomAsciiOfLengthBetween(3, 20), randomAsciiOfLengthBetween(3, 20)); + } + maybeSet(testBuilder::gramSize, randomIntBetween(1, 5)); + maybeSet(testBuilder::forceUnigrams, randomBoolean()); + maybeSet(testBuilder::tokenLimit, randomInt(20)); + if (randomBoolean()) { + Map collateParams = new HashMap<>(); + collateParams.put(randomAsciiOfLength(5), randomAsciiOfLength(5)); + testBuilder.collateParams(collateParams ); + } + if (randomBoolean()) { + // NORELEASE add random model + } + + if (randomBoolean()) { + // NORELEASE add random generator + } + return testBuilder; + } + + @Override + protected void mutateSpecificParameters(PhraseSuggestionBuilder builder) throws IOException { + switch (randomIntBetween(0, 7)) { + case 0: + builder.maxErrors(randomValueOtherThan(builder.maxErrors(), () -> randomFloat())); + break; + case 1: + builder.realWordErrorLikelihood(randomValueOtherThan(builder.realWordErrorLikelihood(), () -> randomFloat())); + break; + case 2: + builder.confidence(randomValueOtherThan(builder.confidence(), () -> randomFloat())); + break; + case 3: + builder.gramSize(randomValueOtherThan(builder.gramSize(), () -> randomIntBetween(1, 5))); + break; + case 4: + builder.tokenLimit(randomValueOtherThan(builder.tokenLimit(), () -> randomInt(20))); + break; + case 5: + builder.separator(randomValueOtherThan(builder.separator(), () -> randomAsciiOfLengthBetween(1, 10))); + break; + case 6: + Template collateQuery = builder.collateQuery(); + if (collateQuery != null) { + builder.collateQuery(randomValueOtherThan(collateQuery.getScript(), () -> randomAsciiOfLengthBetween(3, 20))); + } else { + builder.collateQuery(randomAsciiOfLengthBetween(3, 20)); + } + break; + case 7: + builder.collatePrune(builder.collatePrune() == null ? randomBoolean() : !builder.collatePrune() ); + break; + case 8: + // preTag, postTag + String currentPre = builder.preTag(); + if (currentPre != null) { + // simply double both values + builder.highlight(builder.preTag() + builder.preTag(), builder.postTag() + builder.postTag()); + } else { + builder.highlight(randomAsciiOfLengthBetween(3, 20), randomAsciiOfLengthBetween(3, 20)); + } + break; + case 9: + builder.forceUnigrams(builder.forceUnigrams() == null ? randomBoolean() : ! builder.forceUnigrams()); + break; + case 10: + builder.collateParams().put(randomAsciiOfLength(5), randomAsciiOfLength(5)); + break; + // TODO mutate random Model && generator + } + } + +} diff --git a/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java b/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java index 4fd83f9a850..8511394a427 100644 --- a/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java +++ b/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java @@ -20,43 +20,6 @@ package org.elasticsearch.messy.tests; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; -import org.elasticsearch.action.index.IndexRequestBuilder; -import org.elasticsearch.action.search.ReduceSearchPhaseException; -import org.elasticsearch.action.search.SearchPhaseExecutionException; -import org.elasticsearch.action.search.SearchRequestBuilder; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.search.ShardSearchFailure; -import org.elasticsearch.action.suggest.SuggestRequestBuilder; -import org.elasticsearch.action.suggest.SuggestResponse; -import org.elasticsearch.common.io.PathUtils; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.mustache.MustachePlugin; -import org.elasticsearch.search.suggest.Suggest; -import org.elasticsearch.search.suggest.SuggestBuilder; -import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; -import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder; -import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.DirectCandidateGenerator; -import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; -import org.elasticsearch.test.ESIntegTestCase; -import org.elasticsearch.test.hamcrest.ElasticsearchAssertions; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; - import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; import static org.elasticsearch.common.settings.Settings.settingsBuilder; @@ -76,6 +39,43 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.nullValue; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.action.search.ReduceSearchPhaseException; +import org.elasticsearch.action.search.SearchPhaseExecutionException; +import org.elasticsearch.action.search.SearchRequestBuilder; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.ShardSearchFailure; +import org.elasticsearch.action.suggest.SuggestRequestBuilder; +import org.elasticsearch.action.suggest.SuggestResponse; +import org.elasticsearch.common.io.PathUtils; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.mustache.MustachePlugin; +import org.elasticsearch.search.suggest.Suggest; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.search.suggest.SuggestionBuilder; +import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder; +import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.DirectCandidateGenerator; +import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.hamcrest.ElasticsearchAssertions; + /** * Integration tests for term and phrase suggestions. Many of these tests many requests that vary only slightly from one another. Where * possible these tests should declare for the first request, make the request, modify the configuration for the next request, make that