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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Superclass for {@link ObjectParser} and {@link ConstructingObjectParser}. Defines most of the "declare" methods so they can be shared.
|
* Superclass for {@link ObjectParser} and {@link ConstructingObjectParser}. Defines most of the "declare" methods so they can be shared.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractObjectParser<Value, Context>
|
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<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declare some field. Usually it is easier to use {@link #declareString(BiConsumer, ParseField)} or
|
* 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
|
* @param requiredSet
|
||||||
* A set of required fields, where at least one of the fields in the array _must_ be present
|
* A set of required fields, where at least one of the fields in the array _must_ be present
|
||||||
*/
|
*/
|
||||||
public void declareRequiredFieldSet(String... requiredSet) {
|
public abstract void declareRequiredFieldSet(String... requiredSet);
|
||||||
if (requiredSet.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.requiredFieldSets.add(requiredSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declares a set of fields of which at most one must appear for parsing to succeed
|
* 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
|
* @param exclusiveSet a set of field names, at most one of which must appear
|
||||||
*/
|
*/
|
||||||
public void declareExclusiveFieldSet(String... exclusiveSet) {
|
public abstract void declareExclusiveFieldSet(String... exclusiveSet);
|
||||||
if (exclusiveSet.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.exclusiveFieldSets.add(exclusiveSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface IOSupplier<T> {
|
private interface IOSupplier<T> {
|
||||||
T get() throws IOException;
|
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.
|
* Note: if optional constructor arguments aren't specified then the number of allocations is always the worst case.
|
||||||
* </p>
|
* </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.
|
* 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
|
* Constructor arguments are detected by this "marker" consumer. It
|
||||||
* keeps the API looking clean even if it is a bit sleezy.
|
* 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));
|
constructorArgInfos.add(new ConstructorArgInfo(parseField, required));
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return objectParser.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
|
* 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.
|
* {@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.
|
* 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;
|
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)
|
private void parseArray(XContentParser parser, FieldParser fieldParser, String currentFieldName, Value value, Context context)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
assert parser.currentToken() == XContentParser.Token.START_ARRAY : "Token was: " + parser.currentToken();
|
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.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
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.ObjectParserHelper;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
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));
|
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.completed = completed;
|
||||||
this.task = requireNonNull(task, "task is required");
|
this.task = requireNonNull(task, "task is required");
|
||||||
this.error = error;
|
this.error = error;
|
||||||
|
@ -174,21 +174,17 @@ public final class TaskResult implements Writeable, ToXContentObject {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final ConstructingObjectParser<TaskResult, Void> PARSER = new ConstructingObjectParser<>(
|
public static final InstantiatingObjectParser<TaskResult, Void> PARSER;
|
||||||
"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);
|
|
||||||
});
|
|
||||||
static {
|
static {
|
||||||
PARSER.declareBoolean(constructorArg(), new ParseField("completed"));
|
InstantiatingObjectParser.Builder<TaskResult, Void> parser = InstantiatingObjectParser.builder(
|
||||||
PARSER.declareObject(constructorArg(), TaskInfo.PARSER, new ParseField("task"));
|
"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<>();
|
ObjectParserHelper<TaskResult, Void> parserHelper = new ObjectParserHelper<>();
|
||||||
parserHelper.declareRawObject(PARSER, optionalConstructorArg(), new ParseField("error"));
|
parserHelper.declareRawObject(parser, optionalConstructorArg(), new ParseField("error"));
|
||||||
parserHelper.declareRawObject(PARSER, optionalConstructorArg(), new ParseField("response"));
|
parserHelper.declareRawObject(parser, optionalConstructorArg(), new ParseField("response"));
|
||||||
|
PARSER = parser.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue