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 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
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