/* * Copyright (C) 2012 The Guava Authors * * Licensed 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 com.google.common.reflect; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; import java.util.Collections; import javax.annotation.Nullable; import junit.framework.TestCase; /** * Unit tests for {@link Invokable}. * * @author Ben Yu */ @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? public class InvokableTest extends TestCase { public void testConstructor_returnType() throws Exception { assertEquals(Prepender.class, Prepender.constructor().getReturnType().getType()); } private static class WithConstructorAndTypeParameter<T> { @SuppressWarnings("unused") // by reflection <X> WithConstructorAndTypeParameter() {} } public void testConstructor_returnType_hasTypeParameter() throws Exception { @SuppressWarnings("rawtypes") // Foo.class for Foo<T> is always raw type Class<WithConstructorAndTypeParameter> type = WithConstructorAndTypeParameter.class; @SuppressWarnings("rawtypes") // Foo.class Constructor<WithConstructorAndTypeParameter> constructor = type.getDeclaredConstructor(); Invokable<?, ?> factory = Invokable.from(constructor); assertThat(factory.getTypeParameters()).hasLength(2); assertEquals(type.getTypeParameters()[0], factory.getTypeParameters()[0]); assertEquals(constructor.getTypeParameters()[0], factory.getTypeParameters()[1]); ParameterizedType returnType = (ParameterizedType) factory.getReturnType().getType(); assertEquals(type, returnType.getRawType()); assertEquals(ImmutableList.copyOf(type.getTypeParameters()), ImmutableList.copyOf(returnType.getActualTypeArguments())); } public void testConstructor_exceptionTypes() throws Exception { assertEquals(ImmutableList.of(TypeToken.of(NullPointerException.class)), Prepender.constructor(String.class, int.class).getExceptionTypes()); } public void testConstructor_typeParameters() throws Exception { TypeVariable<?>[] variables = Prepender.constructor().getTypeParameters(); assertThat(variables).hasLength(1); assertEquals("A", variables[0].getName()); } public void testConstructor_parameters() throws Exception { Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class); ImmutableList<Parameter> parameters = delegate.getParameters(); assertEquals(2, parameters.size()); assertEquals(String.class, parameters.get(0).getType().getType()); assertTrue(parameters.get(0).isAnnotationPresent(NotBlank.class)); assertEquals(int.class, parameters.get(1).getType().getType()); assertFalse(parameters.get(1).isAnnotationPresent(NotBlank.class)); new EqualsTester() .addEqualityGroup(parameters.get(0)) .addEqualityGroup(parameters.get(1)) .testEquals(); } public void testConstructor_call() throws Exception { Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class); Prepender prepender = delegate.invoke(null, "a", 1); assertEquals("a", prepender.prefix); assertEquals(1, prepender.times); } public void testConstructor_returning() throws Exception { Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class) .returning(Prepender.class); Prepender prepender = delegate.invoke(null, "a", 1); assertEquals("a", prepender.prefix); assertEquals(1, prepender.times); } public void testConstructor_invalidReturning() throws Exception { Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class); try { delegate.returning(SubPrepender.class); fail(); } catch (IllegalArgumentException expected) {} } public void testStaticMethod_returnType() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class); assertEquals(new TypeToken<Iterable<String>>() {}, delegate.getReturnType()); } public void testStaticMethod_exceptionTypes() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class); assertEquals(ImmutableList.of(), delegate.getExceptionTypes()); } public void testStaticMethod_typeParameters() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class); TypeVariable<?>[] variables = delegate.getTypeParameters(); assertThat(variables).hasLength(1); assertEquals("T", variables[0].getName()); } public void testStaticMethod_parameters() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class); ImmutableList<Parameter> parameters = delegate.getParameters(); assertEquals(2, parameters.size()); assertEquals(String.class, parameters.get(0).getType().getType()); assertTrue(parameters.get(0).isAnnotationPresent(NotBlank.class)); assertEquals(new TypeToken<Iterable<String>>() {}, parameters.get(1).getType()); assertFalse(parameters.get(1).isAnnotationPresent(NotBlank.class)); new EqualsTester() .addEqualityGroup(parameters.get(0)) .addEqualityGroup(parameters.get(1)) .testEquals(); } public void testStaticMethod_call() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class); @SuppressWarnings("unchecked") // prepend() returns Iterable<String> Iterable<String> result = (Iterable<String>) delegate.invoke(null, "a", ImmutableList.of("b", "c")); assertEquals(ImmutableList.of("a", "b", "c"), ImmutableList.copyOf(result)); } public void testStaticMethod_returning() throws Exception { Invokable<?, Iterable<String>> delegate = Prepender.method( "prepend", String.class, Iterable.class) .returning(new TypeToken<Iterable<String>>() {}); assertEquals(new TypeToken<Iterable<String>>() {}, delegate.getReturnType()); Iterable<String> result = delegate.invoke(null, "a", ImmutableList.of("b", "c")); assertEquals(ImmutableList.of("a", "b", "c"), ImmutableList.copyOf(result)); } public void testStaticMethod_returningRawType() throws Exception { @SuppressWarnings("rawtypes") // the purpose is to test raw type Invokable<?, Iterable> delegate = Prepender.method( "prepend", String.class, Iterable.class) .returning(Iterable.class); assertEquals(new TypeToken<Iterable<String>>() {}, delegate.getReturnType()); @SuppressWarnings("unchecked") // prepend() returns Iterable<String> Iterable<String> result = delegate.invoke(null, "a", ImmutableList.of("b", "c")); assertEquals(ImmutableList.of("a", "b", "c"), ImmutableList.copyOf(result)); } public void testStaticMethod_invalidReturning() throws Exception { Invokable<?, Object> delegate = Prepender.method("prepend", String.class, Iterable.class); try { delegate.returning(new TypeToken<Iterable<Integer>>() {}); fail(); } catch (IllegalArgumentException expected) {} } public void testInstanceMethod_returnType() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class); assertEquals(new TypeToken<Iterable<String>>() {}, delegate.getReturnType()); } public void testInstanceMethod_exceptionTypes() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class); assertEquals( ImmutableList.of( TypeToken.of(IllegalArgumentException.class), TypeToken.of(NullPointerException.class)), delegate.getExceptionTypes()); } public void testInstanceMethod_typeParameters() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class); assertThat(delegate.getTypeParameters()).isEmpty(); } public void testInstanceMethod_parameters() throws Exception { Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class); ImmutableList<Parameter> parameters = delegate.getParameters(); assertEquals(1, parameters.size()); assertEquals(new TypeToken<Iterable<String>>() {}, parameters.get(0).getType()); assertThat(parameters.get(0).getAnnotations()).isEmpty(); new EqualsTester().addEqualityGroup(parameters.get(0)).testEquals(); } public void testInstanceMethod_call() throws Exception { Invokable<Prepender, ?> delegate = Prepender.method("prepend", Iterable.class); @SuppressWarnings("unchecked") // prepend() returns Iterable<String> Iterable<String> result = (Iterable<String>) delegate.invoke(new Prepender("a", 2), ImmutableList.of("b", "c")); assertEquals(ImmutableList.of("a", "a", "b", "c"), ImmutableList.copyOf(result)); } public void testInstanceMethod_returning() throws Exception { Invokable<Prepender, Iterable<String>> delegate = Prepender.method( "prepend", Iterable.class) .returning(new TypeToken<Iterable<String>>() {}); assertEquals(new TypeToken<Iterable<String>>() {}, delegate.getReturnType()); Iterable<String> result = delegate.invoke(new Prepender("a", 2), ImmutableList.of("b", "c")); assertEquals(ImmutableList.of("a", "a", "b", "c"), ImmutableList.copyOf(result)); } public void testInstanceMethod_returningRawType() throws Exception { @SuppressWarnings("rawtypes") // the purpose is to test raw type Invokable<Prepender, Iterable> delegate = Prepender.method("prepend", Iterable.class) .returning(Iterable.class); assertEquals(new TypeToken<Iterable<String>>() {}, delegate.getReturnType()); @SuppressWarnings("unchecked") // prepend() returns Iterable<String> Iterable<String> result = delegate.invoke( new Prepender("a", 2), ImmutableList.of("b", "c")); assertEquals(ImmutableList.of("a", "a", "b", "c"), ImmutableList.copyOf(result)); } public void testInstanceMethod_invalidReturning() throws Exception { Invokable<?, Object> delegate = Prepender.method("prepend", Iterable.class); try { delegate.returning(new TypeToken<Iterable<Integer>>() {}); fail(); } catch (IllegalArgumentException expected) {} } public void testPrivateInstanceMethod_isOverridable() throws Exception { Invokable<?, ?> delegate = Prepender.method("privateMethod"); assertTrue(delegate.isPrivate()); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); } public void testPrivateFinalInstanceMethod_isOverridable() throws Exception { Invokable<?, ?> delegate = Prepender.method("privateFinalMethod"); assertTrue(delegate.isPrivate()); assertTrue(delegate.isFinal()); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); } public void testStaticMethod_isOverridable() throws Exception { Invokable<?, ?> delegate = Prepender.method("staticMethod"); assertTrue(delegate.isStatic()); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); } public void testStaticFinalMethod_isFinal() throws Exception { Invokable<?, ?> delegate = Prepender.method("staticFinalMethod"); assertTrue(delegate.isStatic()); assertTrue(delegate.isFinal()); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); } static class Foo {} public void testConstructor_isOverridablel() throws Exception { Invokable<?, ?> delegate = Invokable.from(Foo.class.getDeclaredConstructor()); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); } public void testMethod_isVarArgs() throws Exception { Invokable<?, ?> delegate = Prepender.method("privateVarArgsMethod", String[].class); assertTrue(delegate.isVarArgs()); } public void testConstructor_isVarArgs() throws Exception { Invokable<?, ?> delegate = Prepender.constructor(String[].class); assertTrue(delegate.isVarArgs()); } public void testGetOwnerType_constructor() throws Exception { Invokable<String, String> invokable = Invokable.from(String.class.getConstructor()); assertEquals(TypeToken.of(String.class), invokable.getOwnerType()); } public void testGetOwnerType_method() throws Exception { Invokable<?, ?> invokable = Invokable.from(String.class.getMethod("length")); assertEquals(TypeToken.of(String.class), invokable.getOwnerType()); } private static final class FinalClass { @SuppressWarnings("unused") // used by reflection void notFinalMethod() {} } public void testNonFinalMethodInFinalClass_isOverridable() throws Exception { Invokable<?, ?> delegate = Invokable.from( FinalClass.class.getDeclaredMethod("notFinalMethod")); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); } private class InnerWithDefaultConstructor { class NestedInner {} } public void testInnerClassDefaultConstructor() { Constructor<?> constructor = InnerWithDefaultConstructor.class.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } public void testNestedInnerClassDefaultConstructor() { Constructor<?> constructor = InnerWithDefaultConstructor.NestedInner.class.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } private class InnerWithOneParameterConstructor { @SuppressWarnings("unused") // called by reflection public InnerWithOneParameterConstructor(String s) {} } public void testInnerClassWithOneParameterConstructor() { Constructor<?> constructor = InnerWithOneParameterConstructor.class.getDeclaredConstructors()[0]; Invokable<?, ?> invokable = Invokable.from(constructor); assertEquals(1, invokable.getParameters().size()); assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType()); } private class InnerWithAnnotatedConstructorParameter { @SuppressWarnings("unused") // called by reflection InnerWithAnnotatedConstructorParameter(@Nullable String s) {} } public void testInnerClassWithAnnotatedConstructorParameter() { Constructor<?> constructor = InnerWithAnnotatedConstructorParameter.class.getDeclaredConstructors() [0]; Invokable<?, ?> invokable = Invokable.from(constructor); assertEquals(1, invokable.getParameters().size()); assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType()); } private class InnerWithGenericConstructorParameter { @SuppressWarnings("unused") // called by reflection InnerWithGenericConstructorParameter(Iterable<String> it, String s) {} } public void testInnerClassWithGenericConstructorParameter() { Constructor<?> constructor = InnerWithGenericConstructorParameter.class.getDeclaredConstructors() [0]; Invokable<?, ?> invokable = Invokable.from(constructor); assertEquals(2, invokable.getParameters().size()); assertEquals(new TypeToken<Iterable<String>>() {}, invokable.getParameters().get(0).getType()); assertEquals(TypeToken.of(String.class), invokable.getParameters().get(1).getType()); } public void testAnonymousClassDefaultConstructor() { final int i = 1; final String s = "hello world"; Class<?> anonymous = new Runnable() { @Override public void run() { System.out.println(s + i); } }.getClass(); Constructor<?> constructor = anonymous.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } public void testAnonymousClassWithTwoParametersConstructor() { abstract class Base { @SuppressWarnings("unused") // called by reflection Base(String s, int i) {} } Class<?> anonymous = new Base("test", 0) {}.getClass(); Constructor<?> constructor = anonymous.getDeclaredConstructors() [0]; assertEquals(2, Invokable.from(constructor).getParameters().size()); } public void testLocalClassDefaultConstructor() { final int i = 1; final String s = "hello world"; class LocalWithDefaultConstructor implements Runnable { @Override public void run() { System.out.println(s + i); } } Constructor<?> constructor = LocalWithDefaultConstructor.class.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } public void testStaticAnonymousClassDefaultConstructor() throws Exception { doTestStaticAnonymousClassDefaultConstructor(); } private static void doTestStaticAnonymousClassDefaultConstructor() { final int i = 1; final String s = "hello world"; Class<?> anonymous = new Runnable() { @Override public void run() { System.out.println(s + i); } }.getClass(); Constructor<?> constructor = anonymous.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } public void testAnonymousClassInConstructor() { new AnonymousClassInConstructor(); } private static class AnonymousClassInConstructor { AnonymousClassInConstructor() { final int i = 1; final String s = "hello world"; Class<?> anonymous = new Runnable() { @Override public void run() { System.out.println(s + i); } }.getClass(); Constructor<?> constructor = anonymous.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } } public void testLocalClassInInstanceInitializer() { new LocalClassInInstanceInitializer(); } private static class LocalClassInInstanceInitializer { { class Local {} Constructor<?> constructor = Local.class.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } } public void testLocalClassInStaticInitializer() { new LocalClassInStaticInitializer(); } private static class LocalClassInStaticInitializer { static { class Local {} Constructor<?> constructor = Local.class.getDeclaredConstructors() [0]; assertEquals(0, Invokable.from(constructor).getParameters().size()); } } public void testLocalClassWithSeeminglyHiddenThisInStaticInitializer_BUG() { new LocalClassWithSeeminglyHiddenThisInStaticInitializer(); } /** * This class demonstrates a bug in getParameters() when the local class is inside static * initializer. */ private static class LocalClassWithSeeminglyHiddenThisInStaticInitializer { static { class Local { @SuppressWarnings("unused") // through reflection Local(LocalClassWithSeeminglyHiddenThisInStaticInitializer outer) {} } Constructor<?> constructor = Local.class.getDeclaredConstructors() [0]; int miscalculated = 0; assertEquals(miscalculated, Invokable.from(constructor).getParameters().size()); } } public void testLocalClassWithOneParameterConstructor() throws Exception { final int i = 1; final String s = "hello world"; class LocalWithOneParameterConstructor { @SuppressWarnings("unused") // called by reflection public LocalWithOneParameterConstructor(String x) { System.out.println(s + i); } } Constructor<?> constructor = LocalWithOneParameterConstructor.class.getDeclaredConstructors()[0]; Invokable<?, ?> invokable = Invokable.from(constructor); assertEquals(1, invokable.getParameters().size()); assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType()); } public void testLocalClassWithAnnotatedConstructorParameter() throws Exception { class LocalWithAnnotatedConstructorParameter { @SuppressWarnings("unused") // called by reflection LocalWithAnnotatedConstructorParameter(@Nullable String s) {} } Constructor<?> constructor = LocalWithAnnotatedConstructorParameter.class.getDeclaredConstructors() [0]; Invokable<?, ?> invokable = Invokable.from(constructor); assertEquals(1, invokable.getParameters().size()); assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType()); } public void testLocalClassWithGenericConstructorParameter() throws Exception { class LocalWithGenericConstructorParameter { @SuppressWarnings("unused") // called by reflection LocalWithGenericConstructorParameter(Iterable<String> it, String s) {} } Constructor<?> constructor = LocalWithGenericConstructorParameter.class.getDeclaredConstructors() [0]; Invokable<?, ?> invokable = Invokable.from(constructor); assertEquals(2, invokable.getParameters().size()); assertEquals(new TypeToken<Iterable<String>>() {}, invokable.getParameters().get(0).getType()); assertEquals(TypeToken.of(String.class), invokable.getParameters().get(1).getType()); } public void testEquals() throws Exception { new EqualsTester() .addEqualityGroup(Prepender.constructor(), Prepender.constructor()) .addEqualityGroup(Prepender.constructor(String.class, int.class)) .addEqualityGroup(Prepender.method("privateMethod"), Prepender.method("privateMethod")) .addEqualityGroup(Prepender.method("privateFinalMethod")) .testEquals(); } public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Invokable.class); new NullPointerTester().testAllPublicInstanceMethods(Prepender.method("staticMethod")); } @Retention(RetentionPolicy.RUNTIME) private @interface NotBlank {} /** Class for testing constructor, static method and instance method. */ @SuppressWarnings("unused") // most are called by reflection private static class Prepender { private final String prefix; private final int times; Prepender(@NotBlank String prefix, int times) throws NullPointerException { this.prefix = prefix; this.times = times; } Prepender(String... varargs) { this(null, 0); } // just for testing private <A> Prepender() { this(null, 0); } static <T> Iterable<String> prepend(@NotBlank String first, Iterable<String> tail) { return Iterables.concat(ImmutableList.of(first), tail); } Iterable<String> prepend(Iterable<String> tail) throws IllegalArgumentException, NullPointerException { return Iterables.concat(Collections.nCopies(times, prefix), tail); } static Invokable<?, Prepender> constructor(Class<?>... parameterTypes) throws Exception { Constructor<Prepender> constructor = Prepender.class.getDeclaredConstructor(parameterTypes); return Invokable.from(constructor); } static Invokable<Prepender, Object> method(String name, Class<?>... parameterTypes) { try { Method method = Prepender.class.getDeclaredMethod(name, parameterTypes); @SuppressWarnings("unchecked") // The method is from Prepender. Invokable<Prepender, Object> invokable = (Invokable<Prepender, Object>) Invokable.from(method); return invokable; } catch (NoSuchMethodException e) { throw new IllegalArgumentException(e); } } private void privateMethod() {} private final void privateFinalMethod() {} static void staticMethod() {} static final void staticFinalMethod() {} private void privateVarArgsMethod(String... varargs) {} } private static class SubPrepender extends Prepender { @SuppressWarnings("unused") // needed to satisfy compiler, never called public SubPrepender() throws NullPointerException { throw new AssertionError(); } } }