/** * 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<TypeToken<?>, Set<Invokable<?, ?>>> originalMethodsForTypeToken; @BeforeClass public void backupMethodsForTypeToken() { originalMethodsForTypeToken = getStaticField(Reflection2.class, "methodsForTypeToken"); } private static class ParentWithMethod { @SuppressWarnings("unused") public Set<Object> method() { return null; } } private static class ChildOverridesAndNarrowsMethod extends ParentWithMethod { @Override public SortedSet<Object> 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.<Invokable<?, ?>>of(Invokable.from(methods[0]), Invokable.from(methods[1])))); // getMethod returns the method with the *narrowest* return type if one exists Invokable<?, Object> mostSpecificMethod = Invokable.from(ChildOverridesAndNarrowsMethod.class.getMethod("method")); assertEquals(Reflection2.method(ChildOverridesAndNarrowsMethod.class, "method"), mostSpecificMethod); // now the opposite order Reflection2OverriddenMethodTest.<LoadingCache<?, ?>> getStaticField(Reflection2.class, "methodForParams").invalidateAll(); setStaticField(Reflection2.class, "methodsForTypeToken", keyOverridingCache( TypeToken.of(ChildOverridesAndNarrowsMethod.class), ImmutableSet.<Invokable<?, ?>>of(Invokable.from(methods[1]), Invokable.from(methods[0])))); assertEquals(Reflection2.method(ChildOverridesAndNarrowsMethod.class, "method"), mostSpecificMethod); } private LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> keyOverridingCache( final TypeToken<?> overriddenKey, final Set<Invokable<?, ?>> value) { return new SimpleForwardingLoadingCache<TypeToken<?>, Set<Invokable<?, ?>>>(originalMethodsForTypeToken) { @Override public Set<Invokable<?, ?>> get(TypeToken<?> key) throws ExecutionException { return (key.equals(overriddenKey) ? value : super.get(key)); } }; } @SuppressWarnings("unchecked") private static <T> 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); } }