Coverage Report - biz.xsoftware.impl.mock.MethodVerifier
 
Classes in this File Line Coverage Branch Coverage Complexity
MethodVerifier
72%
93/129
91%
39/43
0
 
 1  
 package biz.xsoftware.impl.mock;
 2  
 
 3  
 import java.lang.reflect.Field;
 4  
 import java.lang.reflect.Method;
 5  
 import java.util.ArrayList;
 6  
 import java.util.Arrays;
 7  
 import java.util.HashSet;
 8  
 import java.util.List;
 9  
 import java.util.Set;
 10  
 import java.util.logging.Level;
 11  
 import java.util.logging.Logger;
 12  
 
 13  
 import biz.xsoftware.mock.MethodSignature;
 14  
 import biz.xsoftware.mock.MockObject;
 15  
 
 16  
 /**
 17  
  * 
 18  
  * @author Dean Hiller, Brian Freeman
 19  
  * @since May 21, 2007
 20  
  */
 21  
 public final class MethodVerifier {
 22  17
     private static final Logger log = Logger.getLogger(MethodVerifier.class.getName());
 23  
 
 24  0
     private MethodVerifier() {
 25  0
     }
 26  
 
 27  
     /**
 28  
      * Searches through all methods in the given classes for the given
 29  
      * methodName. If it isn't found then it throws an IllegalArgumentException
 30  
      * 
 31  
      * @param classes
 32  
      * @param methodName
 33  
      * @param isCaseSensitive
 34  
      * 
 35  
      * @throws IllegalArgumentException If the method name is not found
 36  
      */
 37  
     protected static void verifyMethodNameExist(final Class<?>[] classes, final String methodName,
 38  
             final boolean isCaseSensitive) {
 39  
 
 40  125
         final Set<Method> methods = new HashSet<Method>();
 41  250
         for (Class<?> c : classes) {
 42  125
             if (log.isLoggable(Level.FINEST)) {
 43  0
                 log.finest("adding methods for class=" + c.getName());
 44  
             }
 45  
 
 46  125
             Method[] classMethods = c.getMethods();
 47  125
             methods.addAll(Arrays.asList(classMethods));
 48  
         }
 49  
 
 50  125
         final MethodSignature ms = new MethodSignature(methodName, MethodSignature.ANY_CLASS_TYPE);
 51  
 
 52  125
         final List<Method> m = findMatchingMethods(methods, ms, isCaseSensitive);
 53  125
         if (m == null || m.size() == 0) {
 54  3
             String classNames = getClassNamesString(classes);
 55  3
             throw new IllegalArgumentException("method='" + methodName + "' is not a public method on any of the"
 56  
                     + "\nfollowing Classes/Interfaces:" + classNames);
 57  
         }
 58  122
     }
 59  
 
 60  
     /**
 61  
      * Looks for the given method in array of classes given
 62  
      * 
 63  
      * @param classes The classes to look for the method in
 64  
      * @param method The value of the method name to look for
 65  
      * @param isCaseSensitive If the method name matching should be case
 66  
      *            sensitive
 67  
      * @param argTypes The methods argument class types
 68  
      * 
 69  
      * @return The found <code>Method</code> or null if not found
 70  
      * 
 71  
      * 
 72  
      */
 73  
     public static Method getMethod(final Class<?>[] classes, final MethodSignature methodSignature,
 74  
             final boolean isCaseSensitive) {
 75  
 
 76  94
         if (methodSignature == null) {
 77  0
             throw new IllegalArgumentException("methodSignature parameter cannot be null");
 78  94
         } else if (MockObject.ANY.equals(methodSignature.getName())
 79  
                 || MockObject.NONE.equals(methodSignature.getName())) {
 80  0
             return null;
 81  94
         } else if (classes == null) {
 82  0
             return null;
 83  94
         } else if (methodSignature.getParamTypes() != null) {
 84  92
             for (Class<?> argType : methodSignature.getParamTypes()) {
 85  35
                 if (argType == null) {
 86  0
                     throw new IllegalArgumentException("No Class parameters can be null, yet one was null");
 87  
                 }
 88  
             }
 89  
         }
 90  
 
 91  94
         final Set<Method> methods = new HashSet<Method>();
 92  188
         for (Class<?> c : classes) {
 93  94
             if (log.isLoggable(Level.FINEST)) {
 94  0
                 log.finest("adding methods for class=" + c.getName());
 95  
             }
 96  
 
 97  94
             Method[] classMethods = c.getMethods();
 98  94
             List<Method> listedMethods = Arrays.asList(classMethods);
 99  94
             methods.addAll(listedMethods);
 100  
         }
 101  
 
 102  94
         final List<Method> matchingMethods = findMatchingMethods(methods, methodSignature, isCaseSensitive);
 103  
 
 104  94
         if (matchingMethods == null) {
 105  2
             String classNames = getClassNamesString(classes);
 106  2
             throw new IllegalArgumentException("method='" + methodSignature + "' is not a public method on any of the"
 107  
                     + "\nfollowing Classes/Interfaces:" + classNames);
 108  92
         } else if (matchingMethods.size() == 1) {
 109  91
             return matchingMethods.get(0);
 110  
         } else {
 111  1
             String msg = "Too many methods match your method expected='" + methodSignature + "'.  Methods found:\n";
 112  1
             for (Method m : matchingMethods) {
 113  2
                 msg += MessageHelper.getMethodSignature(null, m, "") + "\n";
 114  2
             }
 115  1
             msg +=
 116  
                     "You need to clarify which method you would like to add the return value to by "
 117  
                             + "specifying the Class arguments which are the type of overloaded method";
 118  1
             throw new IllegalArgumentException(msg);
 119  
         }
 120  
     }
 121  
 
 122  
     /**
 123  
      * @param classes
 124  
      * @return
 125  
      */
 126  
     private static String getClassNamesString(final Class<?>[] classes) {
 127  5
         String classNames = "";
 128  10
         for (int i = 0; i < classes.length; i++) {
 129  5
             classNames += "\n" + classes[i];
 130  
         }
 131  5
         return classNames;
 132  
     }
 133  
 
 134  
     /**
 135  
      * Looks for the matching {@link Method} given the {@link MethodSignature}
 136  
      * 
 137  
      * @param methods
 138  
      * @param methodSignature
 139  
      * @param isCaseSensitive Should we be particular about the method's case?
 140  
      * @return
 141  
      */
 142  
     private static List<Method> findMatchingMethods(final Set<Method> methods, final MethodSignature methodSignature,
 143  
             final boolean isCaseSensitive) {
 144  
 
 145  
         // find all methods with same name first
 146  219
         List<Method> matches = new ArrayList<Method>(methods.size());
 147  219
         for (Method method : methods) {
 148  4866
             if (isCaseSensitive) {
 149  4751
                 if (methodSignature.getName().equals(method.getName())) {
 150  215
                     matches.add(method);
 151  215
                 }
 152  
             } else {
 153  115
                 if (methodSignature.getName().equalsIgnoreCase(method.getName())) {
 154  7
                     matches.add(method);
 155  
                 }
 156  
             }
 157  4866
         }
 158  
 
 159  219
         if (matches.size() == 0) {
 160  5
             return null;
 161  
         }
 162  
 
 163  
         // see if the param types we're looking for is set to ANY_CLASS_TYPE,
 164  
         // if it is then we'll just return all the methods where the name
 165  
         // matches
 166  214
         if (methodSignature.getParamTypes() != null && methodSignature.getParamTypes().length == 1
 167  
                 && methodSignature.getParamTypes()[0] == MethodSignature.ANY_CLASS_TYPE) {
 168  124
             return matches;
 169  
         }
 170  
 
 171  
         // create a new list for the final version of methods that match
 172  
         // use matches.size because we know there shouldn't be any more
 173  
         // matches than that
 174  90
         List<Method> narrowedMatches = new ArrayList<Method>(matches.size());
 175  
 
 176  
         // find one that takes no args if that is what we are looking for...
 177  90
         if (methodSignature.getParamTypes() == null || methodSignature.getParamTypes().length == 0) {
 178  57
             for (Method method : matches) {
 179  57
                 if (method.getParameterTypes() == null || method.getParameterAnnotations().length == 0) {
 180  27
                     narrowedMatches.add(method);
 181  27
                     return narrowedMatches;
 182  
                 }
 183  30
             }
 184  30
             return matches;
 185  
         }
 186  
 
 187  
         // we have some argType then....fine the single method....
 188  33
         for (Method method : matches) {
 189  33
             Class<?>[] types = method.getParameterTypes();
 190  33
             if (doArgTypesMatch(types, methodSignature.getParamTypes())) {
 191  33
                 narrowedMatches.add(method);
 192  33
                 return narrowedMatches;
 193  
             }
 194  0
         }
 195  
 
 196  0
         return null;
 197  
     }
 198  
 
 199  
     /**
 200  
      * @param methodArgTypes
 201  
      * @param objArgTypes
 202  
      * @return
 203  
      */
 204  
     public static boolean doArgTypesMatch(final Class<?>[] methodArgTypes, final Class<?>[] objArgTypes) {
 205  53
         if (log.isLoggable(Level.FINEST)) {
 206  0
             StringBuilder sb = new StringBuilder();
 207  0
             sb.append("doArgTypesMatch({");
 208  
 
 209  0
             Class<?>[][] bothClassArrays = new Class<?>[][] { methodArgTypes, objArgTypes };
 210  
 
 211  0
             boolean printComma = true;
 212  0
             for (Class<?>[] cArray : bothClassArrays) {
 213  0
                 if (cArray.length > 0) {
 214  0
                     int i = 0;
 215  0
                     for (; i < cArray.length - 1; i++) {
 216  0
                         sb.append(cArray[i].getSimpleName()).append(",");
 217  
                     }
 218  0
                     sb.append(cArray[i].getSimpleName());
 219  
                 }
 220  
 
 221  0
                 if (printComma) {
 222  0
                     sb.append("}, {");
 223  0
                     printComma = false;
 224  
                 }
 225  
             }
 226  
 
 227  0
             sb.append("})");
 228  
 
 229  0
             log.finest(sb.toString());
 230  
         }
 231  
 
 232  53
         if (methodArgTypes.length != objArgTypes.length) {
 233  3
             return false;
 234  
         }
 235  
 
 236  
         // loop through each of the arguments checking to see if they don't
 237  
         // match
 238  107
         for (int i = 0; i < methodArgTypes.length; i++) {
 239  62
             if (log.isLoggable(Level.FINEST)) {
 240  0
                 StringBuilder sb = new StringBuilder();
 241  0
                 sb.append("comparing ").append(methodArgTypes[i].getSimpleName()).append(" to ").append(
 242  
                         objArgTypes[i].getSimpleName()).append(">").append("\tequals? ").append(
 243  
                         methodArgTypes[i].equals(objArgTypes[i])).append("\tinstance? ").append(
 244  
                         methodArgTypes[i].isInstance(objArgTypes[i]));
 245  0
                 log.finest(sb.toString());
 246  
             }
 247  
 
 248  
             // see if the objArgType is castable as the expected type
 249  
             try {
 250  62
                 objArgTypes[i].asSubclass(methodArgTypes[i]);
 251  
                 // if we get past the above without throwing an exception
 252  
                 // then we have a match, skip to the next argument
 253  22
                 continue;
 254  40
             } catch (ClassCastException ignore) {}
 255  
 
 256  40
             if (!methodArgTypes[i].equals(objArgTypes[i]) && !objArgTypes[i].equals(MethodSignature.ANY_CLASS_TYPE)) {
 257  
                 // they're not equal and the objArgType is not ANY...
 258  
                 // let's do some more investigation
 259  
 
 260  40
                 if (methodArgTypes[i].isPrimitive()) {
 261  
                     // primitives need to be handled in a special way
 262  36
                     if (log.isLoggable(Level.FINEST)) {
 263  0
                         log.finest("methodArg is primitive: " + methodArgTypes[i]);
 264  
                     }
 265  
 
 266  
                     // see if the objArg matches
 267  
                     try {
 268  36
                         Field f = objArgTypes[i].getDeclaredField("TYPE");
 269  33
                         Class<?> c = (Class<?>) f.get(null);
 270  
 
 271  33
                         if (log.isLoggable(Level.FINEST)) {
 272  0
                             log.finest("objArg is also primitive: " + c);
 273  
                         }
 274  
 
 275  
                         // make sure the two primitive classes match
 276  33
                         if (!methodArgTypes[i].equals(c)) {
 277  0
                             return false;
 278  
                         }
 279  
 
 280  3
                     } catch (Exception ignore) {
 281  
                         // then the two primitive args don't match
 282  3
                         return false;
 283  33
                     }
 284  4
                 } else if (objArgTypes[i].isPrimitive()) {
 285  
                     // primitives need to be handled in a special way
 286  2
                     if (log.isLoggable(Level.FINEST)) {
 287  0
                         log.finest("objArg is primitive: " + objArgTypes[i]);
 288  
                     }
 289  
 
 290  
                     // see if the objArg matches
 291  
                     try {
 292  2
                         Field f = methodArgTypes[i].getDeclaredField("TYPE");
 293  2
                         Class<?> c = (Class<?>) f.get(null);
 294  
 
 295  2
                         if (log.isLoggable(Level.FINEST)) {
 296  0
                             log.finest("methodArg is also primitive: " + c);
 297  
                         }
 298  
 
 299  
                         // make sure the two primitive classes match
 300  2
                         if (!objArgTypes[i].equals(c)) {
 301  0
                             return false;
 302  
                         }
 303  0
                     } catch (Exception ignore) {
 304  
                         // then the two primitive args don't match
 305  0
                         return false;
 306  2
                     }
 307  
                 } else {
 308  
                     // no other special cases to check and they don't match
 309  2
                     return false;
 310  
                 }
 311  
             }
 312  
         }
 313  45
         return true;
 314  
     }
 315  
 }