diff --git a/core/src/main/java/org/jclouds/reflect/Reflection2.java b/core/src/main/java/org/jclouds/reflect/Reflection2.java index 7246f64443..03db79fce6 100644 --- a/core/src/main/java/org/jclouds/reflect/Reflection2.java +++ b/core/src/main/java/org/jclouds/reflect/Reflection2.java @@ -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) 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 Invokable constructor(Class ownerType, Class... parameterTypes) { - return (Invokable) 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, Set>> constructorsForTypeToken = CacheBuilder .newBuilder().build(new CacheLoader, Set>>() { @@ -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 method : methods(key.getRawType())){ + if (method.isStatic() && method.getReturnType().equals(key)) { + builder.add(method); + } + } + } return builder.build(); } }); diff --git a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java index 665fc2a54f..65671256f7 100644 --- a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java +++ b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java @@ -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.>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 foo(); + abstract Map bar(); + + @Inject + static ValueTypeWithFactory create(@Named("foo") List foo, @Named("bar") Map bar) { + return new GenericParamsCopiedIn(foo, bar); + } + } + + private static class GenericParamsCopiedIn extends ValueTypeWithFactory { final List foo; final Map bar; - @Inject - GenericParamsCopiedIn(@Named("foo") List foo, @Named("bar") Map bar) { + @Inject GenericParamsCopiedIn(@Named("foo") List foo, @Named("bar") Map bar) { this.foo = Lists.newArrayList(foo); this.bar = Maps.newHashMap(bar); } + + @Override List foo() { + return foo; + } + + @Override Map bar() { + return bar; + } + } + + public void testBuilderOnAbstractValueType() throws IOException { + TypeAdapter adapter = parameterizedCtorFactory.create(gson, + TypeToken.get(ValueTypeWithFactory.class)); + List inputFoo = Lists.newArrayList(); + inputFoo.add("one"); + Map 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 foo, Map 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}}")); } - } diff --git a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java index 340e32dbb6..a51e0da149 100644 --- a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java +++ b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java @@ -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 ctorParams = FluentIterable.from(constructors(TypeToken.of(MyValue.class))) + .transform(new Function, Iterable>() { + public Iterable 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)); }