| /* |
| * Copyright (C) 2005 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.common.testutils; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Functions; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Maps; |
| |
| import junit.framework.Assert; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Member; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * A test utility that verifies that your methods throw {@link |
| * NullPointerException} or {@link UnsupportedOperationException} whenever any |
| * of their parameters are null. To use it, you must first provide valid default |
| * values for the parameter types used by the class. |
| * |
| * @author Kevin Bourrillion |
| */ |
| public class NullPointerTester { |
| private Map<Class<?>, Object> defaults = Maps.newHashMap(); |
| |
| public NullPointerTester() { |
| setCommonDefaults(); |
| } |
| |
| private final void setCommonDefaults() { |
| setDefault(Appendable.class, new StringBuilder()); |
| setDefault(CharSequence.class, ""); |
| setDefault(Class.class, Class.class); |
| setDefault(Collection.class, Collections.emptySet()); |
| setDefault(Comparable.class, 0); |
| setDefault(Comparator.class, Collections.reverseOrder()); |
| setDefault(Function.class, Functions.identity()); |
| setDefault(Integer.class, 0); |
| setDefault(Iterable.class, Collections.emptySet()); |
| setDefault(Iterator.class, Iterators.emptyIterator()); |
| setDefault(List.class, Collections.emptyList()); |
| setDefault(Map.class, Collections.emptyMap()); |
| setDefault(Object.class, new Object()); |
| setDefault(Object[].class, new Object[0]); |
| setDefault(Pattern.class, Pattern.compile("")); |
| setDefault(Predicate.class, Predicates.alwaysTrue()); |
| setDefault(Set.class, Collections.emptySet()); |
| setDefault(String.class, ""); |
| setDefault(int.class, 0); |
| setDefault(long.class, 0L); |
| setDefault(short.class, (short) 0); |
| setDefault(char.class, 'a'); |
| setDefault(byte.class, (byte) 0); |
| setDefault(float.class, 0.0f); |
| setDefault(double.class, 0.0d); |
| setDefault(boolean.class, false); |
| } |
| |
| /** |
| * Sets a default value that can be used for any parameter of type |
| * {@code type}. |
| */ |
| public <T> void setDefault(Class<T> type, T value) { |
| defaults.put(type, value); |
| } |
| |
| /** |
| * Runs {@link #testConstructor} on every public constructor in class {@code |
| * c}. |
| */ |
| public void testAllPublicConstructors(Class<?> c) throws Exception { |
| for (Constructor<?> constructor : c.getDeclaredConstructors()) { |
| if (isPublic(constructor) && !isStatic(constructor)) { |
| testConstructor(constructor); |
| } |
| } |
| } |
| |
| /** |
| * Runs {@link #testMethod} on every public static method in class |
| * {@code c}. |
| */ |
| public void testAllPublicStaticMethods(Class<?> c) throws Exception { |
| for (Method method : c.getDeclaredMethods()) { |
| if (isPublic(method) && isStatic(method)) { |
| testMethod(null, method); |
| } |
| } |
| } |
| |
| /** |
| * Runs {@link #testMethod} on every public instance method of |
| * {@code instance}. |
| */ |
| public void testAllPublicInstanceMethods(Object instance) throws Exception { |
| Class<?> c = instance.getClass(); |
| for (Method method : c.getDeclaredMethods()) { |
| if (isPublic(method) && !isStatic(method)) { |
| testMethod(instance, method); |
| } |
| } |
| } |
| |
| /** |
| * Verifies that {@code method} produces a {@link NullPointerException} |
| * or {@link UnsupportedOperationException} whenever <i>any</i> of its |
| * non-{@link Nullable} parameters are null. |
| * |
| * @param instance the instance to invoke {@code method} on, or null if |
| * {@code method} is static |
| */ |
| public void testMethod(Object instance, Method method) throws Exception { |
| Class<?>[] types = method.getParameterTypes(); |
| for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { |
| testMethodParameter(instance, method, nullIndex); |
| } |
| } |
| |
| /** |
| * Verifies that {@code ctor} produces a {@link NullPointerException} or |
| * {@link UnsupportedOperationException} whenever <i>any</i> of its |
| * non-{@link Nullable} parameters are null. |
| */ |
| public void testConstructor(Constructor<?> ctor) throws Exception { |
| Class<?>[] types = ctor.getParameterTypes(); |
| for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { |
| testConstructorParameter(ctor, nullIndex); |
| } |
| } |
| |
| /** |
| * Verifies that {@code method} produces a {@link NullPointerException} or |
| * {@link UnsupportedOperationException} when the parameter in position {@code |
| * paramIndex} is null. If this parameter is marked {@link Nullable}, this |
| * method does nothing. |
| * |
| * @param instance the instance to invoke {@code method} on, or null if |
| * {@code method} is static |
| */ |
| public void testMethodParameter(Object instance, final Method method, |
| int paramIndex) throws Exception { |
| method.setAccessible(true); |
| testFunctorParameter(instance, new Functor() { |
| public Class<?>[] getParameterTypes() { |
| return method.getParameterTypes(); |
| } |
| public Annotation[][] getParameterAnnotations() { |
| return method.getParameterAnnotations(); |
| } |
| public void invoke(Object instance, Object[] params) |
| throws InvocationTargetException, IllegalAccessException { |
| method.invoke(instance, params); |
| } |
| @Override public String toString() { |
| return method.getName(); |
| } |
| }, paramIndex, method.getDeclaringClass()); |
| } |
| |
| /** |
| * Verifies that {@code ctor} produces a {@link NullPointerException} or |
| * {@link UnsupportedOperationException} when the parameter in position {@code |
| * paramIndex} is null. If this parameter is marked {@link Nullable}, this |
| * method does nothing. |
| */ |
| public void testConstructorParameter(final Constructor<?> ctor, |
| int paramIndex) throws Exception { |
| ctor.setAccessible(true); |
| testFunctorParameter(null, new Functor() { |
| public Class<?>[] getParameterTypes() { |
| return ctor.getParameterTypes(); |
| } |
| public Annotation[][] getParameterAnnotations() { |
| return ctor.getParameterAnnotations(); |
| } |
| public void invoke(Object instance, Object[] params) |
| throws InvocationTargetException, IllegalAccessException, |
| InstantiationException { |
| ctor.newInstance(params); |
| } |
| }, paramIndex, ctor.getDeclaringClass()); |
| } |
| |
| /** |
| * Verifies that {@code func} produces a {@link NullPointerException} or |
| * {@link UnsupportedOperationException} when the parameter in position {@code |
| * paramIndex} is null. If this parameter is marked {@link Nullable}, this |
| * method does nothing. |
| * |
| * @param instance the instance to invoke {@code func} on, or null if |
| * {@code func} is static |
| */ |
| private void testFunctorParameter(Object instance, Functor func, |
| int paramIndex, Class<?> testedClass) throws Exception { |
| if (parameterIsPrimitiveOrNullable(func, paramIndex)) { |
| return; // there's nothing to test |
| } |
| Object[] params = buildParamList(func, paramIndex); |
| try { |
| func.invoke(instance, params); |
| Assert.fail("No exception thrown from " + func + |
| Arrays.toString(params) + " for " + testedClass); |
| } catch (InvocationTargetException e) { |
| Throwable cause = e.getCause(); |
| Assert.assertTrue("wrong exception thrown from " + func + ": " + cause, |
| cause instanceof NullPointerException |
| || cause instanceof UnsupportedOperationException); |
| } |
| } |
| |
| private static boolean parameterIsPrimitiveOrNullable( |
| Functor func, int paramIndex) { |
| if (func.getParameterTypes()[paramIndex].isPrimitive()) { |
| return true; |
| } |
| Annotation[] annotations = func.getParameterAnnotations()[paramIndex]; |
| for (Annotation annotation : annotations) { |
| if (annotation instanceof Nullable) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private Object[] buildParamList(Functor func, int indexOfParamToSetToNull) { |
| Class<?>[] types = func.getParameterTypes(); |
| Object[] params = new Object[types.length]; |
| |
| for (int i = 0; i < types.length; i++) { |
| if (i != indexOfParamToSetToNull) { |
| params[i] = defaults.get(types[i]); |
| if (!parameterIsPrimitiveOrNullable(func, indexOfParamToSetToNull)) { |
| Assert.assertNotNull("No default value found for " |
| + types[i].getName(), params[i]); |
| } |
| } |
| } |
| return params; |
| } |
| |
| private interface Functor { |
| Class<?>[] getParameterTypes(); |
| Annotation[][] getParameterAnnotations(); |
| void invoke(Object o, Object[] params) throws Exception; |
| } |
| |
| private static boolean isPublic(Member member) { |
| return Modifier.isPublic(member.getModifiers()); |
| } |
| |
| private static boolean isStatic(Member member) { |
| return Modifier.isStatic(member.getModifiers()); |
| } |
| } |