mirror of https://github.com/apache/jclouds.git
JCLOUDS-750 Allow abstract value types to be constructed from json using static factory methods.
This commit is contained in:
parent
8b6081bb94
commit
33ce3eeb12
|
@ -23,6 +23,7 @@ import static com.google.common.collect.Iterables.tryFind;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -70,23 +71,6 @@ public class Reflection2 {
|
||||||
return (TypeToken<T>) get(typeTokenForClass, checkNotNull(in, "class"));
|
return (TypeToken<T>) get(typeTokenForClass, checkNotNull(in, "class"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* returns an {@link Invokable} object that reflects a constructor present in the {@link TypeToken} type.
|
|
||||||
*
|
|
||||||
* @param ownerType
|
|
||||||
* corresponds to {@link Invokable#getOwnerType()}
|
|
||||||
* @param parameterTypes
|
|
||||||
* corresponds to {@link Constructor#getParameterTypes()}
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the constructor doesn't exist or a security exception occurred
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> Invokable<T, T> constructor(Class<T> ownerType, Class<?>... parameterTypes) {
|
|
||||||
return (Invokable<T, T>) get(constructorForParams, new TypeTokenAndParameterTypes(typeToken(ownerType),
|
|
||||||
parameterTypes));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return all constructors present in the class as {@link Invokable}s.
|
* return all constructors present in the class as {@link Invokable}s.
|
||||||
*
|
*
|
||||||
|
@ -144,7 +128,8 @@ public class Reflection2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this gets all declared constructors, not just public ones. makes them accessible, as well.
|
* This gets all declared constructors, not just public ones. makes them accessible, as well.
|
||||||
|
* This also includes factory methods on abstract types, defined static methods returning the same type.
|
||||||
*/
|
*/
|
||||||
private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> constructorsForTypeToken = CacheBuilder
|
private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> constructorsForTypeToken = CacheBuilder
|
||||||
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
|
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
|
||||||
|
@ -154,6 +139,14 @@ public class Reflection2 {
|
||||||
ctor.setAccessible(true);
|
ctor.setAccessible(true);
|
||||||
builder.add(key.constructor(ctor));
|
builder.add(key.constructor(ctor));
|
||||||
}
|
}
|
||||||
|
// Look for factory methods, if this is an abstract type.
|
||||||
|
if (Modifier.isAbstract(key.getRawType().getModifiers())) {
|
||||||
|
for (Invokable<?, Object> method : methods(key.getRawType())){
|
||||||
|
if (method.isStatic() && method.getReturnType().equals(key)) {
|
||||||
|
builder.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Map;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
|
||||||
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
||||||
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
||||||
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
|
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
|
||||||
|
@ -61,7 +62,7 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
|
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
|
||||||
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
|
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
|
||||||
new ExtractSerializedName(), new ExtractNamed()));
|
new ExtractSerializedName(), new ExtractNamed()));
|
||||||
NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = new NamingStrategies.AnnotationConstructorNamingStrategy(
|
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
|
||||||
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
|
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
|
||||||
|
|
||||||
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()),
|
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()),
|
||||||
|
@ -152,15 +153,58 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
gson.fromJson("{\"bar\":1}", ValidatedConstructor.class);
|
gson.fromJson("{\"bar\":1}", ValidatedConstructor.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class GenericParamsCopiedIn {
|
private abstract static class ValueTypeWithFactory {
|
||||||
|
abstract List<String> foo();
|
||||||
|
abstract Map<String, String> bar();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
static ValueTypeWithFactory create(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
||||||
|
return new GenericParamsCopiedIn(foo, bar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GenericParamsCopiedIn extends ValueTypeWithFactory {
|
||||||
final List<String> foo;
|
final List<String> foo;
|
||||||
final Map<String, String> bar;
|
final Map<String, String> bar;
|
||||||
|
|
||||||
@Inject
|
@Inject GenericParamsCopiedIn(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
||||||
GenericParamsCopiedIn(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
|
||||||
this.foo = Lists.newArrayList(foo);
|
this.foo = Lists.newArrayList(foo);
|
||||||
this.bar = Maps.newHashMap(bar);
|
this.bar = Maps.newHashMap(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override List<String> foo() {
|
||||||
|
return foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override Map<String, String> bar() {
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBuilderOnAbstractValueType() throws IOException {
|
||||||
|
TypeAdapter<ValueTypeWithFactory> adapter = parameterizedCtorFactory.create(gson,
|
||||||
|
TypeToken.get(ValueTypeWithFactory.class));
|
||||||
|
List<String> inputFoo = Lists.newArrayList();
|
||||||
|
inputFoo.add("one");
|
||||||
|
Map<String, String> inputBar = Maps.newHashMap();
|
||||||
|
inputBar.put("2", "two");
|
||||||
|
|
||||||
|
ValueTypeWithFactory toTest = adapter.fromJson("{ \"foo\":[\"one\"], \"bar\":{ \"2\":\"two\"}}");
|
||||||
|
assertEquals(inputFoo, toTest.foo());
|
||||||
|
assertNotSame(inputFoo, toTest.foo());
|
||||||
|
assertEquals(inputBar, toTest.bar());
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class ValueTypeWithFactoryMissingSerializedNames {
|
||||||
|
@Inject
|
||||||
|
static ValueTypeWithFactoryMissingSerializedNames create(List<String> foo, Map<String, String> bar) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named")
|
||||||
|
public void testSerializedNameRequiredOnAllFactoryMethodParameters() {
|
||||||
|
parameterizedCtorFactory.create(gson, TypeToken.get(ValueTypeWithFactoryMissingSerializedNames.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGenericParamsCopiedIn() throws IOException {
|
public void testGenericParamsCopiedIn() throws IOException {
|
||||||
|
@ -175,7 +219,6 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
assertEquals(inputFoo, toTest.foo);
|
assertEquals(inputFoo, toTest.foo);
|
||||||
assertNotSame(inputFoo, toTest.foo);
|
assertNotSame(inputFoo, toTest.foo);
|
||||||
assertEquals(inputBar, toTest.bar);
|
assertEquals(inputBar, toTest.bar);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RenamedFields {
|
private static class RenamedFields {
|
||||||
|
@ -259,5 +302,4 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
||||||
.create(gson, TypeToken.get(ComposedObjects.class));
|
.create(gson, TypeToken.get(ComposedObjects.class));
|
||||||
assertNull(adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1}}"));
|
assertNull(adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1}}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,25 @@ public class Reflection2Test {
|
||||||
"[int arg0, float arg1]", "[int arg0]", "[int arg0, float arg1, boolean arg2]"));
|
"[int arg0, float arg1]", "[int arg0]", "[int arg0, float arg1, boolean arg2]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private abstract static class MyValue {
|
||||||
|
abstract String foo();
|
||||||
|
|
||||||
|
static MyValue create(String foo){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConstructorsReturnsFactoryMethods() {
|
||||||
|
Set<String> ctorParams = FluentIterable.from(constructors(TypeToken.of(MyValue.class)))
|
||||||
|
.transform(new Function<Invokable<?, ?>, Iterable<Parameter>>() {
|
||||||
|
public Iterable<Parameter> apply(Invokable<?, ?> input) {
|
||||||
|
return input.getParameters();
|
||||||
|
}
|
||||||
|
}).transform(toStringFunction()).toSet();
|
||||||
|
|
||||||
|
assertEquals(ctorParams, ImmutableSet.of("[]", "[java.lang.String arg0]"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testTypeTokenForClass() {
|
public void testTypeTokenForClass() {
|
||||||
assertEquals(typeToken(String.class), TypeToken.of(String.class));
|
assertEquals(typeToken(String.class), TypeToken.of(String.class));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue