Browse Source

Adding fromXContent to Suggestion.Entry and subclasses (#23202)

This adds parsing from xContent to Suggestion.Entry and its subclasses for Terms-, Phrase-
and CompletionSuggestion.Entry.
Christoph Büscher 8 years ago
parent
commit
268d15ec4c

+ 35 - 16
core/src/main/java/org/elasticsearch/search/suggest/Suggest.java

@@ -25,6 +25,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Streamable;
 import org.elasticsearch.common.io.stream.Streamable;
 import org.elasticsearch.common.text.Text;
 import org.elasticsearch.common.text.Text;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -371,37 +372,38 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
         /**
         /**
          * Represents a part from the suggest text with suggested options.
          * Represents a part from the suggest text with suggested options.
          */
          */
-        public static class Entry<O extends Entry.Option> implements Iterable<O>, Streamable, ToXContent {
+        public static class Entry<O extends Entry.Option> implements Iterable<O>, Streamable, ToXContentObject {
 
 
-            static class Fields {
-
-                static final String TEXT = "text";
-                static final String OFFSET = "offset";
-                static final String LENGTH = "length";
-                static final String OPTIONS = "options";
-
-            }
+            private static final String TEXT = "text";
+            private static final String OFFSET = "offset";
+            private static final String LENGTH = "length";
+            protected static final String OPTIONS = "options";
 
 
             protected Text text;
             protected Text text;
             protected int offset;
             protected int offset;
             protected int length;
             protected int length;
 
 
-            protected List<O> options;
+            protected List<O> options = new ArrayList<>(5);
 
 
             public Entry(Text text, int offset, int length) {
             public Entry(Text text, int offset, int length) {
                 this.text = text;
                 this.text = text;
                 this.offset = offset;
                 this.offset = offset;
                 this.length = length;
                 this.length = length;
-                this.options = new ArrayList<>(5);
             }
             }
 
 
-            public Entry() {
+            protected Entry() {
             }
             }
 
 
             public void addOption(O option) {
             public void addOption(O option) {
                 options.add(option);
                 options.add(option);
             }
             }
 
 
+            protected void addOptions(List<O> options) {
+                for (O option : options) {
+                    addOption(option);
+                }
+            }
+
             protected void sort(Comparator<O> comparator) {
             protected void sort(Comparator<O> comparator) {
                 CollectionUtil.timSort(options, comparator);
                 CollectionUtil.timSort(options, comparator);
             }
             }
@@ -539,10 +541,10 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
             @Override
             @Override
             public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
             public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
                 builder.startObject();
                 builder.startObject();
-                builder.field(Fields.TEXT, text);
-                builder.field(Fields.OFFSET, offset);
-                builder.field(Fields.LENGTH, length);
-                builder.startArray(Fields.OPTIONS);
+                builder.field(TEXT, text);
+                builder.field(OFFSET, offset);
+                builder.field(LENGTH, length);
+                builder.startArray(OPTIONS);
                 for (Option option : options) {
                 for (Option option : options) {
                     option.toXContent(builder, params);
                     option.toXContent(builder, params);
                 }
                 }
@@ -551,6 +553,23 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
                 return builder;
                 return builder;
             }
             }
 
 
+            private static ObjectParser<Entry<Option>, Void> PARSER = new ObjectParser<>("SuggestionEntryParser", true, Entry::new);
+
+            static {
+                declareCommonFields(PARSER);
+                PARSER.declareObjectArray(Entry::addOptions, (p,c) -> Option.fromXContent(p), new ParseField(OPTIONS));
+            }
+
+            protected static void declareCommonFields(ObjectParser<? extends Entry<? extends Option>, Void> parser) {
+                parser.declareString((entry, text) -> entry.text = new Text(text), new ParseField(TEXT));
+                parser.declareInt((entry, offset) -> entry.offset = offset, new ParseField(OFFSET));
+                parser.declareInt((entry, length) -> entry.length = length, new ParseField(LENGTH));
+            }
+
+            public static Entry<? extends Option> fromXContent(XContentParser parser) {
+                return PARSER.apply(parser, null);
+            }
+
             /**
             /**
              * Contains the suggested text with its document frequency and score.
              * Contains the suggested text with its document frequency and score.
              */
              */

+ 13 - 2
core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestion.java

@@ -194,8 +194,7 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
             super(text, offset, length);
             super(text, offset, length);
         }
         }
 
 
