001    /*******************************************************************************
002     * Portions created by Sebastian Thomschke are copyright (c) 2005-2013 Sebastian
003     * Thomschke.
004     *
005     * All Rights Reserved. This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     *
010     * Contributors:
011     *     Sebastian Thomschke - initial implementation.
012     *******************************************************************************/
013    package net.sf.oval.internal.util;
014    
015    import static net.sf.oval.Validator.getCollectionFactory;
016    
017    import java.lang.annotation.Annotation;
018    import java.lang.reflect.Field;
019    import java.lang.reflect.InvocationTargetException;
020    import java.lang.reflect.Member;
021    import java.lang.reflect.Method;
022    import java.lang.reflect.Modifier;
023    import java.lang.reflect.ReflectPermission;
024    import java.security.AccessController;
025    import java.util.HashSet;
026    import java.util.List;
027    import java.util.Locale;
028    import java.util.Set;
029    
030    import net.sf.oval.exception.AccessingFieldValueFailedException;
031    import net.sf.oval.exception.ConstraintsViolatedException;
032    import net.sf.oval.exception.InvokingMethodFailedException;
033    import net.sf.oval.exception.ReflectionException;
034    import net.sf.oval.internal.ContextCache;
035    import net.sf.oval.internal.Log;
036    
037    /**
038     * @author Sebastian Thomschke
039     */
040    public final class ReflectionUtils
041    {
042            private static final Log LOG = Log.getLog(ReflectionUtils.class);
043    
044            private static final ReflectPermission SUPPRESS_ACCESS_CHECKS_PERMISSION = new ReflectPermission("suppressAccessChecks");
045    
046            /**
047             * @throws SecurityException
048             */
049            public static void assertPrivateAccessAllowed()
050            {
051                    final SecurityManager manager = System.getSecurityManager();
052                    if (manager != null)
053                            try
054                            {
055                                    manager.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION);
056                            }
057                            catch (final SecurityException ex)
058                            {
059                                    throw new ReflectionException(
060                                                    "Current security manager configuration does not allow access to private fields and methods.", ex);
061                            }
062            }
063    
064            /**
065             * Returns all annotations present on this class.
066             * @param clazz the class to inspect
067             * @param inspectInterfaces whether to also return annotations declared on interface declaration
068             * @return all annotations present on this class.
069             */
070            public static Annotation[] getAnnotations(final Class< ? > clazz, final boolean inspectInterfaces)
071            {
072                    if (!inspectInterfaces) return clazz.getAnnotations();
073    
074                    final List<Annotation> annotations = ArrayUtils.asList(clazz.getAnnotations());
075                    for (final Class< ? > next : ReflectionUtils.getInterfacesRecursive(clazz))
076                    {
077                            final Annotation[] declaredAnnotations = next.getDeclaredAnnotations();
078                            annotations.addAll(ArrayUtils.asList(declaredAnnotations));
079                    }
080                    return annotations.toArray(new Annotation[annotations.size()]);
081            }
082    
083            /**
084             * Returns all annotations present on this method.
085             * @param method the method to inspect
086             * @param inspectInterfaces whether to also return annotations declared on interface method declaration
087             * @return all annotations present on this method.
088             */
089            public static Annotation[] getAnnotations(final Method method, final boolean inspectInterfaces)
090            {
091                    if (!inspectInterfaces || !isPublic(method)) return method.getAnnotations();
092    
093                    final String methodName = method.getName();
094                    final Class< ? >[] methodParameterTypes = method.getParameterTypes();
095    
096                    final List<Annotation> annotations = ArrayUtils.asList(method.getAnnotations());
097                    for (final Class< ? > nextClass : ReflectionUtils.getInterfacesRecursive(method.getDeclaringClass()))
098                            try
099                            {
100                                    ArrayUtils.addAll(annotations, nextClass.getDeclaredMethod(methodName, methodParameterTypes).getDeclaredAnnotations());
101                            }
102                            catch (final NoSuchMethodException e)
103                            {
104                                    // ignore
105                            }
106                    return annotations.toArray(new Annotation[annotations.size()]);
107            }
108    
109            /**
110             * @return the field or null if the field does not exist
111             */
112            public static Field getField(final Class< ? > clazz, final String fieldName)
113            {
114                    try
115                    {
116                            return clazz.getDeclaredField(fieldName);
117                    }
118                    catch (final NoSuchFieldException e)
119                    {
120                            return null;
121                    }
122            }
123    
124            /**
125             * @param setter
126             * @return the corresponding field for a setter method. Returns null if the method is not a
127             * JavaBean style setter or the field could not be located.
128             */
129            public static Field getFieldForSetter(final Method setter)
130            {
131                    if (!isSetter(setter)) return null;
132    
133                    final Class< ? >[] methodParameterTypes = setter.getParameterTypes();
134                    final String methodName = setter.getName();
135                    final Class< ? > clazz = setter.getDeclaringClass();
136    
137                    // calculate the corresponding field name based on the name of the setter method (e.g. method setName() => field
138                    // name)
139                    String fieldName = methodName.substring(3, 4).toLowerCase(Locale.getDefault());
140                    if (methodName.length() > 4) fieldName += methodName.substring(4);
141    
142                    Field field = null;
143                    try
144                    {
145                            field = clazz.getDeclaredField(fieldName);
146    
147                            // check if field and method parameter are of the same type
148                            if (!field.getType().equals(methodParameterTypes[0]))
149                            {
150                                    LOG.warn("Found field <{1}> in class <{2}>that matches setter <{3}> name, but mismatches parameter type.", fieldName,
151                                                    clazz.getName(), methodName);
152                                    field = null;
153                            }
154                    }
155                    catch (final NoSuchFieldException e)
156                    {
157                            LOG.debug("Field not found", e);
158                    }
159    
160                    // if method parameter type is boolean then check if a field with name isXXX exists (e.g. method setEnabled() =>
161                    // field isEnabled)
162                    if (field == null && (boolean.class.equals(methodParameterTypes[0]) || Boolean.class.equals(methodParameterTypes[0])))
163                    {
164                            fieldName = "is" + methodName.substring(3);
165    
166                            try
167                            {
168                                    field = clazz.getDeclaredField(fieldName);
169    
170                                    // check if found field is of boolean or Boolean
171                                    if (!boolean.class.equals(field.getType()) && Boolean.class.equals(field.getType()))
172                                    {
173                                            LOG.warn("Found field <{1}> in class <{2}>that matches setter <{3}> name, but mismatches parameter type.", fieldName,
174                                                            clazz.getName(), methodName);
175                                            field = null;
176                                    }
177                            }
178                            catch (final NoSuchFieldException ex)
179                            {
180                                    LOG.debug("Field not found", ex);
181                            }
182                    }
183    
184                    return field;
185            }
186    
187            public static Field getFieldRecursive(final Class< ? > clazz, final String fieldName)
188            {
189                    final Field f = getField(clazz, fieldName);
190                    if (f != null) return f;
191    
192                    final Class< ? > superclazz = clazz.getSuperclass();
193                    if (superclazz == null) return null;
194    
195                    return getFieldRecursive(superclazz, fieldName);
196            }
197    
198            public static Object getFieldValue(final Field field, final Object target) throws AccessingFieldValueFailedException
199            {
200                    try
201                    {
202                            if (!field.isAccessible()) AccessController.doPrivileged(new SetAccessibleAction(field));
203                            return field.get(target);
204                    }
205                    catch (final Exception ex)
206                    {
207                            throw new AccessingFieldValueFailedException(field.getName(), target, ContextCache.getFieldContext(field), ex);
208                    }
209            }
210    
211            public static Method getGetter(final Class< ? > clazz, final String propertyName)
212            {
213                    final String appendix = propertyName.substring(0, 1).toUpperCase(Locale.getDefault()) + propertyName.substring(1);
214                    try
215                    {
216                            return clazz.getDeclaredMethod("get" + appendix);
217                    }
218                    catch (final NoSuchMethodException ex)
219                    {
220                            LOG.trace("getXXX method not found.", ex);
221                    }
222                    try
223                    {
224                            return clazz.getDeclaredMethod("is" + appendix);
225                    }
226                    catch (final NoSuchMethodException ex)
227                    {
228                            LOG.trace("isXXX method not found.", ex);
229                            return null;
230                    }
231            }
232    
233            public static Method getGetterRecursive(final Class< ? > clazz, final String propertyName)
234            {
235                    final Method m = getGetter(clazz, propertyName);
236                    if (m != null) return m;
237    
238                    final Class< ? > superclazz = clazz.getSuperclass();
239                    if (superclazz == null) return null;
240    
241                    return getGetterRecursive(superclazz, propertyName);
242            }
243    
244            public static List<Method> getInterfaceMethods(final Method method)
245            {
246                    // static methods cannot be overridden
247                    if (isStatic(method)) return null;
248    
249                    final Class< ? >[] interfaces = method.getDeclaringClass().getInterfaces();
250                    if (interfaces.length == 0) return null;
251    
252                    final String methodName = method.getName();
253                    final Class< ? >[] parameterTypes = method.getParameterTypes();
254    
255                    final List<Method> methods = getCollectionFactory().createList(interfaces.length);
256                    for (final Class< ? > iface : interfaces)
257                    {
258                            final Method m = getMethod(iface, methodName, parameterTypes);
259                            if (m != null) methods.add(m);
260                    }
261                    return methods;
262            }
263    
264            /**
265             * @param clazz the class to inspect
266             * @return a set with all implemented interfaces
267             */
268            public static Set<Class< ? >> getInterfacesRecursive(final Class< ? > clazz)
269            {
270                    final Set<Class< ? >> interfaces = getCollectionFactory().createSet(2);
271                    return getInterfacesRecursive(clazz, interfaces);
272            }
273    
274            private static Set<Class< ? >> getInterfacesRecursive(Class< ? > clazz, final Set<Class< ? >> interfaces)
275            {
276                    while (clazz != null)
277                    {
278                            for (final Class< ? > next : clazz.getInterfaces())
279                            {
280                                    interfaces.add(next);
281                                    getInterfacesRecursive(next, interfaces);
282                            }
283                            clazz = clazz.getSuperclass();
284                    }
285                    return interfaces;
286            }
287    
288            /**
289             * @return the method or null if the method does not exist
290             */
291            public static Method getMethod(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes)
292            {
293                    try
294                    {
295                            return clazz.getDeclaredMethod(methodName, parameterTypes);
296                    }
297                    catch (final NoSuchMethodException e)
298                    {
299                            return null;
300                    }
301            }
302    
303            /**
304             * @return the method or null if the method does not exist
305             */
306            public static Method getMethodRecursive(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes)
307            {
308                    final Method m = getMethod(clazz, methodName, parameterTypes);
309                    if (m != null) return m;
310    
311                    final Class< ? > superclazz = clazz.getSuperclass();
312                    if (superclazz == null) return null;
313    
314                    return getMethodRecursive(superclazz, methodName, parameterTypes);
315            }
316    
317            /**
318             * Returns an array of arrays that represent the annotations on the formal parameters, in declaration order,
319             * of the method represented by this method.
320             *
321             * @param method the method to inspect
322             * @param inspectInterfaces whether to also return annotations declared on interface method declaration
323             * @return an array of arrays that represent the annotations on the formal parameters, in declaration order,
324             * of the method represented by this method.
325             */
326            public static Annotation[][] getParameterAnnotations(final Method method, final boolean inspectInterfaces)
327            {
328                    if (!inspectInterfaces || !isPublic(method)) return method.getParameterAnnotations();
329    
330                    final String methodName = method.getName();
331                    final Class< ? >[] methodParameterTypes = method.getParameterTypes();
332                    final int methodParameterTypesCount = methodParameterTypes.length;
333    
334                    @SuppressWarnings("unchecked")
335                    final HashSet<Annotation>[] methodParameterAnnotations = new HashSet[methodParameterTypesCount];
336    
337                    final Class< ? > clazz = method.getDeclaringClass();
338                    final Set<Class< ? >> classes = ReflectionUtils.getInterfacesRecursive(clazz);
339                    classes.add(clazz);
340                    for (final Class< ? > nextClass : classes)
341                            try
342                            {
343                                    final Method nextMethod = nextClass.getDeclaredMethod(methodName, methodParameterTypes);
344                                    for (int i = 0; i < methodParameterTypesCount; i++)
345                                    {
346                                            final Annotation[] paramAnnos = nextMethod.getParameterAnnotations()[i];
347                                            if (paramAnnos.length > 0)
348                                            {
349                                                    HashSet<Annotation> cummulatedParamAnnos = methodParameterAnnotations[i];
350                                                    if (cummulatedParamAnnos == null) methodParameterAnnotations[i] = cummulatedParamAnnos = new HashSet<Annotation>();
351                                                    for (final Annotation anno : paramAnnos)
352                                                            cummulatedParamAnnos.add(anno);
353                                            }
354                                    }
355                            }
356                            catch (final NoSuchMethodException e)
357                            {
358                                    // ignore
359                            }
360    
361                    final Annotation[][] result = new Annotation[methodParameterTypesCount][];
362                    for (int i = 0; i < methodParameterTypesCount; i++)
363                    {
364                            final HashSet<Annotation> paramAnnos = methodParameterAnnotations[i];
365                            result[i] = paramAnnos == null ? new Annotation[0] : methodParameterAnnotations[i]
366                                            .toArray(new Annotation[methodParameterAnnotations[i].size()]);
367    
368                    }
369                    return result;
370            }
371    
372            public static Method getSetter(final Class< ? > clazz, final String propertyName)
373            {
374                    final String methodName = "set" + propertyName.substring(0, 1).toUpperCase(Locale.getDefault()) + propertyName.substring(1);
375    
376                    final Method[] declaredMethods = clazz.getDeclaredMethods();
377                    for (final Method method : declaredMethods)
378                            if (methodName.equals(method.getName()) && method.getParameterTypes().length == 1) return method;
379                    LOG.trace("No setter for {} not found on class {}.", propertyName, clazz);
380                    return null;
381            }
382    
383            public static Method getSetterRecursive(final Class< ? > clazz, final String propertyName)
384            {
385                    final Method m = getSetter(clazz, propertyName);
386                    if (m != null) return m;
387    
388                    final Class< ? > superclazz = clazz.getSuperclass();
389                    if (superclazz == null) return null;
390    
391                    return getSetterRecursive(superclazz, propertyName);
392            }
393    
394            public static Method getSuperMethod(final Method method)
395            {
396                    // static methods cannot be overridden
397                    if (isStatic(method)) return null;
398    
399                    final String methodName = method.getName();
400                    final Class< ? >[] parameterTypes = method.getParameterTypes();
401    
402                    Class< ? > currentClass = method.getDeclaringClass();
403    
404                    while (currentClass != null && currentClass != Object.class)
405                    {
406                            currentClass = currentClass.getSuperclass();
407    
408                            final Method m = getMethod(currentClass, methodName, parameterTypes);
409                            if (m != null && !isPrivate(m)) return m;
410                    }
411                    return null;
412            }
413    
414            public static String guessFieldName(final Method getter)
415            {
416                    String fieldName = getter.getName();
417    
418                    if (fieldName.startsWith("get") && fieldName.length() > 3)
419                    {
420                            fieldName = fieldName.substring(3);
421                            if (fieldName.length() == 1)
422                                    fieldName = fieldName.toLowerCase(Locale.getDefault());
423                            else
424                                    fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
425                    }
426                    else if (fieldName.startsWith("is") && fieldName.length() > 2)
427                    {
428                            fieldName = fieldName.substring(2);
429                            if (fieldName.length() == 1)
430                                    fieldName = fieldName.toLowerCase(Locale.getDefault());
431                            else
432                                    fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
433                    }
434    
435                    return fieldName;
436            }
437    
438            public static boolean hasField(final Class< ? > clazz, final String fieldName)
439            {
440                    return getField(clazz, fieldName) != null;
441            }
442    
443            public static boolean hasMethod(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes)
444            {
445                    return getMethod(clazz, methodName, parameterTypes) != null;
446            }
447    
448            /**
449             *
450             * @param method the method to invoke
451             * @param obj the object on which to invoke the method
452             * @param args the method arguments
453             * @return the return value of the invoked method
454             * @throws InvokingMethodFailedException
455             */
456            @SuppressWarnings("unchecked")
457            public static <T> T invokeMethod(final Method method, final Object obj, final Object... args) throws InvokingMethodFailedException,
458                            ConstraintsViolatedException
459            {
460                    try
461                    {
462                            if (!method.isAccessible()) AccessController.doPrivileged(new SetAccessibleAction(method));
463                            return (T) method.invoke(obj, args);
464                    }
465                    catch (final Exception ex)
466                    {
467                            if (ex.getCause() instanceof ConstraintsViolatedException) throw (ConstraintsViolatedException) ex.getCause();
468                            throw new InvokingMethodFailedException("Executing method " + method.getName() + " failed.", obj,
469                                            ContextCache.getMethodReturnValueContext(method), ex);
470                    }
471            }
472    
473            /**
474             * Returns true if an annotation for the specified type is present on this method, else false.
475             *
476             * @param method the method to inspect
477             * @param annotationClass the Class object corresponding to the annotation type
478             * @param inspectInterfaces whether to also check annotations declared on interface method declaration
479             * @return true if an annotation for the specified annotation type is present on this method, else false
480             */
481            public static boolean isAnnotationPresent(final Method method, final Class< ? extends Annotation> annotationClass,
482                            final boolean inspectInterfaces)
483            {
484                    if (method.isAnnotationPresent(annotationClass)) return true;
485    
486                    if (!inspectInterfaces || !isPublic(method)) return false;
487    
488                    final String methodName = method.getName();
489                    final Class< ? >[] methodParameterTypes = method.getParameterTypes();
490    
491                    for (final Class< ? > next : getInterfacesRecursive(method.getDeclaringClass()))
492                            try
493                            {
494                                    if (next.getDeclaredMethod(methodName, methodParameterTypes).isAnnotationPresent(annotationClass)) return true;
495                            }
496                            catch (final NoSuchMethodException e)
497                            {
498                                    // ignore
499                            }
500                    return false;
501            }
502    
503            public static boolean isClassPresent(final String className)
504            {
505                    try
506                    {
507                            Class.forName(className);
508                            return true;
509                    }
510                    catch (final ClassNotFoundException e)
511                    {
512                            return false;
513                    }
514            }
515    
516            public static boolean isFinal(final Member member)
517            {
518                    return (member.getModifiers() & Modifier.FINAL) != 0;
519            }
520    
521            /**
522             * determines if a method is a JavaBean style getter method
523             */
524            public static boolean isGetter(final Method method)
525            {
526                    return method.getParameterTypes().length == 0 && (method.getName().startsWith("is") || method.getName().startsWith("get"));
527            }
528    
529            // public Constructor getDeclaredConstructorOfNonStaticInnerClass(Class)
530            public static boolean isNonStaticInnerClass(final Class< ? > clazz)
531            {
532                    return clazz.getName().indexOf('$') > -1 && (clazz.getModifiers() & Modifier.STATIC) == 0;
533            }
534    
535            public static boolean isPackage(final Member member)
536            {
537                    return (member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0;
538            }
539    
540            public static boolean isPrivate(final Member member)
541            {
542                    return (member.getModifiers() & Modifier.PRIVATE) != 0;
543            }
544    
545            public static boolean isPrivateAccessAllowed()
546            {
547                    final SecurityManager manager = System.getSecurityManager();
548                    if (manager != null) try
549                    {
550                            manager.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION);
551                    }
552                    catch (final SecurityException ex)
553                    {
554                            return false;
555                    }
556                    return true;
557            }
558    
559            public static boolean isProtected(final Member member)
560            {
561                    return (member.getModifiers() & Modifier.PROTECTED) != 0;
562            }
563    
564            public static boolean isPublic(final Member member)
565            {
566                    return (member.getModifiers() & Modifier.PUBLIC) != 0;
567            }
568    
569            /**
570             * determines if a method is a JavaBean style setter method
571             */
572            public static boolean isSetter(final Method method)
573            {
574                    final Class< ? >[] methodParameterTypes = method.getParameterTypes();
575    
576                    // check if method has exactly one parameter
577                    if (methodParameterTypes.length != 1) return false;
578    
579                    final String methodName = method.getName();
580                    final int methodNameLen = methodName.length();
581    
582                    // check if the method's name starts with setXXX
583                    if (methodNameLen < 4 || !methodName.startsWith("set")) return false;
584    
585                    return true;
586            }
587    
588            public static boolean isStatic(final Member member)
589            {
590                    return (member.getModifiers() & Modifier.STATIC) != 0;
591            }
592    
593            public static boolean isTransient(final Member member)
594            {
595                    return (member.getModifiers() & Modifier.TRANSIENT) != 0;
596            }
597    
598            /**
599             * determines if a method is a void method
600             */
601            public static boolean isVoidMethod(final Method method)
602            {
603                    return method.getReturnType() == void.class;
604            }
605    
606            public static boolean setViaSetter(final Object target, final String propertyName, final Object propertyValue)
607            {
608                    assert target != null;
609                    assert propertyName != null;
610                    final Method setter = getSetterRecursive(target.getClass(), propertyName);
611                    if (setter != null) try
612                    {
613                            setter.invoke(target, propertyValue);
614                    }
615                    catch (final IllegalArgumentException ex)
616                    {
617                            LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex);
618                            return false;
619                    }
620                    catch (final IllegalAccessException ex)
621                    {
622                            LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex);
623                            return false;
624                    }
625                    catch (final InvocationTargetException ex)
626                    {
627                            LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex);
628                            return false;
629                    }
630                    return false;
631            }
632    
633            private ReflectionUtils()
634            {
635                    super();
636            }
637    }