001/**
002 * Copyright 2010-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util;
017
018import static com.google.common.base.Preconditions.checkArgument;
019
020import java.lang.annotation.Annotation;
021import java.lang.reflect.Field;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Modifier;
024import java.lang.reflect.ParameterizedType;
025import java.lang.reflect.Type;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031
032import org.apache.commons.beanutils.BeanUtils;
033import org.springframework.util.MethodInvoker;
034
035import com.google.common.base.Optional;
036import com.google.common.base.Preconditions;
037import com.google.common.collect.ImmutableCollection;
038import com.google.common.collect.ImmutableList;
039import com.google.common.collect.ImmutableMap;
040import com.google.common.collect.ImmutableSet;
041import com.google.common.collect.Lists;
042import com.google.common.collect.Maps;
043import com.google.common.collect.Sets;
044
045public class ReflectionUtils extends org.springframework.util.ReflectionUtils {
046
047        /**
048         * Returns the path from {@code java.lang.Object}, to {@code type}. The last element in the list is {@code type}.
049         */
050        public static List<Class<?>> getTypeHierarchy(Class<?> type) {
051                List<Class<?>> list = Lists.newArrayList();
052                if (type.getSuperclass() != null) {
053                        list.addAll(getTypeHierarchy(type.getSuperclass()));
054                }
055                list.add(type);
056                return list;
057        }
058
059        /**
060         * Return a map containing every parameterized interface implemented by {@code type}, includes interfaces implemented by super classes
061         */
062        public static Map<Class<?>, ParameterizedType> getAllParameterizedInterfaces(Class<?> type) {
063                List<Type> list = getAllGenericInterfaces(type);
064                Map<Class<?>, ParameterizedType> map = Maps.newHashMap();
065                for (Type element : list) {
066                        if (element instanceof ParameterizedType) {
067                                ParameterizedType pType = (ParameterizedType) element;
068                                Class<?> interfaceClass = (Class<?>) pType.getRawType();
069                                map.put(interfaceClass, pType);
070                        }
071                }
072                return map;
073        }
074
075        /**
076         * Return a list containing every interface implemented by {@code type}, includes interfaces implemented by super classes. Type objects returned accurately reflect the actual
077         * type parameters used in the source code.
078         */
079        public static List<Type> getAllGenericInterfaces(Class<?> type) {
080                List<Class<?>> path = getTypeHierarchy(type);
081                List<Type> list = Lists.newArrayList();
082                for (Class<?> element : path) {
083                        Type[] interfaces = element.getGenericInterfaces();
084                        list.addAll(ImmutableList.copyOf(interfaces));
085                }
086                return list;
087        }
088
089        /**
090         * Return true if this class is declared as final
091         */
092        public static boolean isFinal(Class<?> type) {
093                return Modifier.isFinal(type.getModifiers());
094        }
095
096        /**
097         * Return true if this field is declared as final
098         */
099        public static boolean isFinal(Field field) {
100                return Modifier.isFinal(field.getModifiers());
101        }
102
103        /**
104         * Return true if this class is an immutable Guava collection
105         */
106        public static boolean isImmutableGuavaCollection(Class<?> type) {
107                return ImmutableCollection.class.isAssignableFrom(type);
108        }
109
110        /**
111         * Return true if this class is an immutable Guava map
112         */
113        public static boolean isImmutableGuavaMap(Class<?> type) {
114                return ImmutableMap.class.isAssignableFrom(type);
115        }
116
117        /**
118         * Return true if this field is a {@code java.util.Collection}
119         */
120        public static boolean isCollection(Field field) {
121                return Collection.class.isAssignableFrom(field.getType());
122        }
123
124        /**
125         * Return true if this field is a {@code java.util.Collection<String>}
126         */
127        public static boolean isStringCollection(Field field) {
128                return isCollection(field) && hasMatchingParameterizedArgTypes(field, String.class);
129        }
130
131        /**
132         * Return true if this field is a {@code java.util.Map}
133         */
134        public static boolean isMap(Field field) {
135                return Map.class.isAssignableFrom(field.getType());
136        }
137
138        /**
139         * Return true if this field extends from {@code java.util.Map} and uses {@code String} for its keys
140         * 
141         * <pre>
142         * Map&lt;String,String>  returns true
143         * Map&lt;String,Object>  returns true
144         * Map&lt;String,Integer> returns true
145         * Map&lt;Integer,String> returns false
146         * </pre>
147         */
148        public static boolean isStringKeyedMap(Field field) {
149                return isMap(field) && hasMatchingParameterizedArgTypes(field, String.class);
150        }
151
152        /**
153         * Return true if this field is a {@code java.lang.String}
154         */
155        public static boolean isString(Field field) {
156                // Safe to do this since java.lang.String is final
157                return field.getType() == String.class;
158        }
159
160        /**
161         * Return true if this field is a CharSequence
162         */
163        public static boolean isCharSequence(Field field) {
164                return isCharSequence(field.getType());
165        }
166
167        /**
168         * Return true if this field is a CharSequence
169         */
170        public static boolean isCharSequence(Class<?> type) {
171                return CharSequence.class.isAssignableFrom(type);
172        }
173
174        /**
175         * Return true iff this field is a Guava {@code com.google.common.base.Optional}
176         */
177        public static boolean isOptional(Field field) {
178                return Optional.class.isAssignableFrom(field.getType());
179        }
180
181        /**
182         * Return true iff this field is a Guava {@code com.google.common.base.Optional<String>}
183         */
184        public static boolean isOptionalString(Field field) {
185                return isOptional(field) && hasMatchingParameterizedArgTypes(field, String.class);
186        }
187
188        /**
189         * <p>
190         * Return true if this field is a generic whose argument types match {@code expectedTypeArguments}
191         * </p>
192         * 
193         * For example to match a field declared as {@code Collection<String>}
194         * 
195         * <pre>
196         * hasMatchingParameterizedArgTypes(myField, String.class)
197         * </pre>
198         */
199        public static boolean hasMatchingParameterizedArgTypes(Field field, Class<?>... expectedTypeArguments) {
200                Type genericType = field.getGenericType();
201                if (genericType instanceof ParameterizedType) {
202                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
203                        return hasMatchingActualTypeArguments(parameterizedType, expectedTypeArguments);
204                } else {
205                        return false;
206                }
207        }
208
209        protected static boolean hasMatchingActualTypeArguments(ParameterizedType type, Class<?>... expectedTypeArguments) {
210                Type[] actualTypeArguments = type.getActualTypeArguments();
211                for (int i = 0; i < expectedTypeArguments.length; i++) {
212                        Class<?> expectedTypeArgument = expectedTypeArguments[i];
213                        if (i >= actualTypeArguments.length) {
214                                return false;
215                        }
216                        Class<?> actualTypeArgument = (Class<?>) actualTypeArguments[i];
217                        if (actualTypeArgument != expectedTypeArgument) {
218                                return false;
219                        }
220                }
221                return true;
222        }
223
224        /**
225         * <p>
226         * Throw an exception unless {@code child} is the same as {@code parent} <b>OR</b> descends from {@code parent}. If {@code child} is a primitive type, throw an exception unless
227         * both {@code child} and {@code parent} are the exact same primitive type.
228         * </p>
229         * 
230         * @see equalsOrDescendsFrom
231         * 
232         * @throws IllegalArgumentException
233         *             if {@code equalsOrDescendsFrom(child,parent)} returns {@code false}
234         */
235        public static void validateIsSuperType(Class<?> superType, Class<?> type) {
236                boolean expression = isSuperType(superType, type);
237                Preconditions.checkArgument(expression, "[%s] must descend from (or be) [%s]", type.getCanonicalName(), superType.getCanonicalName());
238        }
239
240        /**
241         * <p>
242         * Return true if {@code type} descends from {@code superType} <b>OR</b> is the same as {@code superType}. If {@code type} is a primitive type, return {@code true} only if both
243         * {@code type} and {@code superType} are the exact same primitive type.
244         * </p>
245         */
246        public static boolean isSuperType(Class<?> superType, Class<?> type) {
247                return superType.isAssignableFrom(type);
248        }
249
250        /**
251         * 
252         * @deprecated Use Annotations.get() instead
253         */
254        @Deprecated
255        public static <T extends Annotation> Optional<T> getAnnotation(Class<?> type, Class<T> annotationClass) {
256                return Optional.fromNullable(type.getAnnotation(annotationClass));
257        }
258
259        /**
260         * 
261         * @deprecated Use Annotations.get() instead
262         */
263        @Deprecated
264        public static <T extends Annotation> Optional<T> getAnnotation(Field field, Class<T> annotationClass) {
265                return Optional.fromNullable(field.getAnnotation(annotationClass));
266        }
267
268        public static List<Class<?>> getDeclarationHierarchy(Class<?> type) {
269                List<Class<?>> hierarchy = new ArrayList<Class<?>>();
270                Class<?> declaringClass = type.getDeclaringClass();
271                if (declaringClass != null) {
272                        hierarchy.addAll(getDeclarationHierarchy(declaringClass));
273                }
274                hierarchy.add(type);
275                return hierarchy;
276        }
277
278        public static String getDeclarationPath(Class<?> type) {
279                List<Class<?>> hierarchy = getDeclarationHierarchy(type);
280                List<String> names = new ArrayList<String>();
281                for (Class<?> element : hierarchy) {
282                        names.add(element.getSimpleName());
283                }
284                return CollectionUtils.getStringWithSeparator(names, ".");
285        }
286
287        /**
288         * Unconditionally attempt to get the value of this field on this bean. If the field is not accessible make it accessible, get the value, then revert the field back to being
289         * inaccessible.
290         */
291        public static Optional<Object> get(Field field, Object instance) {
292
293                // Be thread safe
294                synchronized (field) {
295
296                        // Preserve the original accessibility indicator
297                        boolean accessible = field.isAccessible();
298
299                        // If it's not accessible, change it so it is
300                        if (!accessible) {
301                                field.setAccessible(true);
302                        }
303
304                        try {
305                                // Attempt to get the value of this field on the instance
306                                return Optional.fromNullable(field.get(instance));
307                        } catch (IllegalAccessException e) {
308                                throw new IllegalStateException(e);
309                        } finally {
310                                // Always flip the accessible flag back to what it was before (if we need to)
311                                if (!accessible) {
312                                        field.setAccessible(false);
313                                }
314                        }
315                }
316        }
317
318        /**
319         * Unconditionally attempt to set a value on this field of this instance. If the field is not accessible, make it accessible, set the value, then revert the field to being
320         * inaccessible.
321         */
322        public static void set(Object instance, Field field, Object value) {
323
324                // Be thread safe
325                synchronized (field) {
326
327                        // Preserve the original accessibility indicator
328                        boolean accessible = field.isAccessible();
329
330                        // If it's not accessible, change it so it is
331                        if (!accessible) {
332                                field.setAccessible(true);
333                        }
334
335                        try {
336                                // Attempt to set the value on this field of the instance
337                                field.set(instance, value);
338                        } catch (IllegalAccessException e) {
339                                throw new IllegalStateException(e);
340                        } finally {
341                                // Always flip the accessible flag back to what it was before (if we need to)
342                                if (!accessible) {
343                                        field.setAccessible(false);
344                                }
345                        }
346                }
347        }
348
349        /**
350         * Get fields declared directly on this type as an immutable set.
351         */
352        public static Set<Field> getFields(Class<?> type) {
353                return ImmutableSet.copyOf(type.getDeclaredFields());
354        }
355
356        /**
357         * Get fields for a given type with the option to include all inherited fields
358         * 
359         * <p>
360         * NOTE: field.getName() is not necessarily unique for the elements in the set if includeInheritedFields is true
361         * </p>
362         */
363        public static Set<Field> getFields(Class<?> type, boolean includeInheritedFields) {
364                if (includeInheritedFields) {
365                        return getAllFields(type);
366                } else {
367                        return getFields(type);
368                }
369        }
370
371        /**
372         * Convert the set of fields into a Map keyed by field name. If there are fields that contain duplicate names in the set, which one makes it into the map is undefined
373         * 
374         * @deprecated use getNameMap(List) instead
375         */
376        @Deprecated
377        public static Map<String, Field> getNameMap(Set<Field> fields) {
378                Map<String, Field> map = Maps.newHashMap();
379                for (Field field : fields) {
380                        map.put(field.getName(), field);
381                }
382                return map;
383        }
384
385        /**
386         * Convert the list of fields into a Map keyed by field name. If there are duplicate field names in the list, "last one in wins"
387         */
388        public static Map<String, Field> getNameMap(List<Field> fields) {
389                Map<String, Field> map = Maps.newHashMap();
390                for (Field field : fields) {
391                        map.put(field.getName(), field);
392                }
393                return map;
394        }
395
396        /**
397         * Get a list of all fields contained anywhere in the type hierarchy keyed by field name.
398         * 
399         * @throws IllegalArgumentException
400         *             if {@code type} contains duplicate field names
401         */
402        public static Map<String, Field> getUniqueFieldNames(Class<?> type) {
403                Set<Field> fields = getAllFields(type);
404                checkArgument(hasUniqueFieldNames(fields), "[%s] contains duplicate field names");
405                return getNameMap(Lists.newArrayList(fields));
406        }
407
408        /**
409         * Return the field corresponding
410         * 
411         * @param type
412         * @param fieldNames
413         * @return
414         */
415        public static Map<String, Field> getFields(Class<?> type, Set<String> fieldNames) {
416                Map<String, Field> fields = Maps.newHashMap();
417                for (String fieldName : fieldNames) {
418                        try {
419                                fields.put(fieldName, type.getDeclaredField(fieldName));
420                        } catch (NoSuchFieldException e) {
421                                throw new IllegalStateException(e);
422                        } catch (SecurityException e) {
423                                throw new IllegalStateException(e);
424                        }
425                }
426                return fields;
427        }
428
429        /**
430         * <p>
431         * Recursively examine the type hierarchy and extract every field encountered anywhere in the hierarchy into an immutable set
432         * </p>
433         * 
434         * <p>
435         * NOTE: field.getName() is not necessarily unique for the elements in the set
436         * </p>
437         */
438        public static Set<Field> getAllFields(Class<?> type) {
439                Set<Field> fields = Sets.newHashSet();
440                for (Class<?> c = type; c != null; c = c.getSuperclass()) {
441                        Set<Field> set = getFields(c);
442                        fields.addAll(set);
443                }
444                return ImmutableSet.copyOf(fields);
445        }
446
447        /**
448         * <p>
449         * Recursively examine the type hierarchy and extract every field encountered anywhere in the hierarchy into an immutable list
450         * </p>
451         * 
452         * <p>
453         * NOTE: field.getName() is not necessarily unique for the elements in the list
454         * </p>
455         */
456        public static List<Field> getAllFieldsList(Class<?> type) {
457                List<Field> fields = Lists.newArrayList();
458                for (Class<?> c = type; c != null; c = c.getSuperclass()) {
459                        Set<Field> set = getFields(c);
460                        fields.addAll(set);
461                }
462                return ImmutableList.copyOf(Lists.reverse(fields));
463        }
464
465        /**
466         * Return true if every single field in the recursive type hiearchy has a unique name, false otherwise
467         */
468        public static boolean hasUniqueFieldNames(Class<?> type) {
469                return hasUniqueFieldNames(getAllFields(type));
470        }
471
472        /**
473         * Return true if the fields in this set can be uniquely represented by field name alone
474         */
475        public static boolean hasUniqueFieldNames(Set<Field> fields) {
476                return getNameMap(Lists.newArrayList(fields)).size() == fields.size();
477        }
478
479        public static boolean hasUniqueFieldNames(List<Field> fields) {
480                return getNameMap(fields).size() == fields.size();
481        }
482
483        @SuppressWarnings("unchecked")
484        public static Map<String, Object> describe(Object bean) {
485                try {
486                        return BeanUtils.describe(bean);
487                } catch (IllegalAccessException e) {
488                        throw new IllegalStateException(e);
489                } catch (InvocationTargetException e) {
490                        throw new IllegalStateException(e);
491                } catch (NoSuchMethodException e) {
492                        throw new IllegalStateException(e);
493                }
494        }
495
496        public static void copyProperty(Object bean, String name, Object value) {
497                try {
498                        BeanUtils.copyProperty(bean, name, value);
499                } catch (IllegalAccessException e) {
500                        throw new IllegalStateException(e);
501                } catch (InvocationTargetException e) {
502                        throw new IllegalStateException(e);
503                }
504        }
505
506        public static Object invokeMethod(Class<?> targetClass, String targetMethod, Object... arguments) {
507                MethodInvoker invoker = new MethodInvoker();
508                invoker.setTargetClass(targetClass);
509                invoker.setTargetMethod(targetMethod);
510                invoker.setArguments(arguments);
511                return invoke(invoker);
512        }
513
514        public static Object invokeMethod(Object targetObject, String targetMethod, Object... arguments) {
515                MethodInvoker invoker = new MethodInvoker();
516                invoker.setTargetObject(targetObject);
517                invoker.setTargetMethod(targetMethod);
518                invoker.setArguments(arguments);
519                return invoke(invoker);
520        }
521
522        public static Object invoke(MethodInvoker invoker) {
523                try {
524                        invoker.prepare();
525                        return invoker.invoke();
526                } catch (ClassNotFoundException e) {
527                        throw new IllegalStateException(e);
528                } catch (NoSuchMethodException e) {
529                        throw new IllegalStateException(e);
530                } catch (InvocationTargetException e) {
531                        throw new IllegalStateException(e);
532                } catch (IllegalAccessException e) {
533                        throw new IllegalStateException(e);
534                }
535        }
536
537        public static Class<?> getClass(String className) {
538                try {
539                        return Class.forName(className);
540                } catch (ClassNotFoundException e) {
541                        throw new IllegalArgumentException(e);
542                }
543        }
544
545        @SuppressWarnings("unchecked")
546        public static <T> Class<? extends T> getTypedClass(String className) {
547                try {
548                        return (Class<? extends T>) Class.forName(className);
549                } catch (ClassNotFoundException e) {
550                        throw new IllegalArgumentException(e);
551                }
552        }
553
554        public static <T> T newInstance(String className) {
555                @SuppressWarnings("unchecked")
556                Class<T> clazz = (Class<T>) getClass(className);
557                return (T) newInstance(clazz);
558        }
559
560        public static <T> T newInstance(Class<T> instanceClass) {
561                try {
562                        return (T) instanceClass.newInstance();
563                } catch (IllegalAccessException e) {
564                        throw new IllegalArgumentException("Unexpected error", e);
565                } catch (InstantiationException e) {
566                        throw new IllegalArgumentException("Unexpected error", e);
567                }
568        }
569
570}