diff --git a/core/src/test/java/org/jclouds/reflect/Reflection2OverriddenMethodTest.java b/core/src/test/java/org/jclouds/reflect/Reflection2OverriddenMethodTest.java new file mode 100644 index 0000000000..86928d73dd --- /dev/null +++ b/core/src/test/java/org/jclouds/reflect/Reflection2OverriddenMethodTest.java @@ -0,0 +1,140 @@ +/** + * 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.reflect; + +import static com.google.common.base.Throwables.propagate; +import static org.testng.Assert.assertEquals; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.ExecutionException; + +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.common.cache.ForwardingLoadingCache.SimpleForwardingLoadingCache; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.Invokable; +import com.google.common.reflect.TypeToken; + +/** + * Tests {@link Reflection2#method(Class, String, Class...)} where + * the method overrides one in parent classes and so bridge methods + * need to be ignored. + * + * This test has been separated out into a separate class as it requires + * reflection to modify the internal static caches of {@code Reflection2} + * and needs to perform cleanup to avoid affecting other tests. + * + * @author Andrew Phillips + */ +@Test +public class Reflection2OverriddenMethodTest { + private LoadingCache, Set>> originalMethodsForTypeToken; + + @BeforeClass + public void backupMethodsForTypeToken() { + originalMethodsForTypeToken = getStaticField(Reflection2.class, "methodsForTypeToken"); + } + + private static class ParentWithMethod { + @SuppressWarnings("unused") + public Set method() { + return null; + } + } + + private static class ChildOverridesAndNarrowsMethod extends ParentWithMethod { + @Override + public SortedSet method() { + return null; + } + } + + public void testOverriddenMethodWithNarrowedReturnType() throws NoSuchMethodException { + // expecting two methods: the declared "method" and the bridge version with return type Set + final Method[] methods = ChildOverridesAndNarrowsMethod.class.getDeclaredMethods(); + /* + * Force Reflection2.methodsForTypeToken to reflect the fact that the declared methods + * of a class are not returned in any particular order. + */ + setStaticField(Reflection2.class, "methodsForTypeToken", keyOverridingCache( + TypeToken.of(ChildOverridesAndNarrowsMethod.class), + ImmutableSet.>of(Invokable.from(methods[0]), Invokable.from(methods[1])))); + // getMethod returns the method with the *narrowest* return type if one exists + Invokable mostSpecificMethod = Invokable.from(ChildOverridesAndNarrowsMethod.class.getMethod("method")); + assertEquals(Reflection2.method(ChildOverridesAndNarrowsMethod.class, "method"), + mostSpecificMethod); + + // now the opposite order + Reflection2OverriddenMethodTest.> + getStaticField(Reflection2.class, "methodForParams").invalidateAll(); + setStaticField(Reflection2.class, "methodsForTypeToken", keyOverridingCache( + TypeToken.of(ChildOverridesAndNarrowsMethod.class), + ImmutableSet.>of(Invokable.from(methods[1]), Invokable.from(methods[0])))); + assertEquals(Reflection2.method(ChildOverridesAndNarrowsMethod.class, "method"), + mostSpecificMethod); + } + + private LoadingCache, Set>> keyOverridingCache( + final TypeToken overriddenKey, final Set> value) { + return new SimpleForwardingLoadingCache, Set>>(originalMethodsForTypeToken) { + @Override + public Set> get(TypeToken key) throws ExecutionException { + return (key.equals(overriddenKey) ? value : super.get(key)); + } + }; + } + + @SuppressWarnings("unchecked") + private static T getStaticField(Class declaringClass, String fieldName) { + try { + Field field = declaringClass.getDeclaredField(fieldName); + field.setAccessible(true); + // static field + return (T) field.get(null); + } catch (NoSuchFieldException exception) { + throw propagate(exception); + } catch (IllegalAccessException exception) { + throw propagate(exception); + } + } + + private static void setStaticField(Class declaringClass, String fieldName, Object value) { + try { + Field field = declaringClass.getDeclaredField(fieldName); + field.setAccessible(true); + // static field + field.set(null, value); + } catch (NoSuchFieldException exception) { + throw propagate(exception); + } catch (IllegalAccessException exception) { + throw propagate(exception); + } + } + + @AfterClass(alwaysRun = true) + public void restoreMethodsForTypeToken() { + setStaticField(Reflection2.class, "methodsForTypeToken", originalMethodsForTypeToken); + } +} diff --git a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java index fcb67ad3c7..b1087cc666 100644 --- a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java +++ b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java @@ -19,17 +19,12 @@ package org.jclouds.reflect; import static com.google.common.base.Functions.toStringFunction; -import static com.google.common.base.Throwables.propagate; -import static java.lang.String.format; 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 static org.testng.Assert.fail; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; @@ -37,9 +32,6 @@ import java.util.SortedSet; import org.testng.annotations.Test; import com.google.common.base.Function; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.Invokable; @@ -121,97 +113,9 @@ public class Reflection2Test { .addAll(setMethods).build()); } - public void testOverriddenMethodWithNarrowedReturnType() throws NoSuchMethodException { - LoadingCache, Set>> methodsForTypeTokenBackup = - getStaticField(Reflection2.class, "methodsForTypeToken"); - // expecting two methods: the declared "method" and the bridge version with return type Set - final Method[] methods = ChildOverridesAndNarrowsMethod.class.getDeclaredMethods(); - try { - /* - * Force Reflection2.methodsForTypeToken to reflect the fact that the declared methods - * of a class are not returned in any particular order. - */ - setStaticField(Reflection2.class, "methodsForTypeToken", CacheBuilder.newBuilder().build( - new CacheLoader, Set>>() { - @Override - public Set> load(TypeToken key) throws Exception { - if (!key.equals(TypeToken.of(ChildOverridesAndNarrowsMethod.class))) { - fail(format("expected only key %s to be requested, but was %s", - TypeToken.of(ChildOverridesAndNarrowsMethod.class), key)); - } - return ImmutableSet.>of(Invokable.from(methods[0]), Invokable.from(methods[1])); - } - })); - // getMethod returns the method with the *narrowest* return type if one exists - assertEquals(Reflection2.method(ChildOverridesAndNarrowsMethod.class, "method"), - Invokable.from(ChildOverridesAndNarrowsMethod.class.getMethod("method"))); - - // now the opposite order - Reflection2Test.> - getStaticField(Reflection2.class, "methodForParams").invalidateAll(); - setStaticField(Reflection2.class, "methodsForTypeToken", CacheBuilder.newBuilder().build( - new CacheLoader, Set>>() { - @Override - public Set> load(TypeToken key) throws Exception { - if (!key.equals(TypeToken.of(ChildOverridesAndNarrowsMethod.class))) { - fail(format("expected only key %s to be requested, but was %s", - TypeToken.of(ChildOverridesAndNarrowsMethod.class), key)); - } - return ImmutableSet.>of(Invokable.from(methods[1]), Invokable.from(methods[0])); - } - })); - // getMethod returns the method with the *narrowest* return type if one exists - assertEquals(Reflection2.method(ChildOverridesAndNarrowsMethod.class, "method"), - Invokable.from(ChildOverridesAndNarrowsMethod.class.getMethod("method"))); - } finally { - setStaticField(Reflection2.class, "methodsForTypeToken", methodsForTypeTokenBackup); - } - } - - @SuppressWarnings("unchecked") - private static T getStaticField(Class declaringClass, String fieldName) { - try { - Field field = declaringClass.getDeclaredField(fieldName); - field.setAccessible(true); - // static field - return (T) field.get(null); - } catch (NoSuchFieldException exception) { - throw propagate(exception); - } catch (IllegalAccessException exception) { - throw propagate(exception); - } - } - - private static void setStaticField(Class declaringClass, String fieldName, Object value) { - try { - Field field = declaringClass.getDeclaredField(fieldName); - field.setAccessible(true); - // static field - field.set(null, value); - } catch (NoSuchFieldException exception) { - throw propagate(exception); - } catch (IllegalAccessException exception) { - throw propagate(exception); - } - } - static final Function, String> invokableToName = new Function, String>() { public String apply(Invokable input) { return input.getName(); } }; - - private static class ParentWithMethod { - @SuppressWarnings("unused") - public Set method() { - return null; - } - } - - private static class ChildOverridesAndNarrowsMethod extends ParentWithMethod { - @Override - public SortedSet method() { - return null; - } - } }