From a625127fd2cf05c5c33c03d1649be558f76cbc2d Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 20 Jan 2013 10:48:48 -0800 Subject: [PATCH] added ability to look up constructors as Invokables --- .../FilterStringsBoundToInjectorByName.java | 3 +- .../lifecycle/config/LifeCycleModule.java | 105 +++++----- .../java/org/jclouds/reflect/Reflection2.java | 196 +++++++++++++----- .../org/jclouds/rest/config/RestModule.java | 30 +-- .../org/jclouds/rest/config/SetCaller.java | 6 +- .../org/jclouds/reflect/Reflection2Test.java | 62 +++++- .../jclouds/rest/config/RestModuleTest.java | 120 +++++++++++ .../jclouds/abiquo/domain/DomainWrapper.java | 17 +- 8 files changed, 383 insertions(+), 156 deletions(-) create mode 100644 core/src/test/java/org/jclouds/rest/config/RestModuleTest.java diff --git a/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java b/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java index b5e71c5aba..9154777968 100644 --- a/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java +++ b/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java @@ -53,8 +53,7 @@ public class FilterStringsBoundToInjectorByName implements Function apply(Predicate filter) { - List> stringBindings = injector.findBindingsByType(new TypeLiteral() { - }); + List> stringBindings = injector.findBindingsByType(TypeLiteral.get(String.class)); Iterable> annotatedWithName = Iterables.filter(stringBindings, new Predicate>() { @Override diff --git a/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java b/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java index d5eff87de7..213a59ed8a 100644 --- a/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java +++ b/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java @@ -19,19 +19,18 @@ package org.jclouds.lifecycle.config; import static com.google.common.base.Throwables.propagate; -import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.collect.Iterables.filter; import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; import static com.google.inject.matcher.Matchers.any; -import static java.util.Arrays.asList; import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS; import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS; import static org.jclouds.Constants.PROPERTY_USER_THREADS; +import static org.jclouds.reflect.Reflection2.methods; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Set; +import java.util.Collection; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.PostConstruct; @@ -40,6 +39,8 @@ import javax.inject.Named; import org.jclouds.lifecycle.Closer; +import com.google.common.base.Predicate; +import com.google.common.reflect.Invokable; import com.google.common.util.concurrent.ExecutionList; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.inject.AbstractModule; @@ -51,15 +52,15 @@ import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; /** - * This associates java lifecycle annotations with guice hooks. For example, we invoke - * {@link PostConstruct} after injection, and Associate {@link PreDestroy} with a global - * {@link Closer} object. + * This associates java lifecycle annotations with guice hooks. For example, we invoke {@link PostConstruct} after + * injection, and Associate {@link PreDestroy} with a global {@link Closer} object. * - *

Important

Make sure you create your injector with {@link Stage#PRODUCTION} and execute - * the bound {@link ExecutionList} prior to using any other objects. + *

Important

Make sure you create your injector with {@link Stage#PRODUCTION} and execute the bound + * {@link ExecutionList} prior to using any other objects. * *

* Ex. + * *

  * 
  * 
@@ -89,7 +90,7 @@ public class LifeCycleModule extends AbstractModule { ioExecutor.shutdownNow(); // ScheduledExecutor is defined in an optional module if (scheduledExecutor != null) - scheduledExecutor.shutdownNow(); + scheduledExecutor.shutdownNow(); } }; @@ -103,67 +104,57 @@ public class LifeCycleModule extends AbstractModule { bind(ExecutionList.class).toInstance(list); } + private static final Predicate> isPreDestroy = new Predicate>() { + public boolean apply(Invokable in) { + return in.isAnnotationPresent(PreDestroy.class); + } + }; + + private static final Predicate> isPostConstruct = new Predicate>() { + public boolean apply(Invokable in) { + return in.isAnnotationPresent(PostConstruct.class); + } + }; + protected void bindPostInjectionInvoke(final Closer closer, final ExecutionList list) { bindListener(any(), new TypeListener() { public void hear(TypeLiteral injectableType, TypeEncounter encounter) { - Set methods = newHashSet(); - Class type = injectableType.getRawType(); - while (type != null) { - methods.addAll(asList(type.getDeclaredMethods())); - type = type.getSuperclass(); - } - for (final Method method : methods) { - invokePostConstructMethodAfterInjection(encounter, method); - associatePreDestroyWithCloser(closer, encounter, method); - } - } - - private void associatePreDestroyWithCloser(final Closer closer, TypeEncounter encounter, - final Method method) { - PreDestroy preDestroy = method.getAnnotation(PreDestroy.class); - if (preDestroy != null) { - encounter.register(new InjectionListener() { - public void afterInjection(final I injectee) { - closer.addToClose(new Closeable() { - public void close() throws IOException { - try { - method.invoke(injectee); - } catch (InvocationTargetException ie) { - Throwable e = ie.getTargetException(); - throw new IOException(e.getMessage()); - } catch (IllegalAccessException e) { - throw new IOException(e.getMessage()); - } - } - }); - - } - }); - } - } - - private void invokePostConstructMethodAfterInjection(TypeEncounter encounter, final Method method) { - PostConstruct postConstruct = method.getAnnotation(PostConstruct.class); - if (postConstruct != null) { + Collection> methods = methods(injectableType.getRawType()); + for (final Invokable method : filter(methods, isPostConstruct)) { encounter.register(new InjectionListener() { public void afterInjection(final I injectee) { list.add(new Runnable() { public void run() { - try { - method.invoke(injectee); - } catch (InvocationTargetException ie) { - Throwable e = ie.getTargetException(); - throw propagate(e); - } catch (IllegalAccessException e) { - throw propagate(e); - } + invokeOnInjectee(method, injectee); } + }, sameThreadExecutor()); } }); } + for (final Invokable method : filter(methods, isPreDestroy)) { + encounter.register(new InjectionListener() { + public void afterInjection(final I injectee) { + closer.addToClose(new Closeable() { + public void close() throws IOException { + invokeOnInjectee(method, injectee); + } + }); + } + }); + } } + }); } + private static void invokeOnInjectee(Invokable method, I injectee) { + try { + method.invoke(injectee); + } catch (InvocationTargetException ie) { + throw propagate(ie.getTargetException()); + } catch (IllegalAccessException e) { + throw propagate(e); + } + } } diff --git a/core/src/main/java/org/jclouds/reflect/Reflection2.java b/core/src/main/java/org/jclouds/reflect/Reflection2.java index 7c603abe60..3fca72167e 100644 --- a/core/src/main/java/org/jclouds/reflect/Reflection2.java +++ b/core/src/main/java/org/jclouds/reflect/Reflection2.java @@ -19,23 +19,33 @@ package org.jclouds.reflect; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Iterables.toArray; +import static com.google.common.base.Throwables.propagate; +import static com.google.common.collect.Iterables.tryFind; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; import com.google.common.annotations.Beta; +import com.google.common.base.Function; import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import com.google.common.reflect.Invokable; +import com.google.common.reflect.Parameter; import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.UncheckedExecutionException; /** * Utilities that allow access to {@link Invokable}s with {@link Invokable#getOwnerType() owner types}. @@ -43,14 +53,50 @@ import com.google.common.reflect.TypeToken; * @since 1.6 */ @Beta -public final class Reflection2 { +public class Reflection2 { + + /** + * gets a {@link TypeToken} for the given type. + */ + @SuppressWarnings("unchecked") + public static TypeToken typeToken(Type in) { + return (TypeToken) get(typeTokenForType, checkNotNull(in, "class")); + } /** * gets a {@link TypeToken} for the given class. */ @SuppressWarnings("unchecked") public static TypeToken typeToken(Class in) { - return (TypeToken) typeTokenForClass.apply(checkNotNull(in, "class")); + 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. + * + * @param ownerType + * corresponds to {@link Invokable#getOwnerType()} + */ + @SuppressWarnings("unchecked") + public static Collection> constructors(TypeToken ownerType) { + return Collection.class.cast(get(constructorsForTypeToken, ownerType)); } /** @@ -63,7 +109,7 @@ public final class Reflection2 { */ @SuppressWarnings("unchecked") public static Invokable method(TypeToken ownerType, Method method) { - return (Invokable) methods.apply(new TypeTokenAndMethod(ownerType, method)); + return (Invokable) method(ownerType.getRawType(), method.getName(), method.getParameterTypes()); } /** @@ -79,7 +125,7 @@ public final class Reflection2 { */ @SuppressWarnings("unchecked") public static Invokable method(Class ownerType, String name, Class... parameterTypes) { - return (Invokable) methodForArgs.apply(new TypeTokenNameAndParameterTypes(typeToken(ownerType), name, + return (Invokable) get(methodForParams, new TypeTokenNameAndParameterTypes(typeToken(ownerType), name, parameterTypes)); } @@ -91,51 +137,65 @@ public final class Reflection2 { */ @SuppressWarnings("unchecked") public static Collection> methods(Class ownerType) { - return Collection.class.cast(methodsForTypeToken.apply(typeToken(ownerType)).values()); + return Collection.class.cast(get(methodsForTypeToken, typeToken(ownerType))); } - private static final LoadingCache> methods = CacheBuilder.newBuilder().build( - new CacheLoader>() { - public Invokable load(TypeTokenAndMethod key) { - return key.type.method(key.method); + /** + * this gets all declared constructors, not just public ones. makes them accessible, as well. + */ + private static LoadingCache, Set>> constructorsForTypeToken = CacheBuilder + .newBuilder().build(new CacheLoader, Set>>() { + public Set> load(TypeToken key) { + ImmutableSet.Builder> builder = ImmutableSet.> builder(); + for (Constructor ctor : key.getRawType().getDeclaredConstructors()) { + ctor.setAccessible(true); + builder.add(key.constructor(ctor)); + } + return builder.build(); } }); - private static class TypeTokenAndMethod { - - protected final TypeToken type; - protected final Method method; - - public TypeTokenAndMethod(TypeToken type, Method method) { - this.type = checkNotNull(type, "type"); - this.method = checkNotNull(method, "method"); - } - - public int hashCode() { - return Objects.hashCode(type, method); - } - - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - TypeTokenAndMethod that = TypeTokenAndMethod.class.cast(obj); - return Objects.equal(this.type, that.type) && Objects.equal(this.method, that.method); - } + protected static List> toClasses(ImmutableList params) { + return Lists.transform(params, new Function>() { + public Class apply(Parameter input) { + return input.getType().getRawType(); + } + }); } - private static final LoadingCache, TypeToken> typeTokenForClass = CacheBuilder.newBuilder().build( - new CacheLoader, TypeToken>() { - public TypeToken load(final Class key) { + private static LoadingCache> typeTokenForType = CacheBuilder.newBuilder().build( + new CacheLoader>() { + public TypeToken load(Type key) { return TypeToken.of(key); } }); + private static LoadingCache, TypeToken> typeTokenForClass = CacheBuilder.newBuilder().build( + new CacheLoader, TypeToken>() { + public TypeToken load(Class key) { + return TypeToken.of(key); + } + }); + + private static LoadingCache> constructorForParams = CacheBuilder + .newBuilder().build(new CacheLoader>() { + public Invokable load(final TypeTokenAndParameterTypes key) { + Set> constructors = get(constructorsForTypeToken, key.type); + Optional> constructor = tryFind(constructors, new Predicate>() { + public boolean apply(Invokable input) { + return Objects.equal(toClasses(input.getParameters()), key.parameterTypes); + } + }); + if (constructor.isPresent()) + return constructor.get(); + throw new IllegalArgumentException("no such constructor " + key.toString() + "in: " + constructors); + } + }); + private static class TypeTokenAndParameterTypes { - protected final TypeToken type; - protected final List> parameterTypes; + protected TypeToken type; + protected List> parameterTypes; public TypeTokenAndParameterTypes(TypeToken type, Class... parameterTypes) { this.type = checkNotNull(type, "type"); @@ -160,23 +220,25 @@ public final class Reflection2 { } } - private static final LoadingCache> methodForArgs = CacheBuilder + private static LoadingCache> methodForParams = CacheBuilder .newBuilder().build(new CacheLoader>() { public Invokable load(final TypeTokenNameAndParameterTypes key) { - try { - Method method = key.type.getRawType().getMethod(key.name, toArray(key.parameterTypes, Class.class)); - return methods.apply(new TypeTokenAndMethod(key.type, method)); - } catch (SecurityException e) { - throw new IllegalArgumentException(e.getMessage() + " getting method " + key.toString(), e); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("no such method " + key.toString(), e); - } + Set> methods = get(methodsForTypeToken, key.type); + Optional> method = tryFind(methods, new Predicate>() { + public boolean apply(Invokable input) { + return Objects.equal(input.getName(), key.name) + && Objects.equal(toClasses(input.getParameters()), key.parameterTypes); + } + }); + if (method.isPresent()) + return method.get(); + throw new IllegalArgumentException("no such method " + key.toString() + "in: " + methods); } }); private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes { - private final String name; + private String name; public TypeTokenNameAndParameterTypes(TypeToken type, String name, Class... parameterTypes) { super(type, parameterTypes); @@ -201,14 +263,36 @@ public final class Reflection2 { } } - private static final LoadingCache, Map>> methodsForTypeToken = CacheBuilder - .newBuilder().build(new CacheLoader, Map>>() { - public Map> load(final TypeToken key) { - Builder> builder = ImmutableMap.> builder(); - for (Method method : key.getRawType().getMethods()) - builder.put(method, method(key, method)); + /** + * this gets all declared methods, not just public ones. makes them accessible. Does not include Object methods. + */ + private static LoadingCache, Set>> methodsForTypeToken = CacheBuilder + .newBuilder().build(new CacheLoader, Set>>() { + public Set> load(TypeToken key) { + ImmutableSet.Builder> builder = ImmutableSet.> builder(); + for (TypeToken token : key.getTypes()) { + Class raw = token.getRawType(); + if (raw == Object.class) + continue; + for (Method method : raw.getDeclaredMethods()) { + method.setAccessible(true); + builder.add(key.method(method)); + } + } return builder.build(); } }); + /** + * ensures that exceptions are not doubly-wrapped + */ + private static V get(LoadingCache cache, K key) { + try { + return cache.get(key); + } catch (UncheckedExecutionException e) { + throw propagate(e.getCause()); + } catch (ExecutionException e) { + throw propagate(e.getCause()); + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/rest/config/RestModule.java b/core/src/main/java/org/jclouds/rest/config/RestModule.java index 5112d57891..3ce5c87a19 100644 --- a/core/src/main/java/org/jclouds/rest/config/RestModule.java +++ b/core/src/main/java/org/jclouds/rest/config/RestModule.java @@ -20,7 +20,6 @@ package org.jclouds.rest.config; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.propagate; import static com.google.common.collect.Iterables.toArray; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Maps.transformValues; @@ -32,11 +31,9 @@ import static org.jclouds.rest.config.BinderUtils.bindHttpApi; import static org.jclouds.util.Maps2.transformKeys; import static org.jclouds.util.Predicates2.startsWith; -import java.lang.reflect.Method; import java.net.Proxy; import java.net.URI; import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Named; @@ -47,7 +44,6 @@ import org.jclouds.http.functions.config.SaxParserModule; import org.jclouds.internal.FilterStringsBoundToInjectorByName; import org.jclouds.json.config.GsonModule; import org.jclouds.location.config.LocationModule; -import com.google.common.reflect.Invokable; import org.jclouds.proxy.ProxyForURI; import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.HttpAsyncClient; @@ -55,13 +51,14 @@ import org.jclouds.rest.HttpClient; import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith; import org.jclouds.rest.internal.BlockOnFuture; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.Invokable; import com.google.common.reflect.Parameter; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -80,8 +77,6 @@ public class RestModule extends AbstractModule { this(ImmutableMap., Class> of()); } - private static final Set objectMethods = ImmutableSet.copyOf(Object.class.getMethods()); - public RestModule(Map, Class> sync2Async) { this.sync2Async = sync2Async; } @@ -92,6 +87,11 @@ public class RestModule extends AbstractModule { @Provides @Singleton protected Cache, Invokable> seedKnownSync2AsyncInvokables() { + return seedKnownSync2AsyncInvokables(sync2Async); + } + + @VisibleForTesting + static Cache, Invokable> seedKnownSync2AsyncInvokables(Map, Class> sync2Async) { Cache, Invokable> sync2AsyncBuilder = CacheBuilder.newBuilder().build(); putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder); for (Class s : sync2Async.keySet()) { @@ -103,18 +103,10 @@ public class RestModule extends AbstractModule { // accessible for ClientProvider public static void putInvokables(Class sync, Class async, Cache, Invokable> cache) { for (Invokable invoked : methods(sync)) { - if (!objectMethods.contains(invoked)) { - try { - Invokable delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked)); - checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()), - "invoked %s has different typed exceptions than delegated invoked %s", invoked, delegatedMethod); - invoked.setAccessible(true); - delegatedMethod.setAccessible(true); - cache.put(invoked, delegatedMethod); - } catch (SecurityException e) { - throw propagate(e); - } - } + Invokable delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked)); + checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()), + "invoked %s has different typed exceptions than target %s", invoked, delegatedMethod); + cache.put(invoked, delegatedMethod); } } diff --git a/core/src/main/java/org/jclouds/rest/config/SetCaller.java b/core/src/main/java/org/jclouds/rest/config/SetCaller.java index 65acb46789..ebb94f0e35 100644 --- a/core/src/main/java/org/jclouds/rest/config/SetCaller.java +++ b/core/src/main/java/org/jclouds/rest/config/SetCaller.java @@ -19,14 +19,13 @@ package org.jclouds.rest.config; import static com.google.common.base.Preconditions.checkState; +import static com.google.inject.name.Names.named; import org.jclouds.reflect.Invocation; import com.google.inject.AbstractModule; import com.google.inject.Key; import com.google.inject.Provider; -import com.google.inject.TypeLiteral; -import com.google.inject.name.Names; /** * Allows the provider to supply a value set in a threadlocal. @@ -55,8 +54,7 @@ public class SetCaller { } } - private static final Key CALLER_INVOCATION = Key.get(new TypeLiteral() { - }, Names.named("caller")); + private static final Key CALLER_INVOCATION = Key.get(Invocation.class, named("caller")); class CallerInvocationProvider implements Provider { @Override diff --git a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java index 4255b5225a..b1087cc666 100644 --- a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java +++ b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java @@ -19,12 +19,15 @@ package org.jclouds.reflect; import static com.google.common.base.Functions.toStringFunction; +import static org.jclouds.reflect.Reflection2.constructors; import static org.jclouds.reflect.Reflection2.method; import static org.jclouds.reflect.Reflection2.methods; import static org.jclouds.reflect.Reflection2.typeToken; import static org.testng.Assert.assertEquals; +import java.util.HashSet; import java.util.Set; +import java.util.SortedSet; import org.testng.annotations.Test; @@ -32,7 +35,9 @@ import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.Invokable; +import com.google.common.reflect.Parameter; import com.google.common.reflect.TypeToken; +import com.google.inject.TypeLiteral; /** * @@ -41,7 +46,31 @@ import com.google.common.reflect.TypeToken; @Test public class Reflection2Test { - public void testTypeToken() { + /** + * useful when converting to and from type literals from other libraries such as guice and gson. + */ + public void testTypeTokenForType() { + TypeLiteral> guice = new TypeLiteral>() { + }; + + assertEquals(typeToken(guice.getType()), new TypeToken>() { + private static final long serialVersionUID = 1L; + }); + } + + public void testConstructors() { + Set ctorParams = FluentIterable.from(constructors(TypeToken.of(HashSet.class))) + .transform(new Function, Iterable>() { + public Iterable apply(Invokable input) { + return input.getParameters(); + } + }).transform(toStringFunction()).toSet(); + + assertEquals(ctorParams, ImmutableSet.of("[]", "[java.util.Collection arg0]", + "[int arg0, float arg1]", "[int arg0]", "[int arg0, float arg1, boolean arg2]")); + } + + public void testTypeTokenForClass() { assertEquals(typeToken(String.class), TypeToken.of(String.class)); } @@ -65,15 +94,28 @@ public class Reflection2Test { assertEquals(methodInSuper.getParameters().get(0).getType().getRawType(), Object.class); } - public void testMethods() { - Set methodNames = FluentIterable.from(methods(Set.class)) - .transform(new Function, String>() { - public String apply(Invokable input) { - return input.getName(); - } - }).transform(toStringFunction()).toSet(); + ImmutableSet setMethods = ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains", + "addAll", "size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll"); - assertEquals(methodNames, ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains", "addAll", - "size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll")); + public void testMethods() { + Set methodNames = FluentIterable.from(methods(Set.class)).transform(invokableToName) + .transform(toStringFunction()).toSet(); + + assertEquals(methodNames, setMethods); } + + public void testMethodsSubClass() { + Set methodNames = FluentIterable.from(methods(SortedSet.class)).transform(invokableToName) + .transform(toStringFunction()).toSet(); + + assertEquals(methodNames, + ImmutableSet.builder().add("comparator", "last", "first", "subSet", "headSet", "tailSet") + .addAll(setMethods).build()); + } + + static final Function, String> invokableToName = new Function, String>() { + public String apply(Invokable input) { + return input.getName(); + } + }; } diff --git a/core/src/test/java/org/jclouds/rest/config/RestModuleTest.java b/core/src/test/java/org/jclouds/rest/config/RestModuleTest.java new file mode 100644 index 0000000000..0a48f1e8c1 --- /dev/null +++ b/core/src/test/java/org/jclouds/rest/config/RestModuleTest.java @@ -0,0 +1,120 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.rest.config; + +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Maps.filterEntries; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; + +import org.jclouds.rest.HttpAsyncClient; +import org.jclouds.rest.HttpClient; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.Invokable; +import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit") +public class RestModuleTest { + static interface Sync { + String get(); + } + + private static interface Async { + ListenableFuture get(); + } + + public void testPutInvokablesWhenInterfacesMatch() { + Cache, Invokable> cache = CacheBuilder.newBuilder().build(); + RestModule.putInvokables(Sync.class, Async.class, cache); + + assertEquals(cache.size(), 1); + + Invokable sync = cache.asMap().keySet().iterator().next(); + assertEquals(sync.getOwnerType().getRawType(), Sync.class); + assertEquals(sync.getName(), "get"); + assertEquals(sync.getReturnType(), TypeToken.of(String.class)); + + Invokable async = cache.getIfPresent(sync); + assertEquals(async.getOwnerType().getRawType(), Async.class); + assertEquals(async.getName(), "get"); + assertEquals(async.getReturnType(), new TypeToken>() { + private static final long serialVersionUID = 1L; + }); + } + + private static interface AsyncWithException { + ListenableFuture get() throws IOException; + } + + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* has different typed exceptions than target .*") + public void testPutInvokablesWhenInterfacesMatchExceptExceptions() { + Cache, Invokable> cache = CacheBuilder.newBuilder().build(); + RestModule.putInvokables(Sync.class, AsyncWithException.class, cache); + } + + private static interface AsyncWithMisnamedMethod { + ListenableFuture got(); + } + + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "no such method .*") + public void testPutInvokablesWhenTargetMethodNotFound() { + Cache, Invokable> cache = CacheBuilder.newBuilder().build(); + RestModule.putInvokables(Sync.class, AsyncWithMisnamedMethod.class, cache); + } + + static final Predicate, Invokable>> isHttpInvokable = new Predicate, Invokable>>() { + public boolean apply(Map.Entry, Invokable> in) { + return in.getKey().getOwnerType().getRawType().equals(HttpClient.class) + && in.getValue().getOwnerType().getRawType().equals(HttpAsyncClient.class); + } + }; + + public void testSeedKnownSync2AsyncIncludesHttpClientByDefault() { + Map, Invokable> cache = RestModule.seedKnownSync2AsyncInvokables( + ImmutableMap., Class> of()).asMap(); + + assertEquals(cache.size(), 6); + assertEquals(filterEntries(cache, isHttpInvokable), cache); + } + + public void testSeedKnownSync2AsyncInvokablesInterfacesMatch() { + Map, Invokable> cache = RestModule.seedKnownSync2AsyncInvokables( + ImmutableMap., Class> of(Sync.class, Async.class)).asMap(); + + assertEquals(cache.size(), 7); + + cache = filterEntries(cache, not(isHttpInvokable)); + + assertEquals(cache.size(), 1); + } + +} diff --git a/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java b/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java index d852cc91ef..c4a9829877 100644 --- a/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java +++ b/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java @@ -22,8 +22,9 @@ package org.jclouds.abiquo.domain; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.transform; +import static org.jclouds.reflect.Reflection2.constructor; -import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.Collection; import java.util.List; @@ -44,6 +45,7 @@ import com.abiquo.server.core.task.TaskDto; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.reflect.Invokable; /** * This class is used to decorate transport objects with high level @@ -103,13 +105,12 @@ public abstract class DomainWrapper { } try { - Constructor cons = wrapperClass.getDeclaredConstructor(RestContext.class, target.getClass()); - if (!cons.isAccessible()) { - cons.setAccessible(true); - } - return cons.newInstance(context, target); - } catch (Exception ex) { - throw new WrapperException(wrapperClass, target, ex); + Invokable cons = constructor(wrapperClass, RestContext.class, target.getClass()); + return cons.invoke(null, context, target); + } catch (InvocationTargetException e) { + throw new WrapperException(wrapperClass, target, e.getTargetException()); + } catch (IllegalAccessException e) { + throw new WrapperException(wrapperClass, target, e); } }