1 /* 2 * Copyright (C) 2007 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import com.google.common.base.Function; 20 import com.google.common.base.Joiner; 21 22 import junit.framework.TestCase; 23 24 import java.lang.reflect.Array; 25 import java.lang.reflect.InvocationHandler; 26 import java.lang.reflect.InvocationTargetException; 27 import java.lang.reflect.Method; 28 import java.lang.reflect.Proxy; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.Iterator; 34 import java.util.List; 35 import java.util.Set; 36 37 /** 38 * Base test case for testing the variety of forwarding classes. 39 * 40 * @author Robert Konigsberg 41 * @author Louis Wasserman 42 */ 43 public abstract class ForwardingTestCase extends TestCase { 44 45 private final List<String> calls = new ArrayList<String>(); 46 called(String id)47 private void called(String id) { 48 calls.add(id); 49 } 50 getCalls()51 protected String getCalls() { 52 return calls.toString(); 53 } 54 isCalled()55 protected boolean isCalled() { 56 return !calls.isEmpty(); 57 } 58 59 @SuppressWarnings("unchecked") createProxyInstance(Class<T> c)60 protected <T> T createProxyInstance(Class<T> c) { 61 /* 62 * This invocation handler only registers that a method was called, 63 * and then returns a bogus, but acceptable, value. 64 */ 65 InvocationHandler handler = new InvocationHandler() { 66 @Override 67 public Object invoke(Object proxy, Method method, Object[] args) 68 throws Throwable { 69 called(asString(method)); 70 71 return getDefaultValue(method.getReturnType()); 72 } 73 }; 74 75 return (T) Proxy.newProxyInstance(c.getClassLoader(), 76 new Class[] { c }, handler); 77 } 78 79 private static final Joiner COMMA_JOINER = Joiner.on(","); 80 81 /* 82 * Returns string representation of a method. 83 * 84 * If the method takes no parameters, it returns the name (e.g. 85 * "isEmpty". If the method takes parameters, it returns the simple names 86 * of the parameters (e.g. "put(Object,Object)".) 87 */ asString(Method method)88 private String asString(Method method) { 89 String methodName = method.getName(); 90 Class<?>[] parameterTypes = method.getParameterTypes(); 91 92 if (parameterTypes.length == 0) { 93 return methodName; 94 } 95 96 Iterable<String> parameterNames = Iterables.transform( 97 Arrays.asList(parameterTypes), 98 new Function<Class<?>, String>() { 99 @Override 100 public String apply(Class<?> from) { 101 return from.getSimpleName(); 102 } 103 }); 104 return methodName + "(" + COMMA_JOINER.join(parameterNames) + ")"; 105 } 106 getDefaultValue(Class<?> returnType)107 private static Object getDefaultValue(Class<?> returnType) { 108 if (returnType == boolean.class || returnType == Boolean.class) { 109 return Boolean.FALSE; 110 } else if (returnType == int.class || returnType == Integer.class) { 111 return 0; 112 } else if ((returnType == Set.class) || (returnType == Collection.class)) { 113 return Collections.emptySet(); 114 } else if (returnType == Iterator.class) { 115 return Iterators.emptyModifiableIterator(); 116 } else if (returnType.isArray()) { 117 return Array.newInstance(returnType.getComponentType(), 0); 118 } else if ("java.util.function.Predicate".equals(returnType.getCanonicalName()) 119 || ("java.util.function.Consumer".equals(returnType.getCanonicalName()))) { 120 // Generally, methods that accept java.util.function.* instances 121 // don't like to get null values. We generate them dynamically 122 // using Proxy so that we can have Java 7 compliant code. 123 InvocationHandler handler = new InvocationHandler() { 124 @Override public Object invoke(Object proxy, Method method, 125 Object[] args) { 126 // Crude, but acceptable until we can use Java 8. Other 127 // methods have default implementations, and it is hard to 128 // distinguish. 129 if ("test".equals(method.getName()) 130 || "accept".equals(method.getName())) { 131 return getDefaultValue(method.getReturnType()); 132 } 133 throw new IllegalStateException( 134 "Unexpected " + method + " invoked on " + proxy); 135 } 136 }; 137 return Proxy.newProxyInstance(returnType.getClassLoader(), 138 new Class[] { returnType }, 139 handler); 140 } else { 141 return null; 142 } 143 } 144 callAllPublicMethods(Class<T> theClass, T object)145 protected static <T> void callAllPublicMethods(Class<T> theClass, T object) 146 throws InvocationTargetException { 147 for (Method method : theClass.getMethods()) { 148 Class<?>[] parameterTypes = method.getParameterTypes(); 149 Object[] parameters = new Object[parameterTypes.length]; 150 for (int i = 0; i < parameterTypes.length; i++) { 151 parameters[i] = getDefaultValue(parameterTypes[i]); 152 } 153 try { 154 try { 155 method.invoke(object, parameters); 156 } catch (InvocationTargetException ex) { 157 try { 158 throw ex.getCause(); 159 } catch (UnsupportedOperationException unsupported) { 160 // this is a legit exception 161 } 162 } 163 } catch (Throwable cause) { 164 throw new InvocationTargetException(cause, 165 method + " with args: " + Arrays.toString(parameters)); 166 } 167 } 168 } 169 } 170