• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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