JCLOUDS-750 Allow abstract value types to be constructed from json using static factory methods.

This commit is contained in:
Adrian Cole 2014-10-25 23:16:45 -07:00
parent 8b6081bb94
commit 33ce3eeb12
3 changed files with 78 additions and 24 deletions

View File

@ -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();
} }
}); });

View File

@ -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}}"));
} }
} }

View File

@ -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));
} }