Introduces InstantiatingObjectParser which is similar to the ConstructingObjectParser, but instantiates the object using its constructor instead of a builder function. Closes #52499
This commit is contained in:
parent
0844455505
commit
3504755f44
|
@ -28,17 +28,12 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Superclass for {@link ObjectParser} and {@link ConstructingObjectParser}. Defines most of the "declare" methods so they can be shared.
|
||||
*/
|
||||
public abstract class AbstractObjectParser<Value, Context>
|
||||
implements BiFunction<XContentParser, Context, Value>, ContextParser<Context, Value> {
|
||||
|
||||
final List<String[]> requiredFieldSets = new ArrayList<>();
|
||||
final List<String[]> exclusiveFieldSets = new ArrayList<>();
|
||||
public abstract class AbstractObjectParser<Value, Context> {
|
||||
|
||||
/**
|
||||
* Declare some field. Usually it is easier to use {@link #declareString(BiConsumer, ParseField)} or
|
||||
|
@ -313,12 +308,7 @@ public abstract class AbstractObjectParser<Value, Context>
|
|||
* @param requiredSet
|
||||
* A set of required fields, where at least one of the fields in the array _must_ be present
|
||||
*/
|
||||
public void declareRequiredFieldSet(String... requiredSet) {
|
||||
if (requiredSet.length == 0) {
|
||||
return;
|
||||
}
|
||||
this.requiredFieldSets.add(requiredSet);
|
||||
}
|
||||
public abstract void declareRequiredFieldSet(String... requiredSet);
|
||||
|
||||
/**
|
||||
* Declares a set of fields of which at most one must appear for parsing to succeed
|
||||
|
@ -332,12 +322,7 @@ public abstract class AbstractObjectParser<Value, Context>
|
|||
*
|
||||
* @param exclusiveSet a set of field names, at most one of which must appear
|
||||
*/
|
||||
public void declareExclusiveFieldSet(String... exclusiveSet) {
|
||||
if (exclusiveSet.length == 0) {
|
||||
return;
|
||||
}
|
||||
this.exclusiveFieldSets.add(exclusiveSet);
|
||||
}
|
||||
public abstract void declareExclusiveFieldSet(String... exclusiveSet);
|
||||
|
||||
private interface IOSupplier<T> {
|
||||
T get() throws IOException;
|
||||
|
|
|
@ -73,7 +73,9 @@ import java.util.function.Function;
|
|||
* Note: if optional constructor arguments aren't specified then the number of allocations is always the worst case.
|
||||
* </p>
|
||||
*/
|
||||
public final class ConstructingObjectParser<Value, Context> extends AbstractObjectParser<Value, Context> {
|
||||
public final class ConstructingObjectParser<Value, Context> extends AbstractObjectParser<Value, Context> implements
|
||||
BiFunction<XContentParser, Context, Value>, ContextParser<Context, Value>{
|
||||
|
||||
/**
|
||||
* Consumer that marks a field as a required constructor argument instead of a real object field.
|
||||
*/
|
||||
|
@ -315,6 +317,10 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
}
|
||||
}
|
||||
|
||||
int getNumberOfFields() {
|
||||
return this.constructorArgInfos.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor arguments are detected by this "marker" consumer. It
|
||||
* keeps the API looking clean even if it is a bit sleezy.
|
||||
|
@ -335,7 +341,7 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
constructorArgInfos.add(new ConstructorArgInfo(parseField, required));
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return objectParser.getName();
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Like {@link ConstructingObjectParser} but works with objects which have a constructor that matches declared fields.
|
||||
* <p>
|
||||
* Declaring a {@linkplain InstantiatingObjectParser} is intentionally quite similar to declaring an {@linkplain ConstructingObjectParser}
|
||||
* with two important differences.
|
||||
* <p>
|
||||
* The main differences being that it is using Builder to construct the parser and takes a class of the target object instead of the object
|
||||
* builder. The target object must have exactly one constructor with the number and order of arguments matching the number of order of
|
||||
* declared fields. If there are more then 2 constructors with the same number of arguments, one of them needs to be marked with
|
||||
* {@linkplain ParserConstructor} annotation.
|
||||
* <pre>{@code
|
||||
* public static class Thing{
|
||||
* public Thing(String animal, String vegetable, int mineral) {
|
||||
* ....
|
||||
* }
|
||||
*
|
||||
* public void setFruit(int fruit) { ... }
|
||||
*
|
||||
* public void setBug(int bug) { ... }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* private static final InstantiatingObjectParser<Thing, SomeContext> PARSER = new InstantiatingObjectParser<>("thing", Thing.class);
|
||||
* static {
|
||||
* PARSER.declareString(constructorArg(), new ParseField("animal"));
|
||||
* PARSER.declareString(constructorArg(), new ParseField("vegetable"));
|
||||
* PARSER.declareInt(optionalConstructorArg(), new ParseField("mineral"));
|
||||
* PARSER.declareInt(Thing::setFruit, new ParseField("fruit"));
|
||||
* PARSER.declareInt(Thing::setBug, new ParseField("bug"));
|
||||
* PARSER.finalizeFields()
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public class InstantiatingObjectParser<Value, Context>
|
||||
implements BiFunction<XContentParser, Context, Value>, ContextParser<Context, Value> {
|
||||
|
||||
public static <Value, Context> Builder<Value, Context> builder(String name, boolean ignoreUnknownFields, Class<Value> valueClass) {
|
||||
return new Builder<>(name, ignoreUnknownFields, valueClass);
|
||||
}
|
||||
|
||||
public static <Value, Context> Builder<Value, Context> builder(String name, Class<Value> valueClass) {
|
||||
return new Builder<>(name, valueClass);
|
||||
}
|
||||
|
||||
public static class Builder<Value, Context> extends AbstractObjectParser<Value, Context> {
|
||||
|
||||
private final ConstructingObjectParser<Value, Context> constructingObjectParser;
|
||||
|
||||
private final Class<Value> valueClass;
|
||||
|
||||
private Constructor<Value> constructor;
|
||||
|
||||
public Builder(String name, Class<Value> valueClass) {
|
||||
this(name, false, valueClass);
|
||||
}
|
||||
|
||||
public Builder(String name, boolean ignoreUnknownFields, Class<Value> valueClass) {
|
||||
this.constructingObjectParser = new ConstructingObjectParser<>(name, ignoreUnknownFields, this::build);
|
||||
this.valueClass = valueClass;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public InstantiatingObjectParser<Value, Context> build() {
|
||||
Constructor<?> constructor = null;
|
||||
int neededArguments = constructingObjectParser.getNumberOfFields();
|
||||
// Try to find an annotated constructor
|
||||
for (Constructor<?> c : valueClass.getConstructors()) {
|
||||
if (c.getAnnotation(ParserConstructor.class) != null) {
|
||||
if (constructor != null) {
|
||||
throw new IllegalArgumentException("More then one public constructor with @ParserConstructor annotation exist in " +
|
||||
"the class " + valueClass.getName());
|
||||
}
|
||||
if (c.getParameterCount() != neededArguments) {
|
||||
throw new IllegalArgumentException("Annotated constructor doesn't have " + neededArguments +
|
||||
" arguments in the class " + valueClass.getName());
|
||||
}
|
||||
constructor = c;
|
||||
}
|
||||
}
|
||||
if (constructor == null) {
|
||||
// fallback to a constructor with required number of arguments
|
||||
for (Constructor<?> c : valueClass.getConstructors()) {
|
||||
if (c.getParameterCount() == neededArguments) {
|
||||
if (constructor != null) {
|
||||
throw new IllegalArgumentException("More then one public constructor with " + neededArguments +
|
||||
" arguments found. The use of @ParserConstructor annotation is required for class " + valueClass.getName());
|
||||
}
|
||||
constructor = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (constructor == null) {
|
||||
throw new IllegalArgumentException("No public constructors with " + neededArguments + " parameters exist in the class " +
|
||||
valueClass.getName());
|
||||
}
|
||||
this.constructor = (Constructor<Value>) constructor;
|
||||
return new InstantiatingObjectParser<>(constructingObjectParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Context, T> parser, ParseField parseField,
|
||||
ObjectParser.ValueType type) {
|
||||
constructingObjectParser.declareField(consumer, parser, parseField, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void declareNamedObject(BiConsumer<Value, T> consumer, ObjectParser.NamedObjectParser<T, Context> namedObjectParser,
|
||||
ParseField parseField) {
|
||||
constructingObjectParser.declareNamedObject(consumer, namedObjectParser, parseField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer,
|
||||
ObjectParser.NamedObjectParser<T, Context> namedObjectParser, ParseField parseField) {
|
||||
constructingObjectParser.declareNamedObjects(consumer, namedObjectParser, parseField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer,
|
||||
ObjectParser.NamedObjectParser<T, Context> namedObjectParser,
|
||||
Consumer<Value> orderedModeCallback, ParseField parseField) {
|
||||
constructingObjectParser.declareNamedObjects(consumer, namedObjectParser, orderedModeCallback, parseField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return constructingObjectParser.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareRequiredFieldSet(String... requiredSet) {
|
||||
constructingObjectParser.declareRequiredFieldSet(requiredSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareExclusiveFieldSet(String... exclusiveSet) {
|
||||
constructingObjectParser.declareExclusiveFieldSet(exclusiveSet);
|
||||
}
|
||||
|
||||
private Value build(Object[] args) {
|
||||
if (constructor == null) {
|
||||
throw new IllegalArgumentException("InstantiatingObjectParser for type " + valueClass.getName() + " has to be finalized " +
|
||||
"before the first use");
|
||||
}
|
||||
try {
|
||||
return constructor.newInstance(args);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("Cannot instantiate an object of " + valueClass.getName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final ConstructingObjectParser<Value, Context> constructingObjectParser;
|
||||
|
||||
private InstantiatingObjectParser(ConstructingObjectParser<Value, Context> constructingObjectParser) {
|
||||
this.constructingObjectParser = constructingObjectParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value parse(XContentParser parser, Context context) throws IOException {
|
||||
return constructingObjectParser.parse(parser, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value apply(XContentParser xContentParser, Context context) {
|
||||
return constructingObjectParser.apply(xContentParser, context);
|
||||
}
|
||||
}
|
|
@ -70,7 +70,12 @@ import static org.elasticsearch.common.xcontent.XContentParser.Token.VALUE_STRIN
|
|||
* It's highly recommended to use the high level declare methods like {@link #declareString(BiConsumer, ParseField)} instead of
|
||||
* {@link #declareField} which can be used to implement exceptional parsing operations not covered by the high level methods.
|
||||
*/
|
||||
public final class ObjectParser<Value, Context> extends AbstractObjectParser<Value, Context> {
|
||||
public final class ObjectParser<Value, Context> extends AbstractObjectParser<Value, Context>
|
||||
implements BiFunction<XContentParser, Context, Value>, ContextParser<Context, Value>{
|
||||
|
||||
private final List<String[]> requiredFieldSets = new ArrayList<>();
|
||||
private final List<String[]> exclusiveFieldSets = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Adapts an array (or varags) setter into a list setter.
|
||||
*/
|
||||
|
@ -496,6 +501,22 @@ public final class ObjectParser<Value, Context> extends AbstractObjectParser<Val
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareRequiredFieldSet(String... requiredSet) {
|
||||
if (requiredSet.length == 0) {
|
||||
return;
|
||||
}
|
||||
this.requiredFieldSets.add(requiredSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareExclusiveFieldSet(String... exclusiveSet) {
|
||||
if (exclusiveSet.length == 0) {
|
||||
return;
|
||||
}
|
||||
this.exclusiveFieldSets.add(exclusiveSet);
|
||||
}
|
||||
|
||||
private void parseArray(XContentParser parser, FieldParser fieldParser, String currentFieldName, Value value, Context context)
|
||||
throws IOException {
|
||||
assert parser.currentToken() == XContentParser.Token.START_ARRAY : "Token was: " + parser.currentToken();
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.common.xcontent;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks the constructor that should be used by {@linkplain InstantiatingObjectParser} if multiple constructors with the same
|
||||
* number of arguments exist.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.CONSTRUCTOR })
|
||||
public @interface ParserConstructor {
|
||||
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class InstantiatingObjectParserTests extends ESTestCase {
|
||||
|
||||
public static class NoAnnotations {
|
||||
final int a;
|
||||
final String b;
|
||||
final long c;
|
||||
|
||||
public NoAnnotations() {
|
||||
this(1, "2", 3);
|
||||
}
|
||||
|
||||
private NoAnnotations(int a) {
|
||||
this(a, "2", 3);
|
||||
}
|
||||
|
||||
public NoAnnotations(int a, String b) {
|
||||
this(a, b, 3);
|
||||
}
|
||||
|
||||
public NoAnnotations(int a, long c) {
|
||||
this(a, "2", c);
|
||||
}
|
||||
|
||||
public NoAnnotations(int a, String b, long c) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
NoAnnotations that = (NoAnnotations) o;
|
||||
return a == that.a &&
|
||||
c == that.c &&
|
||||
Objects.equals(b, that.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(a, b, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void testNoAnnotation() throws IOException {
|
||||
InstantiatingObjectParser.Builder<NoAnnotations, Void> builder = InstantiatingObjectParser.builder("foo", NoAnnotations.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
builder.declareString(constructorArg(), new ParseField("b"));
|
||||
builder.declareLong(constructorArg(), new ParseField("c"));
|
||||
InstantiatingObjectParser<NoAnnotations, Void> parser = builder.build();
|
||||
try (XContentParser contentParser = createParser(JsonXContent.jsonXContent, "{\"a\": 5, \"b\":\"6\", \"c\": 7 }")) {
|
||||
assertThat(parser.parse(contentParser, null), equalTo(new NoAnnotations(5, "6", 7)));
|
||||
}
|
||||
}
|
||||
|
||||
public void testNoAnnotationWrongArgumentNumber() {
|
||||
InstantiatingObjectParser.Builder<NoAnnotations, Void> builder = InstantiatingObjectParser.builder("foo", NoAnnotations.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
builder.declareString(constructorArg(), new ParseField("b"));
|
||||
builder.declareLong(constructorArg(), new ParseField("c"));
|
||||
builder.declareLong(constructorArg(), new ParseField("d"));
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, builder::build);
|
||||
assertThat(e.getMessage(), containsString("No public constructors with 4 parameters exist in the class"));
|
||||
}
|
||||
|
||||
public void testAmbiguousConstructor() {
|
||||
InstantiatingObjectParser.Builder<NoAnnotations, Void> builder = InstantiatingObjectParser.builder("foo", NoAnnotations.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
builder.declareString(constructorArg(), new ParseField("b"));
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, builder::build);
|
||||
assertThat(e.getMessage(), containsString(
|
||||
"More then one public constructor with 2 arguments found. The use of @ParserConstructor annotation is required"
|
||||
));
|
||||
}
|
||||
|
||||
public void testPrivateConstructor() {
|
||||
InstantiatingObjectParser.Builder<NoAnnotations, Void> builder = InstantiatingObjectParser.builder("foo", NoAnnotations.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, builder::build);
|
||||
assertThat(e.getMessage(), containsString("No public constructors with 1 parameters exist in the class "));
|
||||
}
|
||||
|
||||
public static class LonelyArgument {
|
||||
public final int a;
|
||||
|
||||
private String b;
|
||||
|
||||
public LonelyArgument(int a) {
|
||||
this.a = a;
|
||||
this.b = "Not set";
|
||||
}
|
||||
|
||||
public void setB(String b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public String getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
LonelyArgument that = (LonelyArgument) o;
|
||||
return a == that.a &&
|
||||
Objects.equals(b, that.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
public void testOneArgConstructor() throws IOException {
|
||||
InstantiatingObjectParser.Builder<LonelyArgument, Void> builder = InstantiatingObjectParser.builder("foo", LonelyArgument.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
InstantiatingObjectParser<LonelyArgument, Void> parser = builder.build();
|
||||
try (XContentParser contentParser = createParser(JsonXContent.jsonXContent, "{\"a\": 5 }")) {
|
||||
assertThat(parser.parse(contentParser, null), equalTo(new LonelyArgument(5)));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSetNonConstructor() throws IOException {
|
||||
InstantiatingObjectParser.Builder<LonelyArgument, Void> builder = InstantiatingObjectParser.builder("foo", LonelyArgument.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
builder.declareString(LonelyArgument::setB, new ParseField("b"));
|
||||
InstantiatingObjectParser<LonelyArgument, Void> parser = builder.build();
|
||||
try (XContentParser contentParser = createParser(JsonXContent.jsonXContent, "{\"a\": 5, \"b\": \"set\" }")) {
|
||||
LonelyArgument expected = parser.parse(contentParser, null);
|
||||
assertThat(expected.a, equalTo(5));
|
||||
assertThat(expected.b, equalTo("set"));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Annotations {
|
||||
final int a;
|
||||
final String b;
|
||||
final long c;
|
||||
|
||||
public Annotations() {
|
||||
this(1, "2", 3);
|
||||
}
|
||||
|
||||
public Annotations(int a, String b) {
|
||||
this(a, b, -1);
|
||||
}
|
||||
|
||||
public Annotations(int a, String b, long c) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
@ParserConstructor
|
||||
public Annotations(int a, String b, String c) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = Long.parseLong(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Annotations that = (Annotations) o;
|
||||
return a == that.a &&
|
||||
c == that.c &&
|
||||
Objects.equals(b, that.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(a, b, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void testAnnotation() throws IOException {
|
||||
InstantiatingObjectParser.Builder<Annotations, Void> builder = InstantiatingObjectParser.builder("foo", Annotations.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
builder.declareString(constructorArg(), new ParseField("b"));
|
||||
builder.declareString(constructorArg(), new ParseField("c"));
|
||||
InstantiatingObjectParser<Annotations, Void> parser = builder.build();
|
||||
try (XContentParser contentParser = createParser(JsonXContent.jsonXContent, "{\"a\": 5, \"b\":\"6\", \"c\": \"7\"}")) {
|
||||
assertThat(parser.parse(contentParser, null), equalTo(new Annotations(5, "6", 7)));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAnnotationWrongArgumentNumber() {
|
||||
InstantiatingObjectParser.Builder<Annotations, Void> builder = InstantiatingObjectParser.builder("foo", Annotations.class);
|
||||
builder.declareInt(constructorArg(), new ParseField("a"));
|
||||
builder.declareString(constructorArg(), new ParseField("b"));
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, builder::build);
|
||||
assertThat(e.getMessage(), containsString("Annotated constructor doesn't have 2 arguments in the class"));
|
||||
}
|
||||
|
||||
}
|
|
@ -27,7 +27,7 @@ import org.elasticsearch.common.bytes.BytesReference;
|
|||
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.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.InstantiatingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ObjectParserHelper;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
|
@ -79,7 +79,7 @@ public final class TaskResult implements Writeable, ToXContentObject {
|
|||
this(true, task, null, XContentHelper.toXContent(response, Requests.INDEX_CONTENT_TYPE, true));
|
||||
}
|
||||
|
||||
private TaskResult(boolean completed, TaskInfo task, @Nullable BytesReference error, @Nullable BytesReference result) {
|
||||
public TaskResult(boolean completed, TaskInfo task, @Nullable BytesReference error, @Nullable BytesReference result) {
|
||||
this.completed = completed;
|
||||
this.task = requireNonNull(task, "task is required");
|
||||
this.error = error;
|
||||
|
@ -174,21 +174,17 @@ public final class TaskResult implements Writeable, ToXContentObject {
|
|||
return builder;
|
||||
}
|
||||
|
||||
public static final ConstructingObjectParser<TaskResult, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"stored_task_result", a -> {
|
||||
int i = 0;
|
||||
boolean completed = (boolean) a[i++];
|
||||
TaskInfo task = (TaskInfo) a[i++];
|
||||
BytesReference error = (BytesReference) a[i++];
|
||||
BytesReference response = (BytesReference) a[i++];
|
||||
return new TaskResult(completed, task, error, response);
|
||||
});
|
||||
public static final InstantiatingObjectParser<TaskResult, Void> PARSER;
|
||||
|
||||
static {
|
||||
PARSER.declareBoolean(constructorArg(), new ParseField("completed"));
|
||||
PARSER.declareObject(constructorArg(), TaskInfo.PARSER, new ParseField("task"));
|
||||
InstantiatingObjectParser.Builder<TaskResult, Void> parser = InstantiatingObjectParser.builder(
|
||||
"stored_task_result", true, TaskResult.class);
|
||||
parser.declareBoolean(constructorArg(), new ParseField("completed"));
|
||||
parser.declareObject(constructorArg(), TaskInfo.PARSER, new ParseField("task"));
|
||||
ObjectParserHelper<TaskResult, Void> parserHelper = new ObjectParserHelper<>();
|
||||
parserHelper.declareRawObject(PARSER, optionalConstructorArg(), new ParseField("error"));
|
||||
parserHelper.declareRawObject(PARSER, optionalConstructorArg(), new ParseField("response"));
|
||||
parserHelper.declareRawObject(parser, optionalConstructorArg(), new ParseField("error"));
|
||||
parserHelper.declareRawObject(parser, optionalConstructorArg(), new ParseField("response"));
|
||||
PARSER = parser.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue