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
17package com.google.common.collect;
18
19import com.google.common.base.Function;
20import com.google.common.base.Joiner;
21
22import junit.framework.TestCase;
23
24import java.lang.reflect.Array;
25import java.lang.reflect.InvocationHandler;
26import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Method;
28import java.lang.reflect.Proxy;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.Iterator;
34import java.util.List;
35import 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 */
43public abstract class ForwardingTestCase extends TestCase {
44
45  private final List<String> calls = new ArrayList<String>();
46
47  private void called(String id) {
48    calls.add(id);
49  }
50
51  protected String getCalls() {
52    return calls.toString();
53  }
54
55  protected boolean isCalled() {
56    return !calls.isEmpty();
57  }
58
59  @SuppressWarnings("unchecked")
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   */
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
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 {
119      return null;
120    }
121  }
122
123  protected static <T> void callAllPublicMethods(Class<T> theClass, T object)
124      throws InvocationTargetException {
125    for (Method method : theClass.getMethods()) {
126      Class<?>[] parameterTypes = method.getParameterTypes();
127      Object[] parameters = new Object[parameterTypes.length];
128      for (int i = 0; i < parameterTypes.length; i++) {
129        parameters[i] = getDefaultValue(parameterTypes[i]);
130      }
131      try {
132        try {
133          method.invoke(object, parameters);
134        } catch (InvocationTargetException ex) {
135          try {
136            throw ex.getCause();
137          } catch (UnsupportedOperationException unsupported) {
138            // this is a legit exception
139          }
140        }
141      } catch (Throwable cause) {
142        throw new InvocationTargetException(cause,
143            method + " with args: " + Arrays.toString(parameters));
144      }
145    }
146  }
147}
148