Coverage Report - org.dozer.util.ReflectionUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
ReflectionUtils
86%
168/194
81%
95/116
4.304
 
 1  
 /**
 2  
  * Copyright 2005-2013 Dozer Project
 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  
 package org.dozer.util;
 17  
 
 18  
 import org.apache.commons.beanutils.PropertyUtils;
 19  
 import org.apache.commons.lang3.StringUtils;
 20  
 import org.dozer.MappingException;
 21  
 import org.dozer.fieldmap.HintContainer;
 22  
 import org.dozer.propertydescriptor.DeepHierarchyElement;
 23  
 
 24  
 import java.beans.IntrospectionException;
 25  
 import java.beans.PropertyDescriptor;
 26  
 import java.lang.reflect.*;
 27  
 import java.util.*;
 28  
 
 29  
 /**
 30  
  * Internal class that provides a various reflection utilities(specific to Dozer requirements) used throughout the code
 31  
  * base. Not intended for direct use by application code.
 32  
  *
 33  
  * @author tierney.matt
 34  
  * @author garsombke.franz
 35  
  */
 36  
 public final class ReflectionUtils {
 37  
 
 38  
   private static final String IAE_MESSAGE = "argument type mismatch";
 39  
 
 40  0
   private ReflectionUtils() {
 41  0
   }
 42  
 
 43  
   public static PropertyDescriptor findPropertyDescriptor(Class<?> objectClass, String fieldName,
 44  
       HintContainer deepIndexHintContainer) {
 45  340803
     PropertyDescriptor result = null;
 46  340803
     if (MappingUtils.isDeepMapping(fieldName)) {
 47  4247
       DeepHierarchyElement[] hierarchy = getDeepFieldHierarchy(objectClass, fieldName, deepIndexHintContainer);
 48  4164
       result = hierarchy[hierarchy.length - 1].getPropDescriptor();
 49  4164
     } else {
 50  336556
       PropertyDescriptor[] descriptors = getPropertyDescriptors(objectClass);
 51  
 
 52  336556
       if (descriptors != null) {
 53  336556
         int size = descriptors.length;
 54  1020191
         for (int i = 0; i < size; i++) {
 55  
 
 56  
           /*
 57  
             Bugfix #2826468.
 58  
             if object class has methods, f.e, getValue() and getValue(int index) in this case
 59  
             could happen that this field couldn't be mapped, because getValue(int index) becomes first
 60  
             and PropertyDescriptor.getReadMethod() returns null. We need to exclude IndexedPropertyDescriptor from
 61  
             search. At this time dozer dosen't support mappings from indexed fields from POJO.
 62  
 
 63  
             See KnownFailures.testIndexedGetFailure()
 64  
           */
 65  
           // TODO Disables for now as it breaks indexed array mapping
 66  
           //          if (descriptors[i] instanceof IndexedPropertyDescriptor) {
 67  
           //            continue;
 68  
           //          }
 69  
 
 70  1009215
           String propertyName = descriptors[i].getName();
 71  1009215
           Method readMethod = descriptors[i].getReadMethod();
 72  1009215
           if (fieldName.equals(propertyName)) {
 73  325580
             return fixGenericDescriptor(objectClass, descriptors[i]);
 74  
           }
 75  
 
 76  683635
           if (fieldName.equalsIgnoreCase(propertyName)) {
 77  144
             result = descriptors[i];
 78  
           }
 79  
         }
 80  
       }
 81  
     }
 82  
 
 83  15140
     return result;
 84  
   }
 85  
 
 86  
         /**
 87  
          * There are some nasty bugs for introspection with generics. This method addresses those nasty bugs and tries to find proper methods if available
 88  
          *  http://bugs.sun.com/view_bug.do?bug_id=6788525
 89  
          *  http://bugs.sun.com/view_bug.do?bug_id=6528714
 90  
          * @param descriptor
 91  
          * @return
 92  
          */
 93  
     private static PropertyDescriptor fixGenericDescriptor(Class<?> clazz, PropertyDescriptor descriptor) {
 94  325580
       Method readMethod = descriptor.getReadMethod();
 95  
 
 96  325580
       if(readMethod != null && (readMethod.isBridge() || readMethod.isSynthetic())) {
 97  0
         String propertyName = descriptor.getName();
 98  
         //capitalize the first letter of the string;
 99  0
         String baseName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
 100  0
         String setMethodName = "set" + baseName;
 101  0
         String getMethodName = "get" + baseName;
 102  0
         Method getMethod = findNonSyntheticMethod(getMethodName, clazz);
 103  0
         Method setMethod = findNonSyntheticMethod(setMethodName, clazz);
 104  
         try {
 105  0
           return new PropertyDescriptor(propertyName, getMethod, setMethod);
 106  0
         } catch (IntrospectionException e) {
 107  
           //move on
 108  
         }
 109  
       }
 110  325580
       return descriptor;
 111  
     }
 112  
 
 113  
     private static Method findNonSyntheticMethod(String methodName, Class<?> clazz) {
 114  0
       Method[] methods = clazz.getMethods();
 115  0
       for (Method method : methods) {
 116  0
         if(method.getName().equals(methodName) && !method.isBridge() && !method.isSynthetic() ) {
 117  0
           return method;
 118  
         }
 119  
       }
 120  0
       return null;
 121  
     }
 122  
 
 123  
     public static DeepHierarchyElement[] getDeepFieldHierarchy(Class<?> parentClass, String field,
 124  
       HintContainer deepIndexHintContainer) {
 125  136022
     if (!MappingUtils.isDeepMapping(field)) {
 126  1
       MappingUtils.throwMappingException("Field does not contain deep field delimitor");
 127  
     }
 128  
 
 129  136021
     StringTokenizer toks = new StringTokenizer(field, DozerConstants.DEEP_FIELD_DELIMITER);
 130  136021
     Class<?> latestClass = parentClass;
 131  136021
     DeepHierarchyElement[] hierarchy = new DeepHierarchyElement[toks.countTokens()];
 132  136021
     int index = 0;
 133  136021
     int hintIndex = 0;
 134  408553
     while (toks.hasMoreTokens()) {
 135  272628
       String aFieldName = toks.nextToken();
 136  272628
       String theFieldName = aFieldName;
 137  272628
       int collectionIndex = -1;
 138  
 
 139  272628
       if (aFieldName.contains("[")) {
 140  434
         theFieldName = aFieldName.substring(0, aFieldName.indexOf("["));
 141  434
         collectionIndex = Integer.parseInt(aFieldName.substring(aFieldName.indexOf("[") + 1, aFieldName.indexOf("]")));
 142  
       }
 143  
 
 144  272628
       PropertyDescriptor propDescriptor = findPropertyDescriptor(latestClass, theFieldName, deepIndexHintContainer);
 145  272628
       DeepHierarchyElement r = new DeepHierarchyElement(propDescriptor, collectionIndex);
 146  
 
 147  272628
       if (propDescriptor == null) {
 148  25
         MappingUtils.throwMappingException("Exception occurred determining deep field hierarchy for Class --> "
 149  
             + parentClass.getName() + ", Field --> " + field + ".  Unable to determine property descriptor for Class --> "
 150  
             + latestClass.getName() + ", Field Name: " + aFieldName);
 151  
       }
 152  
 
 153  272603
       latestClass = propDescriptor.getPropertyType();
 154  272603
       if (toks.hasMoreTokens()) {
 155  136678
         if (latestClass.isArray()) {
 156  130
           latestClass = latestClass.getComponentType();
 157  136548
         } else if (Collection.class.isAssignableFrom(latestClass)) {
 158  304
           Class<?> genericType = determineGenericsType(parentClass.getClass(), propDescriptor);
 159  
 
 160  304
           if (genericType == null && deepIndexHintContainer == null) {
 161  59
             MappingUtils
 162  
                 .throwMappingException("Hint(s) or Generics not specified.  Hint(s) or Generics must be specified for deep mapping with indexed field(s). Exception occurred determining deep field hierarchy for Class --> "
 163  
                     + parentClass.getName()
 164  
                     + ", Field --> "
 165  
                     + field
 166  
                     + ".  Unable to determine property descriptor for Class --> "
 167  
                     + latestClass.getName() + ", Field Name: " + aFieldName);
 168  
           }
 169  245
           if (genericType != null) {
 170  8
             latestClass = genericType;
 171  
           } else {
 172  237
             latestClass = deepIndexHintContainer.getHint(hintIndex);
 173  225
             hintIndex += 1;
 174  
           }
 175  
         }
 176  
       }
 177  272532
       hierarchy[index++] = r;
 178  272532
     }
 179  
 
 180  135925
     return hierarchy;
 181  
   }
 182  
 
 183  
   public static Method getMethod(Object obj, String methodName) {
 184  1
     return getMethod(obj.getClass(), methodName);
 185  
   }
 186  
 
 187  
   public static Method getMethod(Class<?> clazz, String methodName) {
 188  1
     Method result = findMethod(clazz, methodName);
 189  1
     if (result == null) {
 190  1
       MappingUtils.throwMappingException("No method found for class:" + clazz + " and method name:" + methodName);
 191  
     }
 192  0
     return result;
 193  
   }
 194  
 
 195  
   private static Method findMethod(Class<?> clazz, String methodName) {
 196  2861
     Method[] methods = clazz.getMethods();
 197  2861
     Method result = null;
 198  65365
     for (Method method : methods) {
 199  62504
       if (method.getName().equals(methodName)) {
 200  2857
         result = method;
 201  
       }
 202  
     }
 203  2861
     return result;
 204  
   }
 205  
 
 206  
   /**
 207  
    * Find a method with concrete string representation of it's parameters
 208  
    * @param clazz clazz to search
 209  
    * @param methodName name of method with representation of it's parameters
 210  
    * @return found method
 211  
    * @throws NoSuchMethodException
 212  
    */
 213  
   public static Method findAMethod(Class<?> clazz, String methodName) throws NoSuchMethodException {
 214  2876
     StringTokenizer tokenizer = new StringTokenizer(methodName, "(");
 215  2876
     String m = tokenizer.nextToken();
 216  
     Method result;
 217  
     // If tokenizer has more elements, it mean that parameters may have been specified
 218  2876
     if (tokenizer.hasMoreElements()) {
 219  16
       StringTokenizer tokens = new StringTokenizer(tokenizer.nextToken(), ")");
 220  16
       String params = (tokens.hasMoreTokens() ? tokens.nextToken() : null);
 221  16
       result = findMethodWithParam(clazz, m, params);
 222  15
     } else {
 223  2860
       result = findMethod(clazz, methodName);
 224  
     }
 225  2875
     if (result == null) {
 226  3
       throw new NoSuchMethodException(clazz.getName() + "." + methodName);
 227  
     }
 228  2872
     return result;
 229  
   }
 230  
 
 231  
   private static Method findMethodWithParam(Class<?> parentDestClass, String methodName, String params)
 232  
       throws NoSuchMethodException {
 233  16
     List<Class<?>> list = new ArrayList<Class<?>>();
 234  16
     if (params != null) {
 235  14
       StringTokenizer tokenizer = new StringTokenizer(params, ",");
 236  28
       while (tokenizer.hasMoreTokens()) {
 237  14
         String token = tokenizer.nextToken();
 238  14
         list.add(MappingUtils.loadClass(token));
 239  14
       }
 240  
     }
 241  16
     return getMethod(parentDestClass, methodName, list.toArray(new Class[list.size()]));
 242  
   }
 243  
 
 244  
   public static PropertyDescriptor[] getPropertyDescriptors(Class<?> objectClass) {
 245  
     // If the class is an interface, use custom method to get all prop descriptors in the inheritance hierarchy.
 246  
     // PropertyUtils.getPropertyDescriptors() does not work correctly for interface inheritance. It finds props in the
 247  
     // actual interface ok, but does not find props in the inheritance hierarchy.
 248  374214
     if (objectClass.isInterface()) {
 249  1242
       return getInterfacePropertyDescriptors(objectClass);
 250  
     } else {
 251  372972
       return PropertyUtils.getPropertyDescriptors(objectClass);
 252  
     }
 253  
   }
 254  
 
 255  
   static PropertyDescriptor[] getInterfacePropertyDescriptors(Class<?> interfaceClass) {
 256  1907
     List<PropertyDescriptor> propDescriptors = new ArrayList<PropertyDescriptor>();
 257  
     // Add prop descriptors for interface passed in
 258  1907
     propDescriptors.addAll(Arrays.asList(PropertyUtils.getPropertyDescriptors(interfaceClass)));
 259  
 
 260  
     // Look for interface inheritance. If super interfaces are found, recurse up the hierarchy tree and add prop
 261  
     // descriptors for each interface found.
 262  
     // PropertyUtils.getPropertyDescriptors() does not correctly walk the inheritance hierarchy for interfaces.
 263  1907
     Class<?>[] interfaces = interfaceClass.getInterfaces();
 264  1907
     if (interfaces != null) {
 265  2569
       for (Class<?> superInterfaceClass : interfaces) {
 266  662
         List<PropertyDescriptor> superInterfacePropertyDescriptors = Arrays
 267  
             .asList(getInterfacePropertyDescriptors(superInterfaceClass));
 268  
         /*
 269  
          * #1814758
 270  
          * Check for existing descriptor with the same name to prevent 2 property descriptors with the same name being added
 271  
          * to the result list.  This caused issues when getter and setter of an attribute on different interfaces in
 272  
          * an inheritance hierarchy
 273  
          */
 274  662
         for (PropertyDescriptor superPropDescriptor : superInterfacePropertyDescriptors) {
 275  1876
           PropertyDescriptor existingPropDescriptor = findPropDescriptorByName(propDescriptors, superPropDescriptor.getName());
 276  1876
           if (existingPropDescriptor == null) {
 277  1604
             propDescriptors.add(superPropDescriptor);
 278  
           } else {
 279  
             try {
 280  272
               if (existingPropDescriptor.getReadMethod() == null) {
 281  2
                 existingPropDescriptor.setReadMethod(superPropDescriptor.getReadMethod());
 282  
               }
 283  272
               if (existingPropDescriptor.getWriteMethod() == null) {
 284  0
                 existingPropDescriptor.setWriteMethod(superPropDescriptor.getWriteMethod());
 285  
               }
 286  0
             } catch (IntrospectionException e) {
 287  0
               throw new MappingException(e);
 288  272
             }
 289  
 
 290  
           }
 291  1876
         }
 292  
       }
 293  
     }
 294  1907
     return propDescriptors.toArray(new PropertyDescriptor[propDescriptors.size()]);
 295  
   }
 296  
 
 297  
   private static PropertyDescriptor findPropDescriptorByName(List<PropertyDescriptor> propDescriptors, String name) {
 298  1876
     PropertyDescriptor result = null;
 299  1876
     for (PropertyDescriptor pd : propDescriptors) {
 300  10479
       if (pd.getName().equals(name)) {
 301  272
         result = pd;
 302  272
         break;
 303  
       }
 304  10207
     }
 305  1876
     return result;
 306  
   }
 307  
 
 308  
   public static Field getFieldFromBean(Class<?> clazz, String fieldName) {
 309  738
     return getFieldFromBean(clazz, fieldName, clazz);
 310  
   }
 311  
 
 312  
   private static Field getFieldFromBean(Class<?> clazz, String fieldName, final Class<?> originalClass) {
 313  
     try {
 314  841
       Field field = clazz.getDeclaredField(fieldName);
 315  
       // Allow access to private instance var's that dont have public setter.
 316  736
       field.setAccessible(true);
 317  736
       return field;
 318  105
     } catch (NoSuchFieldException e) {
 319  105
       if (clazz.getSuperclass() != null) {
 320  103
         return getFieldFromBean(clazz.getSuperclass(), fieldName, originalClass);
 321  
       }
 322  2
       throw new MappingException("No such field found " + originalClass.getName() + "." + fieldName, e);
 323  
     }
 324  
   }
 325  
 
 326  
   public static Object invoke(Method method, Object obj, Object[] args) {
 327  840191
     Object result = null;
 328  
     try {
 329  840191
       result = method.invoke(obj, args);
 330  1
     } catch (IllegalArgumentException e) {
 331  
 
 332  1
       if (e.getMessage().equals(IAE_MESSAGE)) {
 333  1
         MappingUtils.throwMappingException(prepareExceptionMessage(method, args), e);
 334  
       }
 335  0
       MappingUtils.throwMappingException(e);
 336  0
     } catch (IllegalAccessException e) {
 337  0
       MappingUtils.throwMappingException(e);
 338  4
     } catch (InvocationTargetException e) {
 339  4
       MappingUtils.throwMappingException(e);
 340  840186
     }
 341  840186
     return result;
 342  
   }
 343  
 
 344  
   private static String prepareExceptionMessage(Method method, Object[] args) {
 345  1
     StringBuffer message = new StringBuffer("Illegal object type for the method '" + method.getName() + "'. \n ");
 346  1
     message.append("Expected types: \n");
 347  2
     for (Class<?> type : method.getParameterTypes()) {
 348  1
       message.append(type.getName());
 349  
     }
 350  1
     message.append("\n Actual types: \n");
 351  2
     for (Object param : args) {
 352  1
       message.append(param.getClass().getName());
 353  
     }
 354  1
     return message.toString();
 355  
   }
 356  
 
 357  
   public static Method getMethod(Class<?> clazz, String name, Class<?>[] parameterTypes) throws NoSuchMethodException {
 358  59
     return clazz.getMethod(name, parameterTypes);
 359  
   }
 360  
 
 361  
   public static <T> T newInstance(Class<T> clazz) {
 362  164
     T result = null;
 363  
     try {
 364  164
       result = clazz.newInstance();
 365  0
     } catch (InstantiationException e) {
 366  0
       MappingUtils.throwMappingException(e);
 367  0
     } catch (IllegalAccessException e) {
 368  0
       MappingUtils.throwMappingException(e);
 369  164
     }
 370  164
     return result;
 371  
   }
 372  
 
 373  
   public static Class<?> determineGenericsType(Class<?> parentClazz, PropertyDescriptor propDescriptor) {
 374  320
     Class<?> result = null;
 375  
     //Try getter and setter to determine the Generics type in case one does not exist
 376  320
     if (propDescriptor.getWriteMethod() != null) {
 377  320
       result = determineGenericsType(parentClazz, propDescriptor.getWriteMethod(), false);
 378  
     }
 379  
 
 380  320
     if (result == null && propDescriptor.getReadMethod() != null) {
 381  308
       result = determineGenericsType(parentClazz, propDescriptor.getReadMethod(), true);
 382  
     }
 383  
 
 384  320
     return result;
 385  
   }
 386  
 
 387  
   public static Class<?> determineGenericsType(Class<?> clazz, Method method, boolean isReadMethod) {
 388  1079
     Class<?> result = null;
 389  1079
     if (isReadMethod) {
 390  308
       Type parameterType = method.getGenericReturnType();
 391  308
       result = determineGenericsType(parameterType);
 392  308
     } else {
 393  771
       Type[] parameterTypes = method.getGenericParameterTypes();
 394  771
       if (parameterTypes != null) {
 395  771
         result = determineGenericsType(parameterTypes[0]);
 396  
       }
 397  
     }
 398  
 
 399  1079
     if(result == null) {
 400  
       // Have a look at generic superclass
 401  1037
       Type genericSuperclass = clazz.getGenericSuperclass();
 402  1037
       if (genericSuperclass != null) {
 403  1037
         result = determineGenericsType(genericSuperclass);
 404  
       }
 405  
     }
 406  
 
 407  1079
     return result;
 408  
   }
 409  
 
 410  
   public static Class<?> determineGenericsType(Type type) {
 411  2120
     Class<?> result = null;
 412  2120
     if (type != null && ParameterizedType.class.isAssignableFrom(type.getClass())) {
 413  44
       Type genericType = ((ParameterizedType) type).getActualTypeArguments()[0];
 414  44
       if (genericType != null) {
 415  44
          if(! (genericType instanceof TypeVariable)) {
 416  43
             result = (Class<?>) genericType;
 417  
          }
 418  
       }
 419  
     }
 420  2120
     return result;
 421  
   }
 422  
 
 423  
   public static Method getNonVoidSetter(Class<?> clazz, String fieldName) {
 424  9638
     String methodName = "set" + StringUtils.capitalize(fieldName);
 425  199922
     for (Method method : clazz.getMethods()) {
 426  190297
       if (method.getName().equals(methodName) && method.getParameterTypes().length == 1 && method.getReturnType() != Void.TYPE) {
 427  13
         return method;
 428  
       }
 429  
     }
 430  9625
     return null;
 431  
   }
 432  
 
 433  
 }