-        protected Entry() {
-            super();
+        Entry() {
         }
         }
 
 
         @Override
         @Override
@@ -203,6 +202,18 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
             return new Option();
             return new Option();
         }
         }
 
 
+        private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("CompletionSuggestionEntryParser", 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);
+        }
+
         public static class Option extends Suggest.Suggestion.Entry.Option {
         public static class Option extends Suggest.Suggestion.Entry.Option {
             private Map<String, Set<CharSequence>> contexts = Collections.emptyMap();
             private Map<String, Set<CharSequence>> contexts = Collections.emptyMap();
             private ScoreDoc doc;
             private ScoreDoc doc;

+ 15 - 1
core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestion.java

@@ -19,9 +19,12 @@
 
 
 package org.elasticsearch.search.suggest.phrase;
 package org.elasticsearch.search.suggest.phrase;
 
 
+import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.text.Text;
 import org.elasticsearch.common.text.Text;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.search.suggest.Suggest;
 import org.elasticsearch.search.suggest.Suggest;
 import org.elasticsearch.search.suggest.Suggest.Suggestion;
 import org.elasticsearch.search.suggest.Suggest.Suggestion;
 
 
@@ -69,7 +72,7 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
             this.cutoffScore = cutoffScore;
             this.cutoffScore = cutoffScore;
         }
         }
 
 
-        public Entry() {
+        Entry() {
         }
         }
 
 
         /**
         /**
@@ -100,6 +103,17 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
             }
             }
         }
         }
 
 
+        private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("PhraseSuggestionEntryParser", 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);
+        }
+
         @Override
         @Override
         public void readFrom(StreamInput in) throws IOException {
         public void readFrom(StreamInput in) throws IOException {
             super.readFrom(in);
             super.readFrom(in);

+ 13 - 1
core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestion.java

@@ -24,6 +24,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.text.Text;
 import org.elasticsearch.common.text.Text;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.search.suggest.SortBy;
 import org.elasticsearch.search.suggest.SortBy;
@@ -142,7 +143,7 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
     public static class Entry extends
     public static class Entry extends
             org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<TermSuggestion.Entry.Option> {
             org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<TermSuggestion.Entry.Option> {
 
 
-        Entry(Text text, int offset, int length) {
+        public Entry(Text text, int offset, int length) {
             super(text, offset, length);
             super(text, offset, length);
         }
         }
 
 
@@ -154,6 +155,17 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
             return new Option();
             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.
          * Contains the suggested text with its document frequency and score.
          */
          */

+ 162 - 0
core/src/test/java/org/elasticsearch/search/suggest/SuggestionEntryTests.java

@@ -0,0 +1,162 @@
+/*
+ * 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.bytes.BytesReference;
+import org.elasticsearch.common.text.Text;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.search.suggest.Suggest.Suggestion;
+import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
+import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
+import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
+import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
+import org.elasticsearch.search.suggest.term.TermSuggestion;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
+
+public class SuggestionEntryTests extends ESTestCase {
+
+    @SuppressWarnings("rawtypes")
+    private static final Map<Class<? extends Entry>, Function<XContentParser, ? extends Entry>> ENTRY_PARSERS = new HashMap<>();
+    static {
+        ENTRY_PARSERS.put(Suggestion.Entry.class, Suggestion.Entry::fromXContent);
+        ENTRY_PARSERS.put(TermSuggestion.Entry.class, TermSuggestion.Entry::fromXContent);
+        ENTRY_PARSERS.put(PhraseSuggestion.Entry.class, PhraseSuggestion.Entry::fromXContent);
+        ENTRY_PARSERS.put(CompletionSuggestion.Entry.class, CompletionSuggestion.Entry::fromXContent);
+    }
+
+    /**
+     * Create a randomized Suggestion.Entry
+     */
+    @SuppressWarnings("unchecked")
+    public static <O extends Option> Entry<O> createTestItem(Class<? extends Entry> entryType) {
+        Text entryText = new Text(randomAsciiOfLengthBetween(5, 15));
+        int offset = randomInt();
+        int length = randomInt();
+        @SuppressWarnings("rawtypes")
+        Entry entry = null;
+        Supplier<Option> supplier = null;
+        if (entryType == Suggestion.Entry.class) {
+            entry = new Suggestion.Entry<>(entryText, offset, length);
+            supplier = SuggestionOptionTests::createTestItem;
+        } else if (entryType == TermSuggestion.Entry.class) {
+            entry = new TermSuggestion.Entry(entryText, offset, length);
+            supplier = TermSuggestionOptionTests::createTestItem;
+        } else if (entryType == PhraseSuggestion.Entry.class) {
+            entry = new PhraseSuggestion.Entry(entryText, offset, length, randomDouble());
+            supplier = SuggestionOptionTests::createTestItem;
+        } else if (entryType == CompletionSuggestion.Entry.class) {
+            entry = new CompletionSuggestion.Entry(entryText, offset, length);
+            supplier = CompletionSuggestionOptionTests::createTestItem;
+        }
+        int numOptions = randomIntBetween(0, 5);
+        for (int i = 0; i < numOptions; i++) {
+            entry.addOption(supplier.get());
+        }
+        return entry;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testFromXContent() throws IOException {
+        for (Class<? extends Entry> entryType : ENTRY_PARSERS.keySet()) {
+            Entry<Option> entry = createTestItem(entryType);
+            XContentType xContentType = randomFrom(XContentType.values());
+            boolean humanReadable = randomBoolean();
+            BytesReference originalBytes = toXContent(entry, xContentType, humanReadable);
+            Entry<Option> parsed;
+            try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
+                ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+                parsed = ENTRY_PARSERS.get(entry.getClass()).apply(parser);
+                assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
+                assertNull(parser.nextToken());
+            }
+            assertEquals(entry.getClass(), parsed.getClass());
+            assertEquals(entry.getText(), parsed.getText());
+            assertEquals(entry.getLength(), parsed.getLength());
+            assertEquals(entry.getOffset(), parsed.getOffset());
+            assertEquals(entry.getOptions().size(), parsed.getOptions().size());
+            for (int i = 0; i < entry.getOptions().size(); i++) {
+                assertEquals(entry.getOptions().get(i).getClass(), parsed.getOptions().get(i).getClass());
+            }
+            assertToXContentEquivalent(originalBytes, toXContent(parsed, xContentType, humanReadable), xContentType);
+        }
+    }
+
+    public void testToXContent() throws IOException {
+        Option option = new Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
+        Entry<Option> entry = new Entry<>(new Text("entryText"), 42, 313);
+        entry.addOption(option);
+        BytesReference xContent = toXContent(entry, XContentType.JSON, randomBoolean());
+        assertEquals(
+                "{\"text\":\"entryText\","
+                + "\"offset\":42,"
+                + "\"length\":313,"
+                + "\"options\":["
+                    + "{\"text\":\"someText\","
+                    + "\"highlighted\":\"somethingHighlighted\","
+                    + "\"score\":1.3,"
+                    + "\"collate_match\":true}"
+                + "]}", xContent.utf8ToString());
+
+        org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option termOption =
+                new org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option(new Text("termSuggestOption"), 42, 3.13f);
+        entry = new Entry<>(new Text("entryText"), 42, 313);
+        entry.addOption(termOption);
+        xContent = toXContent(entry, XContentType.JSON, randomBoolean());
+        assertEquals(
+                "{\"text\":\"entryText\","
+                + "\"offset\":42,"
+                + "\"length\":313,"
+                + "\"options\":["
+                    + "{\"text\":\"termSuggestOption\","
+                    + "\"score\":3.13,"
+                    + "\"freq\":42}"
+                + "]}", xContent.utf8ToString());
+
+        org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option completionOption =
+                new org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option(-1, new Text("completionOption"),
+                        3.13f, Collections.singletonMap("key", Collections.singleton("value")));
+        entry = new Entry<>(new Text("entryText"), 42, 313);
+        entry.addOption(completionOption);
+        xContent = toXContent(entry, XContentType.JSON, randomBoolean());
+        assertEquals(
+                "{\"text\":\"entryText\","
+                + "\"offset\":42,"
+                + "\"length\":313,"
+                + "\"options\":["
+                    + "{\"text\":\"completionOption\","
+                    + "\"score\":3.13,"
+                    + "\"contexts\":{\"key\":[\"value\"]}"
+                    + "}"
+                + "]}", xContent.utf8ToString());
+    }
+
+}