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
2a1dff243d
commit
671749d6b7
|
@ -23,6 +23,7 @@ import static com.google.common.collect.Iterables.tryFind;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -70,23 +71,6 @@ public class Reflection2 {
|
|||
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.
|
||||
*
|
||||
|
@ -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
|
||||
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
|
||||
|
@ -154,6 +139,14 @@ public class Reflection2 {
|
|||
ctor.setAccessible(true);
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Map;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
|
||||
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
|
||||
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
|
||||
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
|
||||
|
@ -61,7 +62,7 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
|||
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
|
||||
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
|
||||
new ExtractSerializedName(), new ExtractNamed()));
|
||||
NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = new NamingStrategies.AnnotationConstructorNamingStrategy(
|
||||
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
|
||||
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
|
||||
|
||||
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(ImmutableMap.<Type, InstanceCreator<?>>of()),
|
||||
|
@ -152,15 +153,58 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
|||
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 Map<String, String> bar;
|
||||
|
||||
@Inject
|
||||
GenericParamsCopiedIn(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
||||
@Inject GenericParamsCopiedIn(@Named("foo") List<String> foo, @Named("bar") Map<String, String> bar) {
|
||||
this.foo = Lists.newArrayList(foo);
|
||||
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 {
|
||||
|
@ -175,7 +219,6 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
|||
assertEquals(inputFoo, toTest.foo);
|
||||
assertNotSame(inputFoo, toTest.foo);
|
||||
assertEquals(inputBar, toTest.bar);
|
||||
|
||||
}
|
||||
|
||||
private static class RenamedFields {
|
||||
|
@ -259,5 +302,4 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
|
|||
.create(gson, TypeToken.get(ComposedObjects.class));
|
||||
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]"));
|
||||
}
|
||||
|
||||
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() {
|
||||
assertEquals(typeToken(String.class), TypeToken.of(String.class));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue