From 9abf00b665cdcb62aaee1805a24172ae3b15d0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Mon, 8 Feb 2016 15:24:06 +0100 Subject: [PATCH] Add fromXContent method to SuggestBuilder --- .../elasticsearch/search/SearchService.java | 1 + .../search/suggest/SuggestBuilder.java | 54 +++++++- .../phrase/PhraseSuggestionBuilder.java | 6 +- .../search/suggest/SuggestBuilderTests.java | 118 ++++++++++++++++++ .../suggest/completion/WritableTestCase.java | 115 +++++++++++++++++ .../phrase/PhraseSuggestionBuilderTests.java | 4 + 6 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/search/suggest/SuggestBuilderTests.java create mode 100644 core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java diff --git a/core/src/main/java/org/elasticsearch/search/SearchService.java b/core/src/main/java/org/elasticsearch/search/SearchService.java index 0da838a799b..29fa5555fed 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchService.java +++ b/core/src/main/java/org/elasticsearch/search/SearchService.java @@ -20,6 +20,7 @@ package org.elasticsearch.search; import com.carrotsearch.hppc.ObjectFloatHashMap; + import org.apache.lucene.search.FieldDoc; import org.apache.lucene.search.TopDocs; import org.elasticsearch.ExceptionsHelper; 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 8037646f152..d16e8e1d84a 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java @@ -20,10 +20,15 @@ package org.elasticsearch.search.suggest; import org.elasticsearch.action.support.ToXContentToBytes; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.QueryParseContext; import java.io.IOException; import java.util.ArrayList; @@ -39,6 +44,9 @@ import java.util.Objects; */ public class SuggestBuilder extends ToXContentToBytes implements Writeable { + public static final SuggestBuilder PROTOTYPE = new SuggestBuilder(); + protected static final ParseField GLOBAL_TEXT_FIELD = new ParseField("text"); + private String globalText; private final List> suggestions = new ArrayList<>(); @@ -58,7 +66,14 @@ public class SuggestBuilder extends ToXContentToBytes implements WriteableSuggestions are added, is the same as in the response. */ public SuggestBuilder addSuggestion(SuggestionBuilder suggestion) { @@ -66,6 +81,13 @@ public class SuggestBuilder extends ToXContentToBytes implements WriteableSuggestions that were added to the globat {@link SuggestBuilder} + */ + public List> getSuggestions() { + return suggestions; + } + /** * Returns all suggestions with the defined names. */ @@ -86,6 +108,35 @@ public class SuggestBuilder extends ToXContentToBytes implements Writeable { + + + @Override + protected NamedWriteableRegistry provideNamedWritbaleRegistry() { + NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(); + namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, TermSuggestionBuilder.PROTOTYPE); + namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, PhraseSuggestionBuilder.PROTOTYPE); + namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, CompletionSuggestionBuilder.PROTOTYPE); + namedWriteableRegistry.registerPrototype(SmoothingModel.class, Laplace.PROTOTYPE); + namedWriteableRegistry.registerPrototype(SmoothingModel.class, LinearInterpolation.PROTOTYPE); + namedWriteableRegistry.registerPrototype(SmoothingModel.class, StupidBackoff.PROTOTYPE); + return namedWriteableRegistry; + } + + /** + * creates random suggestion builder, renders it to xContent and back to new instance that should be equal to original + */ + public void testFromXContent() throws IOException { + Suggesters suggesters = new Suggesters(Collections.emptyMap(), null, null); + QueryParseContext context = new QueryParseContext(null); + context.parseFieldMatcher(new ParseFieldMatcher(Settings.EMPTY)); + for (int runs = 0; runs < NUMBER_OF_RUNS; runs++) { + SuggestBuilder suggestBuilder = createTestModel(); + XContentBuilder xContentBuilder = XContentFactory.contentBuilder(randomFrom(XContentType.values())); + if (randomBoolean()) { + xContentBuilder.prettyPrint(); + } + suggestBuilder.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); + XContentParser parser = XContentHelper.createParser(xContentBuilder.bytes()); + context.reset(parser); + parser.nextToken(); + + SuggestBuilder secondSuggestBuilder = SuggestBuilder.fromXContent(context, suggesters); + assertNotSame(suggestBuilder, secondSuggestBuilder); + assertEquals(suggestBuilder, secondSuggestBuilder); + assertEquals(suggestBuilder.hashCode(), secondSuggestBuilder.hashCode()); + } + } + + @Override + protected SuggestBuilder createTestModel() { + SuggestBuilder suggestBuilder = new SuggestBuilder(); + if (randomBoolean()) { + suggestBuilder.setText(randomAsciiOfLengthBetween(5, 50)); + } + int numberOfSuggestions = randomIntBetween(0, 5); + for (int i = 0; i < numberOfSuggestions; i++) { + suggestBuilder.addSuggestion(PhraseSuggestionBuilderTests.randomPhraseSuggestionBuilder()); + } + return suggestBuilder; + } + + @Override + protected SuggestBuilder createMutation(SuggestBuilder original) throws IOException { + SuggestBuilder mutation = new SuggestBuilder().setText(original.getText()); + for (SuggestionBuilder suggestionBuilder : original.getSuggestions()) { + mutation.addSuggestion(suggestionBuilder); + } + if (randomBoolean()) { + mutation.setText(randomAsciiOfLengthBetween(5, 60)); + } else { + mutation.addSuggestion(PhraseSuggestionBuilderTests.randomPhraseSuggestionBuilder()); + } + return mutation; + } + + @Override + protected SuggestBuilder readFrom(StreamInput in) throws IOException { + return SuggestBuilder.PROTOTYPE.readFrom(in); + } + +} diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java new file mode 100644 index 00000000000..47b33733425 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java @@ -0,0 +1,115 @@ +/* + * 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.completion; + +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; + +/** + * Base class for testing serialization and equality for + * {@link Writeable} models + */ +public abstract class WritableTestCase extends ESTestCase { + + protected static final int NUMBER_OF_RUNS = 20; + + /** + * create random model that is put under test + */ + protected abstract M createTestModel(); + + /** + * mutate the given model so the returned model is different + */ + protected abstract M createMutation(M original) throws IOException; + + /** + * model prototype to read serialized format + */ + protected abstract M readFrom(StreamInput in) throws IOException; + + /** + * Test serialization and deserialization of the tested model. + */ + public void testSerialization() throws IOException { + for (int i = 0; i < NUMBER_OF_RUNS; i++) { + M testModel = createTestModel(); + M deserializedModel = copyModel(testModel); + assertEquals(testModel, deserializedModel); + assertEquals(testModel.hashCode(), deserializedModel.hashCode()); + assertNotSame(testModel, deserializedModel); + } + } + + /** + * Test equality and hashCode properties + */ + @SuppressWarnings("unchecked") + public void testEqualsAndHashcode() throws IOException { + M firstModel = createTestModel(); + String modelName = firstModel.getClass().getSimpleName(); + assertFalse(modelName + " is equal to null", firstModel.equals(null)); + assertFalse(modelName + " is equal to incompatible type", firstModel.equals("")); + assertTrue(modelName + " is not equal to self", firstModel.equals(firstModel)); + assertThat("same "+ modelName + "'s hashcode returns different values if called multiple times", firstModel.hashCode(), + equalTo(firstModel.hashCode())); + assertThat("different " + modelName + " should not be equal", createMutation(firstModel), not(equalTo(firstModel))); + + M secondModel = copyModel(firstModel); + assertTrue(modelName + " is not equal to self", secondModel.equals(secondModel)); + assertTrue(modelName + " is not equal to its copy", firstModel.equals(secondModel)); + assertTrue("equals is not symmetric", secondModel.equals(firstModel)); + assertThat(modelName + " copy's hashcode is different from original hashcode", secondModel.hashCode(), + equalTo(firstModel.hashCode())); + + M thirdModel = copyModel(secondModel); + assertTrue(modelName + " is not equal to self", thirdModel.equals(thirdModel)); + assertTrue(modelName + " is not equal to its copy", secondModel.equals(thirdModel)); + assertThat(modelName + " copy's hashcode is different from original hashcode", secondModel.hashCode(), + equalTo(thirdModel.hashCode())); + assertTrue("equals is not transitive", firstModel.equals(thirdModel)); + assertThat(modelName + " copy's hashcode is different from original hashcode", firstModel.hashCode(), + equalTo(thirdModel.hashCode())); + assertTrue(modelName + " equals is not symmetric", thirdModel.equals(secondModel)); + assertTrue(modelName + " equals is not symmetric", thirdModel.equals(firstModel)); + } + + private M copyModel(M original) throws IOException { + try (BytesStreamOutput output = new BytesStreamOutput()) { + original.writeTo(output); + try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), provideNamedWritbaleRegistry())) { + return readFrom(in); + } + } + } + + protected NamedWriteableRegistry provideNamedWritbaleRegistry() { + return new NamedWriteableRegistry(); + } +} 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 index 3cf65722a5d..d74719fa6f7 100644 --- a/core/src/test/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestionBuilderTests.java @@ -42,6 +42,10 @@ public class PhraseSuggestionBuilderTests extends AbstractSuggestionBuilderTestC @Override protected PhraseSuggestionBuilder randomSuggestionBuilder() { + return randomPhraseSuggestionBuilder(); + } + + public static PhraseSuggestionBuilder randomPhraseSuggestionBuilder() { PhraseSuggestionBuilder testBuilder = new PhraseSuggestionBuilder(randomAsciiOfLength(10)); maybeSet(testBuilder::maxErrors, randomFloat()); maybeSet(testBuilder::separator, randomAsciiOfLengthBetween(1, 10));