Java tutorial
// // Copyright (c) 2013 Financial Business Systems, Inc. All rights reserved. // // 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.sparkplatform.api.core; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import static org.junit.Assert.*; /** * Test utility class that makes easy work of testing default behavior of getters and setters. * Brilliant idea from http://www.nearinfinity.com/blogs/scott_leberknight/do_you_unit_test_getters.html * * @author Scott Leberknight */ @SuppressWarnings("rawtypes") public final class PropertyAsserter { private static final Log log = LogFactory.getLog(PropertyAsserter.class); private static final Map<Class, Object> TYPE_ARGUMENTS = new HashMap<Class, Object>(); static { TYPE_ARGUMENTS.put(Collection.class, new ArrayList()); TYPE_ARGUMENTS.put(List.class, new ArrayList()); TYPE_ARGUMENTS.put(Set.class, new HashSet()); TYPE_ARGUMENTS.put(SortedSet.class, new TreeSet()); TYPE_ARGUMENTS.put(Map.class, new HashMap()); TYPE_ARGUMENTS.put(SortedMap.class, new TreeMap()); TYPE_ARGUMENTS.put(Boolean.class, true); TYPE_ARGUMENTS.put(Boolean.TYPE, true); TYPE_ARGUMENTS.put(Character.class, 'Z'); TYPE_ARGUMENTS.put(Character.TYPE, 'Z'); TYPE_ARGUMENTS.put(Byte.class, (byte) 10); TYPE_ARGUMENTS.put(Byte.TYPE, (byte) 10); TYPE_ARGUMENTS.put(Short.class, (short) 10); TYPE_ARGUMENTS.put(Short.TYPE, (short) 10); TYPE_ARGUMENTS.put(Integer.class, 10); TYPE_ARGUMENTS.put(Integer.TYPE, 10); TYPE_ARGUMENTS.put(Long.class, 10L); TYPE_ARGUMENTS.put(Long.TYPE, 10L); TYPE_ARGUMENTS.put(Float.class, 3.14159F); TYPE_ARGUMENTS.put(Float.TYPE, 3.14159F); TYPE_ARGUMENTS.put(Double.class, 3.14159); TYPE_ARGUMENTS.put(Double.TYPE, 3.14159); TYPE_ARGUMENTS.put(java.sql.Date.class, new java.sql.Date(new Date().getTime())); TYPE_ARGUMENTS.put(Timestamp.class, new Timestamp(new Date().getTime())); TYPE_ARGUMENTS.put(Calendar.class, Calendar.getInstance()); } private static final Map<Class, Object> DEFAULT_TYPE_ARGUMENTS = Collections .unmodifiableMap(new HashMap<Class, Object>(TYPE_ARGUMENTS)); private static final int TEST_ARRAY_SIZE = 10; private PropertyAsserter() { } /** * Registers the specified type that will default to the speicifed <code>defaultArgument</code> as the argument to * setter methods. Note this method will override any existing default arguments for a type. * * @param type the type to register * @param defaultArgument the default argument to use in setters */ public static void registerTypeAndDefaultArgument(Class type, Object defaultArgument) { TYPE_ARGUMENTS.put(type, defaultArgument); } /** * Removes the specified type, so that there wil no longer be a default argument for the type. * * @param type the type to degister */ public static void deregisterType(Class type) { TYPE_ARGUMENTS.remove(type); } /** Resets the types and default arguments. */ public static void resetToDefaultTypes() { TYPE_ARGUMENTS.clear(); TYPE_ARGUMENTS.putAll(DEFAULT_TYPE_ARGUMENTS); } /** * Returns the default argument for the specified type. * * @param type the type * @return the type's default argument */ public static Object defaultArgumentForType(Class type) { return TYPE_ARGUMENTS.get(type); } /** * Tests that the getter and setter methods for <code>property</code> work in a basic fashion, which is that the * getter returns the exact same object as set by the setter method. (And we don't care that FindBugz says this is * bad, bad, bad and furthermore we disable that check in FindBugz anyway based on the Reduction of Java * Overengineering Act. Then again, some might argue that <i>this</i> class itself embodies Java Overengineering!) * <p/> Uses a default argument for basic collection types, primitive types, Dates, java.sql.Dates, and Timestamps. * See {@link PropertyAsserter#TYPE_ARGUMENTS}. * * @param target the object on which to invoke the getter and setter * @param property the property name, e.g. "firstName" */ public static void assertBasicGetterSetterBehavior(Object target, String property) { assertBasicGetterSetterBehavior(target, property, null); } /** * See {@link #assertBasicGetterSetterBehavior(Object,String)} method. Only difference is that here we accept an * explicit argument for the setter method. * * @param target the object on which to invoke the getter and setter * @param property the property name, e.g. "firstName" * @param argument the property value, i.e. the value the setter will be invoked with */ public static void assertBasicGetterSetterBehavior(Object target, String property, Object argument) { try { PropertyDescriptor descriptor = new PropertyDescriptor(property, target.getClass()); Object arg = argument; Class type = descriptor.getPropertyType(); if (arg == null) { if (type.isArray()) { arg = Array.newInstance(type.getComponentType(), new int[] { TEST_ARRAY_SIZE }); } else if (type.isEnum()) { arg = type.getEnumConstants()[0]; } else if (TYPE_ARGUMENTS.containsKey(type)) { arg = TYPE_ARGUMENTS.get(type); } else { arg = invokeDefaultConstructorEvenIfPrivate(type); } } Method writeMethod = descriptor.getWriteMethod(); Method readMethod = descriptor.getReadMethod(); writeMethod.invoke(target, arg); Object propertyValue = readMethod.invoke(target); if (type.isPrimitive()) { assertEquals(property + " getter/setter failed test", arg, propertyValue); } else { assertSame(property + " getter/setter failed test", arg, propertyValue); } } catch (IntrospectionException e) { String msg = "Error creating PropertyDescriptor for property [" + property + "]. Do you have a getter and a setter?"; log.error(msg, e); fail(msg); } catch (IllegalAccessException e) { String msg = "Error accessing property. Are the getter and setter both accessible?"; log.error(msg, e); fail(msg); } catch (InvocationTargetException e) { String msg = "Error invoking method on target"; log.error(msg, e); fail(msg); } } @SuppressWarnings("unchecked") private static Object invokeDefaultConstructorEvenIfPrivate(Class type) { try { Constructor ctor = type.getDeclaredConstructor(); ctor.setAccessible(true); return ctor.newInstance(); } catch (Exception ex) { throw new RuntimeException("Could not invoke default constructor on type " + type, ex); } } /** * See {@link #assertBasicGetterSetterBehavior(Object,String)} method. Only difference is that here we accept a map * containing property name/value pairs. Use this to test a bunch of property accessors at once. Note that the * values in the map can be null, and in that case we'll try to supply a default argument. * * @param target the object on which to invoke the getter and setter * @param properties map of property names to argument values */ public static void assertBasicGetterSetterBehavior(Object target, Map<String, Object> properties) { Set<Map.Entry<String, Object>> entries = properties.entrySet(); for (Map.Entry<String, Object> entry : entries) { assertBasicGetterSetterBehavior(target, entry.getKey(), entry.getValue()); } } /** * See {@link #assertBasicGetterSetterBehavior(Object,String)} method. Only difference is that here we accept an * array of property names. Use this to test a bunch of property accessors at once, using default arguments. * * @param target the object on which to invoke the getter and setter * @param propertyNames the names of the propertyes you want to test */ public static void assertBasicGetterSetterBehavior(Object target, String... propertyNames) { Map<String, Object> properties = new LinkedHashMap<String, Object>(); for (String propertyName : propertyNames) { properties.put(propertyName, null); } assertBasicGetterSetterBehavior(target, properties); } /** * See {@link #assertBasicGetterSetterBehavior(Object, String[])} method. No items are blacklisted. * * @param target the object on which to invoke the getter and setter */ public static void assertBasicGetterSetterBehavior(Object target) { assertBasicGetterSetterBehaviorWithBlacklist(target); } /** * See {@link #assertBasicGetterSetterBehavior(Object,String)} method. Big difference here is that we try to * automatically introspect the target object, finding read/write properties, and automatically testing the getter * and setter. Note specifically that read-only properties are ignored, as there is no way for us to know how to set * the value (since there isn't a public setter). * <p/> * Any property names contained in the blacklist will be skipped. * <p/> * * @param target the object on which to invoke the getter and setter * @param propertyNames the list of property names that should not be tested */ public static void assertBasicGetterSetterBehaviorWithBlacklist(Object target, String... propertyNames) { List<String> blacklist = Arrays.asList(propertyNames); try { BeanInfo beanInfo = Introspector.getBeanInfo(target.getClass()); PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor descriptor : descriptors) { if (descriptor.getWriteMethod() == null) { continue; } if (!blacklist.contains(descriptor.getDisplayName())) { assertBasicGetterSetterBehavior(target, descriptor.getDisplayName()); } } } catch (IntrospectionException e) { fail("Failed while introspecting target " + target.getClass()); } } }