001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.test; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.ParameterizedType; 022import java.lang.reflect.Type; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.List; 029 030import org.conqat.lib.commons.collections.AllEqualComparator; 031import org.conqat.lib.commons.collections.CollectionUtils; 032import org.conqat.lib.commons.collections.IdentityHashSet; 033import org.conqat.lib.commons.reflect.MethodNameComparator; 034import org.conqat.lib.commons.string.StringUtils; 035 036/** 037 * This class provides various utility methods used to test deep cloning 038 * implementations. 039 */ 040public class SimulinkTestUtils { 041 042 /** Name of the clone method. */ 043 private static final String CLONE_METHOD_NAME = "clone"; 044 045 /** 046 * This works analogous to {@link #getAllReferencedObjects(Object, String...)} 047 * but allows to limit the results to a certain type. 048 */ 049 public static <T> IdentityHashSet<T> getAllReferencedObjects(Object root, Class<T> type, 050 String... packagePrefixes) { 051 IdentityHashSet<T> result = new IdentityHashSet<>(); 052 for (Object object : getAllReferencedObjects(root, packagePrefixes)) { 053 if (type.isAssignableFrom(object.getClass())) { 054 @SuppressWarnings("unchecked") 055 T object2 = (T) object; 056 result.add(object2); 057 } 058 } 059 return result; 060 } 061 062 /** 063 * Get all objects an object references. To to find the objects, this method 064 * uses reflection to determine the methods that define the object networks, 065 * typically examples are <code>getChildren()</code> or 066 * <code>getParent()</code>. To limit the selection of methods, a list of 067 * package prefixes must be specified. Only methods that do return a type 068 * matched by one of the prefixes are taken into account. Additionally all 069 * methods that return an implementation of {@link Collection} are considered. 070 * 071 * @param root 072 * root of the object network 073 * @param packagePrefixes 074 * list of package prefixes to take into account 075 */ 076 public static IdentityHashSet<Object> getAllReferencedObjects(Object root, String... packagePrefixes) { 077 IdentityHashSet<Object> result = new IdentityHashSet<>(); 078 buildReferenceSet(root, result, packagePrefixes); 079 return result; 080 } 081 082 /** Recursively build set of all referenced objects. */ 083 private static void buildReferenceSet(Object object, IdentityHashSet<Object> set, String[] packagePrefixes) { 084 085 set.add(object); 086 for (Object item : getReferencedObjects(object, AllEqualComparator.OBJECT_INSTANCE, packagePrefixes)) { 087 if (item != null && !set.contains(item)) { 088 buildReferenceSet(item, set, packagePrefixes); 089 } 090 } 091 } 092 093 /** 094 * Get all objects referenced by an object through methods that do return 095 * arrays. 096 */ 097 private static ArrayList<Object> getArrayObjects(Object object, Comparator<Object> comparator, 098 String... packagePrefixes) { 099 100 ArrayList<Object> result = new ArrayList<>(); 101 102 for (Method method : object.getClass().getMethods()) { 103 if (method.getParameterTypes().length == 0 && !isExcluded(method) 104 && hasArrayReturnType(method, packagePrefixes)) { 105 Object returnValue = invoke(object, method); 106 if (returnValue != null) { 107 Object[] array = (Object[]) returnValue; 108 List<Object> list = Arrays.asList(array); 109 110 Collections.sort(list, comparator); 111 result.addAll(list); 112 } 113 } 114 } 115 116 return result; 117 } 118 119 /** 120 * Get all objects referenced by an object through methods that do return 121 * collections. 122 */ 123 private static ArrayList<Object> getCollectionObjects(Object object, Comparator<Object> comparator, 124 String... packagePrefixes) { 125 126 ArrayList<Object> result = new ArrayList<>(); 127 128 for (Method method : object.getClass().getMethods()) { 129 if (method.getParameterTypes().length == 0 && !isExcluded(method) 130 && hasCollectionReturnType(method, packagePrefixes)) { 131 Object returnValue = invoke(object, method); 132 133 if (returnValue != null) { 134 Collection<?> collection = (Collection<?>) returnValue; 135 List<?> list = CollectionUtils.sort(collection, comparator); 136 result.addAll(list); 137 } 138 } 139 140 } 141 142 return result; 143 } 144 145 /** 146 * Get list of methods that (1) do not have parameters, (2) whose return type 147 * starts with one of the given prefixes and whose names is not 148 * <code>deepClone()</code>. The methods are ordered by name. 149 */ 150 private static ArrayList<Method> getMethods(Object object, String[] packagePrefixes) { 151 ArrayList<Method> methods = new ArrayList<>(); 152 for (Method method : object.getClass().getMethods()) { 153 if (method.getName().equals(CLONE_METHOD_NAME)) { 154 continue; 155 } 156 if (method.getParameterTypes().length > 0) { 157 continue; 158 } 159 if (method.isSynthetic()) { 160 continue; 161 } 162 Class<?> returnType = method.getReturnType(); 163 if (StringUtils.startsWithOneOf(returnType.getName(), packagePrefixes)) { 164 methods.add(method); 165 } 166 } 167 168 Collections.sort(methods, MethodNameComparator.INSTANCE); 169 170 return methods; 171 } 172 173 /** 174 * Get all objects referenced by an object through methods that do <em>not</em> 175 * return collections. 176 */ 177 private static ArrayList<Object> getNonCollectionObjects(Object object, String... packagePrefixes) { 178 179 ArrayList<Object> objects = new ArrayList<>(); 180 181 for (Method method : getMethods(object, packagePrefixes)) { 182 if (!isExcluded(method)) { 183 objects.add(invoke(object, method)); 184 } 185 } 186 return objects; 187 } 188 189 /** 190 * Returns true if the given method should be excluded from testing. This checks 191 * for the presence of the {@link SimulinkTestExclude} annotation. 192 */ 193 private static boolean isExcluded(Method method) { 194 return method.isAnnotationPresent(SimulinkTestExclude.class); 195 } 196 197 /** 198 * Get all objects an object references in an order defined by the comparator. 199 */ 200 private static ArrayList<Object> getReferencedObjects(Object object, Comparator<Object> comparator, 201 String... packagePrefixes) { 202 203 ArrayList<Object> result = new ArrayList<>(); 204 205 ArrayList<Object> nonCollectionObjects = getNonCollectionObjects(object, packagePrefixes); 206 result.addAll(nonCollectionObjects); 207 208 ArrayList<Object> collectionObjects = getCollectionObjects(object, comparator, packagePrefixes); 209 result.addAll(collectionObjects); 210 211 ArrayList<Object> arrayObjects = getArrayObjects(object, comparator, packagePrefixes); 212 result.addAll(arrayObjects); 213 214 return result; 215 } 216 217 /** 218 * Checks if a method returns an array with type that starts with one of the 219 * provided prefixes. 220 */ 221 private static boolean hasArrayReturnType(Method method, String... packagePrefixes) { 222 Class<?> returnType = method.getReturnType(); 223 if (!returnType.isArray()) { 224 return false; 225 } 226 Class<?> actualType = returnType.getComponentType(); 227 return StringUtils.startsWithOneOf(actualType.getName(), packagePrefixes); 228 } 229 230 /** 231 * Checks if a method returns an collection whose generic type that starts with 232 * one of the provided prefixes. 233 */ 234 private static boolean hasCollectionReturnType(Method method, String... packagePrefixes) { 235 Class<?> returnType = method.getReturnType(); 236 237 if (!Collection.class.isAssignableFrom(returnType)) { 238 return false; 239 } 240 241 Type genericReturnType = method.getGenericReturnType(); 242 // Raw type 243 if (returnType == genericReturnType) { 244 return false; 245 } 246 247 ParameterizedType type = (ParameterizedType) method.getGenericReturnType(); 248 249 // Collections have only one type parameter 250 Type typeArg = type.getActualTypeArguments()[0]; 251 252 // potentially this can be another parameterized type, e.g. for 253 // Set<Set<K>> or a wildcard type. Handling these is very tricky and is 254 // currently not supported. Hence, we silently ignore these. 255 if (!(typeArg instanceof Class<?>)) { 256 return false; 257 } 258 259 Class<?> actualType = (Class<?>) typeArg; 260 261 return StringUtils.startsWithOneOf(actualType.getName(), packagePrefixes); 262 } 263 264 /** 265 * This simpy calls {@link Method#invoke(Object, Object...)}. If the called 266 * method throws an exception, this returns <code>null</code>. A possible 267 * {@link IllegalAccessException} is converted to a {@link RuntimeException} . 268 */ 269 private static Object invoke(Object object, Method method) { 270 try { 271 return method.invoke(object); 272 } catch (RuntimeException e) { 273 return null; 274 } catch (IllegalAccessException e) { 275 throw new RuntimeException(e); 276 } catch (InvocationTargetException e) { 277 return null; 278 } 279 } 280}