Home | History | Annotate | Download | only in collect
      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 
     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