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