Merge pull request #16505 from cbuescher/suggestBuilder-refactoring
Add fromXContent method to SuggestBuilder
This commit is contained in:
commit
16d01088de
|
@ -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;
|
||||
|
|
|
@ -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<SuggestBuilder> {
|
||||
|
||||
public static final SuggestBuilder PROTOTYPE = new SuggestBuilder();
|
||||
protected static final ParseField GLOBAL_TEXT_FIELD = new ParseField("text");
|
||||
|
||||
private String globalText;
|
||||
private final List<SuggestionBuilder<?>> suggestions = new ArrayList<>();
|
||||
|
||||
|
@ -58,7 +66,14 @@ public class SuggestBuilder extends ToXContentToBytes implements Writeable<Sugge
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link org.elasticsearch.search.suggest.term.TermSuggestionBuilder} instance under a user defined name.
|
||||
* Gets the global suggest text
|
||||
*/
|
||||
public String getText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link org.elasticsearch.search.suggest.SuggestionBuilder} instance under a user defined name.
|
||||
* The order in which the <code>Suggestions</code> are added, is the same as in the response.
|
||||
*/
|
||||
public SuggestBuilder addSuggestion(SuggestionBuilder<?> suggestion) {
|
||||
|
@ -66,6 +81,13 @@ public class SuggestBuilder extends ToXContentToBytes implements Writeable<Sugge
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <code>Suggestions</code> that were added to the globat {@link SuggestBuilder}
|
||||
*/
|
||||
public List<SuggestionBuilder<?>> getSuggestions() {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all suggestions with the defined names.
|
||||
*/
|
||||
|
@ -86,6 +108,35 @@ public class SuggestBuilder extends ToXContentToBytes implements Writeable<Sugge
|
|||
return builder;
|
||||
}
|
||||
|
||||
public static SuggestBuilder fromXContent(QueryParseContext parseContext, Suggesters suggesters) throws IOException {
|
||||
XContentParser parser = parseContext.parser();
|
||||
ParseFieldMatcher parseFieldMatcher = parseContext.parseFieldMatcher();
|
||||
SuggestBuilder suggestBuilder = new SuggestBuilder();
|
||||
String fieldName = null;
|
||||
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
fieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if (parseFieldMatcher.match(fieldName, GLOBAL_TEXT_FIELD)) {
|
||||
suggestBuilder.setText(parser.text());
|
||||
} else {
|
||||
throw new IllegalArgumentException("[suggest] does not support [" + fieldName + "]");
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
String suggestionName = fieldName;
|
||||
if (suggestionName == null) {
|
||||
throw new IllegalArgumentException("Suggestion must have name");
|
||||
}
|
||||
suggestBuilder.addSuggestion(SuggestionBuilder.fromXContent(parseContext, suggestionName, suggesters));
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "] after [" + fieldName + "]");
|
||||
}
|
||||
}
|
||||
return suggestBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuggestBuilder readFrom(StreamInput in) throws IOException {
|
||||
final SuggestBuilder builder = new SuggestBuilder();
|
||||
|
@ -125,5 +176,4 @@ public class SuggestBuilder extends ToXContentToBytes implements Writeable<Sugge
|
|||
public int hashCode() {
|
||||
return Objects.hash(globalText, suggestions);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -414,7 +414,7 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
|
|||
* Default discount parameter for {@link StupidBackoff} smoothing
|
||||
*/
|
||||
public static final double DEFAULT_BACKOFF_DISCOUNT = 0.4;
|
||||
static final StupidBackoff PROTOTYPE = new StupidBackoff(DEFAULT_BACKOFF_DISCOUNT);
|
||||
public static final StupidBackoff PROTOTYPE = new StupidBackoff(DEFAULT_BACKOFF_DISCOUNT);
|
||||
private double discount = DEFAULT_BACKOFF_DISCOUNT;
|
||||
private static final String NAME = "stupid_backoff";
|
||||
private static final ParseField DISCOUNT_FIELD = new ParseField("discount");
|
||||
|
@ -511,7 +511,7 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
|
|||
* Default alpha parameter for laplace smoothing
|
||||
*/
|
||||
public static final double DEFAULT_LAPLACE_ALPHA = 0.5;
|
||||
static final Laplace PROTOTYPE = new Laplace(DEFAULT_LAPLACE_ALPHA);
|
||||
public static final Laplace PROTOTYPE = new Laplace(DEFAULT_LAPLACE_ALPHA);
|
||||
|
||||
/**
|
||||
* Creates a Laplace smoothing model.
|
||||
|
@ -656,7 +656,7 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
|
|||
*/
|
||||
public static final class LinearInterpolation extends SmoothingModel {
|
||||
private static final String NAME = "linear";
|
||||
static final LinearInterpolation PROTOTYPE = new LinearInterpolation(0.8, 0.1, 0.1);
|
||||
public static final LinearInterpolation PROTOTYPE = new LinearInterpolation(0.8, 0.1, 0.1);
|
||||
private final double trigramLambda;
|
||||
private final double bigramLambda;
|
||||
private final double unigramLambda;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
|
||||
import org.elasticsearch.search.suggest.completion.WritableTestCase;
|
||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
|
||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.Laplace;
|
||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.LinearInterpolation;
|
||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.SmoothingModel;
|
||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.StupidBackoff;
|
||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilderTests;
|
||||
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public class SuggestBuilderTests extends WritableTestCase<SuggestBuilder> {
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<M extends Writeable> 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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue